Code

Merge branch 'jn/maint-sequencer-fixes'
authorJunio C Hamano <gitster@pobox.com>
Tue, 20 Dec 2011 00:05:45 +0000 (16:05 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 20 Dec 2011 00:05:45 +0000 (16:05 -0800)
* jn/maint-sequencer-fixes:
  revert: stop creating and removing sequencer-old directory
  Revert "reset: Make reset remove the sequencer state"
  revert: do not remove state until sequence is finished
  revert: allow single-pick in the middle of cherry-pick sequence
  revert: pass around rev-list args in already-parsed form
  revert: allow cherry-pick --continue to commit before resuming
  revert: give --continue handling its own function

branch.c
builtin/revert.c
sequencer.c
sequencer.h
t/t3510-cherry-pick-sequence.sh
t/t7106-reset-sequence.sh [deleted file]

index a715a1174982970943ed2c4fc1c2ab4906699fdc..c2e625f40c9d77fa048b905ca4a3982da563b299 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -3,7 +3,6 @@
 #include "refs.h"
 #include "remote.h"
 #include "commit.h"
-#include "sequencer.h"
 
 struct tracking {
        struct refspec spec;
@@ -280,5 +279,4 @@ void remove_branch_state(void)
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
        unlink(git_path("SQUASH_MSG"));
-       remove_sequencer_state(0);
 }
index 1ea525c10e4c00b66006bf46465ebd490823f25f..028bcbcd75d21ada2bc0406ff74e3073014bb41f 100644 (file)
@@ -60,13 +60,14 @@ struct replay_opts {
        int allow_rerere_auto;
 
        int mainline;
-       int commit_argc;
-       const char **commit_argv;
 
        /* Merge strategy */
        const char *strategy;
        const char **xopts;
        size_t xopts_nr, xopts_alloc;
+
+       /* Only used by REPLAY_NONE */
+       struct rev_info *revs;
 };
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@ -169,9 +170,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                        die(_("program error"));
        }
 
-       opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str,
-                                       PARSE_OPT_KEEP_ARGV0 |
-                                       PARSE_OPT_KEEP_UNKNOWN);
+       argc = parse_options(argc, argv, NULL, options, usage_str,
+                       PARSE_OPT_KEEP_ARGV0 |
+                       PARSE_OPT_KEEP_UNKNOWN);
 
        /* Check for incompatible subcommands */
        verify_opt_mutually_compatible(me,
@@ -213,9 +214,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                                NULL);
        }
 
-       else if (opts->commit_argc < 2)
-               usage_with_options(usage_str, options);
-
        if (opts->allow_ff)
                verify_opt_compatible(me, "--ff",
                                "--signoff", opts->signoff,
@@ -223,7 +221,20 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                                "-x", opts->record_origin,
                                "--edit", opts->edit,
                                NULL);
-       opts->commit_argv = argv;
+
+       if (opts->subcommand != REPLAY_NONE) {
+               opts->revs = NULL;
+       } else {
+               opts->revs = xmalloc(sizeof(*opts->revs));
+               init_revisions(opts->revs, NULL);
+               opts->revs->no_walk = 1;
+               if (argc < 2)
+                       usage_with_options(usage_str, options);
+               argc = setup_revisions(argc, argv, opts->revs, NULL);
+       }
+
+       if (argc > 1)
+               usage_with_options(usage_str, options);
 }
 
 struct commit_message {
@@ -631,23 +642,15 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        return res;
 }
 
-static void prepare_revs(struct rev_info *revs, struct replay_opts *opts)
+static void prepare_revs(struct replay_opts *opts)
 {
-       int argc;
-
-       init_revisions(revs, NULL);
-       revs->no_walk = 1;
        if (opts->action != REVERT)
-               revs->reverse = 1;
-
-       argc = setup_revisions(opts->commit_argc, opts->commit_argv, revs, NULL);
-       if (argc > 1)
-               usage(*revert_or_cherry_pick_usage(opts));
+               opts->revs->reverse ^= 1;
 
-       if (prepare_revision_walk(revs))
+       if (prepare_revision_walk(opts->revs))
                die(_("revision walk setup failed"));
 
-       if (!revs->commits)
+       if (!opts->revs->commits)
                die(_("empty commit set passed"));
 }
 
@@ -844,14 +847,13 @@ static void read_populate_opts(struct replay_opts **opts_ptr)
 static void walk_revs_populate_todo(struct commit_list **todo_list,
                                struct replay_opts *opts)
 {
-       struct rev_info revs;
        struct commit *commit;
        struct commit_list **next;
 
-       prepare_revs(&revs, opts);
+       prepare_revs(opts);
 
        next = todo_list;
-       while ((commit = get_revision(&revs)))
+       while ((commit = get_revision(opts->revs)))
                next = commit_list_append(commit, next);
 }
 
@@ -942,7 +944,7 @@ static int sequencer_rollback(struct replay_opts *opts)
        }
        if (reset_for_rollback(sha1))
                goto fail;
-       remove_sequencer_state(1);
+       remove_sequencer_state();
        strbuf_release(&buf);
        return 0;
 fail:
@@ -1016,33 +1018,64 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
        for (cur = todo_list; cur; cur = cur->next) {
                save_todo(cur, opts);
                res = do_pick_commit(cur->item, opts);
-               if (res) {
-                       if (!cur->next)
-                               /*
-                                * An error was encountered while
-                                * picking the last commit; the
-                                * sequencer state is useless now --
-                                * the user simply needs to resolve
-                                * the conflict and commit
-                                */
-                               remove_sequencer_state(0);
+               if (res)
                        return res;
-               }
        }
 
        /*
         * Sequence of picks finished successfully; cleanup by
         * removing the .git/sequencer directory
         */
-       remove_sequencer_state(1);
+       remove_sequencer_state();
        return 0;
 }
 
+static int continue_single_pick(void)
+{
+       const char *argv[] = { "commit", NULL };
+
+       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+           !file_exists(git_path("REVERT_HEAD")))
+               return error(_("no cherry-pick or revert in progress"));
+       return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int sequencer_continue(struct replay_opts *opts)
+{
+       struct commit_list *todo_list = NULL;
+
+       if (!file_exists(git_path(SEQ_TODO_FILE)))
+               return continue_single_pick();
+       read_populate_opts(&opts);
+       read_populate_todo(&todo_list, opts);
+
+       /* Verify that the conflict has been resolved */
+       if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
+           file_exists(git_path("REVERT_HEAD"))) {
+               int ret = continue_single_pick();
+               if (ret)
+                       return ret;
+       }
+       if (index_differs_from("HEAD", 0))
+               return error_dirty_index(opts);
+       todo_list = todo_list->next;
+       return pick_commits(todo_list, opts);
+}
+
+static int single_pick(struct commit *cmit, struct replay_opts *opts)
+{
+       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+       return do_pick_commit(cmit, opts);
+}
+
 static int pick_revisions(struct replay_opts *opts)
 {
        struct commit_list *todo_list = NULL;
        unsigned char sha1[20];
 
+       if (opts->subcommand == REPLAY_NONE)
+               assert(opts->revs);
+
        read_and_refresh_cache(opts);
 
        /*
@@ -1051,21 +1084,32 @@ static int pick_revisions(struct replay_opts *opts)
         * one that is being continued
         */
        if (opts->subcommand == REPLAY_REMOVE_STATE) {
-               remove_sequencer_state(1);
+               remove_sequencer_state();
                return 0;
        }
        if (opts->subcommand == REPLAY_ROLLBACK)
                return sequencer_rollback(opts);
-       if (opts->subcommand == REPLAY_CONTINUE) {
-               if (!file_exists(git_path(SEQ_TODO_FILE)))
-                       return error(_("No %s in progress"), action_name(opts));
-               read_populate_opts(&opts);
-               read_populate_todo(&todo_list, opts);
-
-               /* Verify that the conflict has been resolved */
-               if (!index_differs_from("HEAD", 0))
-                       todo_list = todo_list->next;
-               return pick_commits(todo_list, opts);
+       if (opts->subcommand == REPLAY_CONTINUE)
+               return sequencer_continue(opts);
+
+       /*
+        * If we were called as "git cherry-pick <commit>", just
+        * cherry-pick/revert it, set CHERRY_PICK_HEAD /
+        * REVERT_HEAD, and don't touch the sequencer state.
+        * This means it is possible to cherry-pick in the middle
+        * of a cherry-pick sequence.
+        */
+       if (opts->revs->cmdline.nr == 1 &&
+           opts->revs->cmdline.rev->whence == REV_CMD_REV &&
+           opts->revs->no_walk &&
+           !opts->revs->cmdline.rev->flags) {
+               struct commit *cmit;
+               if (prepare_revision_walk(opts->revs))
+                       die(_("revision walk setup failed"));
+               cmit = get_revision(opts->revs);
+               if (!cmit || get_revision(opts->revs))
+                       die("BUG: expected exactly one commit from walk");
+               return single_pick(cmit, opts);
        }
 
        /*
index bc2c046aab23c5b3de8caa02c23703856ee86fb4..d1f28a6945cbbdc8f58a32d7bb481ecd4af6cc39 100644 (file)
@@ -3,17 +3,11 @@
 #include "strbuf.h"
 #include "dir.h"
 
-void remove_sequencer_state(int aggressive)
+void remove_sequencer_state(void)
 {
        struct strbuf seq_dir = STRBUF_INIT;
-       struct strbuf seq_old_dir = STRBUF_INIT;
 
        strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR));
-       strbuf_addf(&seq_old_dir, "%s", git_path(SEQ_OLD_DIR));
-       remove_dir_recursively(&seq_old_dir, 0);
-       rename(git_path(SEQ_DIR), git_path(SEQ_OLD_DIR));
-       if (aggressive)
-               remove_dir_recursively(&seq_old_dir, 0);
+       remove_dir_recursively(&seq_dir, 0);
        strbuf_release(&seq_dir);
-       strbuf_release(&seq_old_dir);
 }
index f435fdb4b147d2e2ce5480ddb4832228af1cb73b..2d4528f2928053827aedd0b737bf01985f1c4957 100644 (file)
@@ -2,19 +2,11 @@
 #define SEQUENCER_H
 
 #define SEQ_DIR                "sequencer"
-#define SEQ_OLD_DIR    "sequencer-old"
 #define SEQ_HEAD_FILE  "sequencer/head"
 #define SEQ_TODO_FILE  "sequencer/todo"
 #define SEQ_OPTS_FILE  "sequencer/opts"
 
-/*
- * Removes SEQ_OLD_DIR and renames SEQ_DIR to SEQ_OLD_DIR, ignoring
- * any errors.  Intended to be used by 'git reset'.
- *
- * With the aggressive flag, it additionally removes SEQ_OLD_DIR,
- * ignoring any errors.  Inteded to be used by the sequencer's
- * '--quit' subcommand.
- */
-void remove_sequencer_state(int aggressive);
+/* Removes SEQ_DIR. */
+extern void remove_sequencer_state(void);
 
 #endif
index 2c4c1c851dcbb1cd38edc2a2620e2b04e36f7192..e80050e1fef9c7c2d83a34aaa671415ea168e8c4 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='Test cherry-pick continuation features
 
+ +  conflicting: rewrites unrelated to conflicting
   + yetanotherpick: rewrites foo to e
   + anotherpick: rewrites foo to d
   + picked: rewrites foo to c
@@ -27,6 +28,7 @@ test_cmp_rev () {
 }
 
 test_expect_success setup '
+       git config advice.detachedhead false
        echo unrelated >unrelated &&
        git add unrelated &&
        test_commit initial foo a &&
@@ -35,8 +37,8 @@ test_expect_success setup '
        test_commit picked foo c &&
        test_commit anotherpick foo d &&
        test_commit yetanotherpick foo e &&
-       git config advice.detachedhead false
-
+       pristine_detach initial &&
+       test_commit conflicting unrelated
 '
 
 test_expect_success 'cherry-pick persists data on failure' '
@@ -48,6 +50,18 @@ test_expect_success 'cherry-pick persists data on failure' '
        test_path_is_file .git/sequencer/opts
 '
 
+test_expect_success 'cherry-pick mid-cherry-pick-sequence' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       test_cmp_rev picked CHERRY_PICK_HEAD &&
+       # "oops, I forgot that these patches rely on the change from base"
+       git checkout HEAD foo &&
+       git cherry-pick base &&
+       git cherry-pick picked &&
+       git cherry-pick --continue &&
+       git diff --exit-code anotherpick
+'
+
 test_expect_success 'cherry-pick persists opts correctly' '
        pristine_detach initial &&
        test_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick &&
@@ -189,10 +203,10 @@ test_expect_success '--abort refuses to clobber unrelated change, harder case' '
        test_cmp_rev initial HEAD
 '
 
-test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '
+test_expect_success 'cherry-pick still writes sequencer state when one commit is left' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..picked &&
-       test_path_is_missing .git/sequencer &&
+       test_path_is_dir .git/sequencer &&
        echo "resolved" >foo &&
        git add foo &&
        git commit &&
@@ -213,7 +227,7 @@ test_expect_success 'cherry-pick cleans up sequencer state when one commit is le
        test_cmp expect actual
 '
 
-test_expect_failure '--abort after last commit in sequence' '
+test_expect_success '--abort after last commit in sequence' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..picked &&
        git cherry-pick --abort &&
@@ -243,7 +257,66 @@ test_expect_success '--continue complains when there are unresolved conflicts' '
        test_must_fail git cherry-pick --continue
 '
 
-test_expect_success '--continue continues after conflicts are resolved' '
+test_expect_success '--continue of single cherry-pick' '
+       pristine_detach initial &&
+       echo c >expect &&
+       test_must_fail git cherry-pick picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       test_cmp expect foo &&
+       test_cmp_rev initial HEAD^ &&
+       git diff --exit-code HEAD &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success '--continue of single revert' '
+       pristine_detach initial &&
+       echo resolved >expect &&
+       echo "Revert \"picked\"" >expect.msg &&
+       test_must_fail git revert picked &&
+       echo resolved >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp expect foo &&
+       test_cmp_rev initial HEAD^ &&
+       git diff-tree -s --pretty=tformat:%s HEAD >msg &&
+       test_cmp expect.msg msg &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success '--continue after resolving conflicts' '
+       pristine_detach initial &&
+       echo d >expect &&
+       cat >expect.log <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_must_fail git cherry-pick base..anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual.log &&
+       test_cmp expect foo &&
+       test_cmp expect.log actual.log
+'
+
+test_expect_success '--continue after resolving conflicts and committing' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..anotherpick &&
        echo "c" >foo &&
@@ -270,6 +343,29 @@ test_expect_success '--continue continues after conflicts are resolved' '
        test_cmp expect actual
 '
 
+test_expect_success '--continue asks for help after resolving patch to nil' '
+       pristine_detach conflicting &&
+       test_must_fail git cherry-pick initial..picked &&
+
+       test_cmp_rev unrelatedpick CHERRY_PICK_HEAD &&
+       git checkout HEAD -- unrelated &&
+       test_must_fail git cherry-pick --continue 2>msg &&
+       test_i18ngrep "The previous cherry-pick is now empty" msg
+'
+
+test_expect_success 'follow advice and skip nil patch' '
+       pristine_detach conflicting &&
+       test_must_fail git cherry-pick initial..picked &&
+
+       git checkout HEAD -- unrelated &&
+       test_must_fail git cherry-pick --continue &&
+       git reset &&
+       git cherry-pick --continue &&
+
+       git rev-list initial..HEAD >commits &&
+       test_line_count = 3 commits
+'
+
 test_expect_success '--continue respects opts' '
        pristine_detach initial &&
        test_must_fail git cherry-pick -x base..anotherpick &&
@@ -288,6 +384,29 @@ test_expect_success '--continue respects opts' '
        grep "cherry picked from" anotherpick_msg
 '
 
+test_expect_success '--continue of single-pick respects -x' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -x picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD >msg &&
+       grep "cherry picked from" msg
+'
+
+test_expect_success '--continue respects -x in first commit in multi-pick' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -x picked anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD^ >msg &&
+       picked=$(git rev-parse --verify picked) &&
+       grep "cherry picked from.*$picked" msg
+'
+
 test_expect_success '--signoff is not automatically propagated to resolved conflict' '
        pristine_detach initial &&
        test_must_fail git cherry-pick --signoff base..anotherpick &&
@@ -306,6 +425,32 @@ test_expect_success '--signoff is not automatically propagated to resolved confl
        grep "Signed-off-by:" anotherpick_msg
 '
 
+test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s picked anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp_rev initial HEAD^^ &&
+       git cat-file commit HEAD^ >msg &&
+       ! grep Signed-off-by: msg
+'
+
+test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp_rev initial HEAD^ &&
+       git cat-file commit HEAD >msg &&
+       ! grep Signed-off-by: msg
+'
+
 test_expect_success 'malformed instruction sheet 1' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..anotherpick &&
@@ -328,4 +473,9 @@ test_expect_success 'malformed instruction sheet 2' '
        test_must_fail git cherry-pick --continue
 '
 
+test_expect_success 'empty commit set' '
+       pristine_detach initial &&
+       test_expect_code 128 git cherry-pick base..base
+'
+
 test_done
diff --git a/t/t7106-reset-sequence.sh b/t/t7106-reset-sequence.sh
deleted file mode 100755 (executable)
index 83f7ea5..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/bin/sh
-
-test_description='Test interaction of reset --hard with sequencer
-
-  + anotherpick: rewrites foo to d
-  + picked: rewrites foo to c
-  + unrelatedpick: rewrites unrelated to reallyunrelated
-  + base: rewrites foo to b
-  + initial: writes foo as a, unrelated as unrelated
-'
-
-. ./test-lib.sh
-
-pristine_detach () {
-       git cherry-pick --quit &&
-       git checkout -f "$1^0" &&
-       git read-tree -u --reset HEAD &&
-       git clean -d -f -f -q -x
-}
-
-test_expect_success setup '
-       echo unrelated >unrelated &&
-       git add unrelated &&
-       test_commit initial foo a &&
-       test_commit base foo b &&
-       test_commit unrelatedpick unrelated reallyunrelated &&
-       test_commit picked foo c &&
-       test_commit anotherpick foo d &&
-       git config advice.detachedhead false
-
-'
-
-test_expect_success 'reset --hard cleans up sequencer state, providing one-level undo' '
-       pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
-       test_path_is_dir .git/sequencer &&
-       git reset --hard &&
-       test_path_is_missing .git/sequencer &&
-       test_path_is_dir .git/sequencer-old &&
-       git reset --hard &&
-       test_path_is_missing .git/sequencer-old
-'
-
-test_expect_success 'cherry-pick --abort does not leave sequencer-old dir' '
-       pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
-       git cherry-pick --abort &&
-       test_path_is_missing .git/sequencer &&
-       test_path_is_missing .git/sequencer-old
-'
-
-test_done