{"thread":{"id":"22279","subject":"[PATCH v2 3/5] Documentation: reset: describe new \"--keep\" option","startedAt":"2010-01-19T04:25:56Z","lastAt":"2010-01-23T04:18:28Z","messageCount":8,"participants":["Christian Couder","Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":5},"messages":[{"id":"132064","messageId":"20100119042404.4510.48855.chriscool@tuxfamily.org","threadId":"22279","inReplyTo":null,"subject":"[PATCH v2 0/5] add \"--keep\" option to \"git reset\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2010-01-19T04:25:56Z","receivedAt":"2010-01-19T04:25:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Changes since the previous series are the following:\n\n- removed parts of the commit message that contrasted \"--keep\" with \"--merge\"\n- improved wording in the documentation\n- added a new patch to disallow \"--keep\" when there are unmerged entries\n- added some tests for --keep in t7111-reset-table.sh\n\nChristian Couder (5):\n  reset: add option \"--keep\" to \"git reset\"\n  reset: add test cases for \"--keep\" option\n  Documentation: reset: describe new \"--keep\" option\n  reset: disallow \"reset --keep\" outside a work tree\n  reset: disallow using --keep when there are unmerged entries\n\n Documentation/git-reset.txt |   48 +++++++++++++++++-\n builtin-reset.c             |   46 ++++++++++++++---\n t/t7103-reset-bare.sh       |   25 ++++++---\n t/t7110-reset-merge.sh      |  116 ++++++++++++++++++++++++++++++++++++++++++-\n t/t7111-reset-table.sh      |    8 +++\n 5 files changed, 223 insertions(+), 20 deletions(-)\n"},{"id":"132066","messageId":"20100119042602.4510.24100.chriscool@tuxfamily.org","threadId":"22279","inReplyTo":"20100119042404.4510.48855.chriscool@tuxfamily.org","subject":"[PATCH v2 1/5] reset: add option \"--keep\" to \"git reset\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2010-01-19T04:25:57Z","receivedAt":"2010-01-19T04:25:57Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"The purpose of this new option is to discard some of the\nlast commits but to keep current changes in the work tree.\n\nThe use case is when you work on something and commit\nthat work. And then you work on something else that touches\nother files, but you don't commit it yet. Then you realize\nthat what you commited when you worked on the first thing\nis not good or belongs to another branch.\n\nSo you want to get rid of the previous commits (at least in\nthe current branch) but you want to make sure that you keep\nthe changes you have in the work tree. And you are pretty\nsure that your changes are independent from what you\npreviously commited, so you don't want the reset to succeed\nif the previous commits changed a file that you also\nchanged in your work tree.\n\nThe table below shows what happens when running\n\"git reset --keep target\" to reset the HEAD to another\ncommit (as a special case \"target\" could be the same as\nHEAD).\n\nworking index HEAD target         working index HEAD\n----------------------------------------------------\n  A      B     C     D   --keep    (disallowed)\n  A      B     C     C   --keep     A      C     C\n  B      B     C     D   --keep    (disallowed)\n  B      B     C     C   --keep     B      C     C\n\nIn this table, A, B and C are some different states of\na file. For example the last line of the table means\nthat if a file is in state B in the working tree and\nthe index, and in a different state C in HEAD and in\nthe target, then \"git reset --keep target\" will put\nthe file in state B in the working tree, and in state\nC in the index and in HEAD.\n\nThe following table shows what happens on unmerged entries:\n\nworking index HEAD target         working index HEAD\n----------------------------------------------------\n X       U     A    B     --keep  (disallowed)\n X       U     A    A     --keep   X       A     A\n\nIn this table X can be any state and U means an unmerged\nentry.\n\nThough the error message when \"reset --keep\" is disallowed\non unmerged entries is something like:\n\nerror: Entry 'file1' would be overwritten by merge. Cannot merge.\nfatal: Could not reset index file to revision 'HEAD^'.\n\nwhich is not very nice.\n\nA following patch will add some test cases for \"--keep\".\n\nThe \"--keep\" option is implemented by doing a 2 way merge\nbetween HEAD and the reset target, and if this succeeds\nby doing a mixed reset to the target.\n\nThe code comes from the sequencer GSoC project, where\nsuch an option was developed by Stephan Beyer:\n\ngit://repo.or.cz/git/sbeyer.git\n\n(at commit 5a78908b70ceb5a4ea9fd4b82f07ceba1f019079)\n\nBut in the sequencer project the \"reset\" flag was set\nin the \"struct unpack_trees_options\" passed to\n\"unpack_trees()\". With this flag the changes in the\nworking tree were discarded if the file was different\nbetween HEAD and the reset target.\n\nMentored-by: Daniel Barkalow <barkalow@iabervon.org>\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Stephan Beyer <s-beyer@gmx.net>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin-reset.c |   29 ++++++++++++++++++++++++-----\n 1 files changed, 24 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin-reset.c b/builtin-reset.c\nindex 0f5022e..da61f20 100644\n--- a/builtin-reset.c\n+++ b/builtin-reset.c\n@@ -22,13 +22,15 @@\n #include \"cache-tree.h\"\n \n static const char * const git_reset_usage[] = {\n-\t\"git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]\",\n+\t\"git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]\",\n \t\"git reset [--mixed] <commit> [--] <paths>...\",\n \tNULL\n };\n \n-enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };\n-static const char *reset_type_names[] = { \"mixed\", \"soft\", \"hard\", \"merge\", NULL };\n+enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE };\n+static const char *reset_type_names[] = {\n+\t\"mixed\", \"soft\", \"hard\", \"merge\", \"keep\", NULL\n+};\n \n static char *args_to_str(const char **argv)\n {\n@@ -71,6 +73,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet\n \tif (!quiet)\n \t\topts.verbose_update = 1;\n \tswitch (reset_type) {\n+\tcase KEEP:\n \tcase MERGE:\n \t\topts.update = 1;\n \t\tbreak;\n@@ -85,6 +88,16 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet\n \n \tread_cache_unmerged();\n \n+\tif (reset_type == KEEP) {\n+\t\tunsigned char head_sha1[20];\n+\t\tif (get_sha1(\"HEAD\", head_sha1))\n+\t\t\treturn error(\"You do not have a valid HEAD.\");\n+\t\tif (!fill_tree_descriptor(desc, head_sha1))\n+\t\t\treturn error(\"Failed to find tree of HEAD.\");\n+\t\tnr++;\n+\t\topts.fn = twoway_merge;\n+\t}\n+\n \tif (!fill_tree_descriptor(desc + nr - 1, sha1))\n \t\treturn error(\"Failed to find tree of %s.\", sha1_to_hex(sha1));\n \tif (unpack_trees(nr, desc, &opts))\n@@ -229,6 +242,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\t\t\t\"reset HEAD, index and working tree\", HARD),\n \t\tOPT_SET_INT(0, \"merge\", &reset_type,\n \t\t\t\t\"reset HEAD, index and working tree\", MERGE),\n+\t\tOPT_SET_INT(0, \"keep\", &reset_type,\n+\t\t\t\t\"reset HEAD but keep local changes\", KEEP),\n \t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\tOPT_END()\n \t};\n@@ -317,9 +332,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == SOFT) {\n \t\tif (is_merge() || read_cache() < 0 || unmerged_cache())\n \t\t\tdie(\"Cannot do a soft reset in the middle of a merge.\");\n+\t} else {\n+\t\tint err = reset_index_file(sha1, reset_type, quiet);\n+\t\tif (reset_type == KEEP)\n+\t\t\terr = err || reset_index_file(sha1, MIXED, quiet);\n+\t\tif (err)\n+\t\t\tdie(\"Could not reset index file to revision '%s'.\", rev);\n \t}\n-\telse if (reset_index_file(sha1, reset_type, quiet))\n-\t\tdie(\"Could not reset index file to revision '%s'.\", rev);\n \n \t/* Any resets update HEAD to the head being switched to,\n \t * saving the previous head in ORIG_HEAD before. */\n-- \n1.6.6.271.gc8799\n"},{"id":"132063","messageId":"20100119042602.4510.5880.chriscool@tuxfamily.org","threadId":"22279","inReplyTo":"20100119042404.4510.48855.chriscool@tuxfamily.org","subject":"[PATCH v2 2/5] reset: add test cases for \"--keep\" option","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2010-01-19T04:25:58Z","receivedAt":"2010-01-19T04:25:58Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This shows that with the \"--keep\" option, changes that are both in\nthe work tree and the index are kept in the work tree after the\nreset (but discarded in the index).\n\nIn the case of unmerged entries, we can see that \"git reset --keep\"\nworks only when the target state is the same as HEAD. And then the\nwork tree is not reset.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t7110-reset-merge.sh |  119 +++++++++++++++++++++++++++++++++++++++++++++++-\n t/t7111-reset-table.sh |    8 +++\n 2 files changed, 126 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh\nindex 8704d00..1a9c1c7 100755\n--- a/t/t7110-reset-merge.sh\n+++ b/t/t7110-reset-merge.sh\n@@ -3,7 +3,7 @@\n # Copyright (c) 2009 Christian Couder\n #\n \n-test_description='Tests for \"git reset --merge\"'\n+test_description='Tests for \"git reset\" with \"--merge\" and \"--keep\" options'\n \n . ./test-lib.sh\n \n@@ -47,6 +47,30 @@ test_expect_success 'reset --merge is ok when switching back' '\n #\n #           working index HEAD target         working index HEAD\n #           ----------------------------------------------------\n+# file1:     C       C     C    D     --keep   D       D     D\n+# file2:     C       D     D    D     --keep   C       D     D\n+test_expect_success 'reset --keep is ok with changes in file it does not touch' '\n+    git reset --hard second &&\n+    cat file1 >file2 &&\n+    git reset --keep HEAD^ &&\n+    ! grep 4 file1 &&\n+    grep 4 file2 &&\n+    test \"$(git rev-parse HEAD)\" = \"$(git rev-parse initial)\" &&\n+    test -z \"$(git diff --cached)\"\n+'\n+\n+test_expect_success 'reset --keep is ok when switching back' '\n+    git reset --keep second &&\n+    grep 4 file1 &&\n+    grep 4 file2 &&\n+    test \"$(git rev-parse HEAD)\" = \"$(git rev-parse second)\" &&\n+    test -z \"$(git diff --cached)\"\n+'\n+\n+# The next test will test the following:\n+#\n+#           working index HEAD target         working index HEAD\n+#           ----------------------------------------------------\n # file1:     B       B     C    D     --merge  D       D     D\n # file2:     C       D     D    D     --merge  C       D     D\n test_expect_success 'reset --merge discards changes added to index (1)' '\n@@ -78,6 +102,18 @@ test_expect_success 'reset --merge is ok again when switching back (1)' '\n #\n #           working index HEAD target         working index HEAD\n #           ----------------------------------------------------\n+# file1:     B       B     C    D     --keep   (disallowed)\n+test_expect_success 'reset --keep fails with changes in index in files it touches' '\n+    git reset --hard second &&\n+    echo \"line 5\" >> file1 &&\n+    git add file1 &&\n+    test_must_fail git reset --keep HEAD^\n+'\n+\n+# The next test will test the following:\n+#\n+#           working index HEAD target         working index HEAD\n+#           ----------------------------------------------------\n # file1:     C       C     C    D     --merge  D       D     D\n # file2:     C       C     D    D     --merge  D       D     D\n test_expect_success 'reset --merge discards changes added to index (2)' '\n@@ -104,6 +140,30 @@ test_expect_success 'reset --merge is ok again when switching back (2)' '\n #\n #           working index HEAD target         working index HEAD\n #           ----------------------------------------------------\n+# file1:     C       C     C    D     --keep   D       D     D\n+# file2:     C       C     D    D     --keep   C       D     D\n+test_expect_success 'reset --keep keeps changes it does not touch' '\n+    git reset --hard second &&\n+    echo \"line 4\" >> file2 &&\n+    git add file2 &&\n+    git reset --keep HEAD^ &&\n+    grep 4 file2 &&\n+    test \"$(git rev-parse HEAD)\" = \"$(git rev-parse initial)\" &&\n+    test -z \"$(git diff --cached)\"\n+'\n+\n+test_expect_success 'reset --keep keeps changes when switching back' '\n+    git reset --keep second &&\n+    grep 4 file2 &&\n+    grep 4 file1 &&\n+    test \"$(git rev-parse HEAD)\" = \"$(git rev-parse second)\" &&\n+    test -z \"$(git diff --cached)\"\n+'\n+\n+# The next test will test the following:\n+#\n+#           working index HEAD target         working index HEAD\n+#           ----------------------------------------------------\n # file1:     A       B     B    C     --merge  (disallowed)\n test_expect_success 'reset --merge fails with changes in file it touches' '\n     git reset --hard second &&\n@@ -116,6 +176,22 @@ test_expect_success 'reset --merge fails with changes in file it touches' '\n     grep file1 err.log | grep \"not uptodate\"\n '\n \n+# The next test will test the following:\n+#\n+#           working index HEAD target         working index HEAD\n+#           ----------------------------------------------------\n+# file1:     A       B     B    C     --keep   (disallowed)\n+test_expect_success 'reset --keep fails with changes in file it touches' '\n+    git reset --hard second &&\n+    echo \"line 5\" >> file1 &&\n+    test_tick &&\n+    git commit -m \"add line 5\" file1 &&\n+    sed -e \"s/line 1/changed line 1/\" <file1 >file3 &&\n+    mv file3 file1 &&\n+    test_must_fail git reset --keep HEAD^ 2>err.log &&\n+    grep file1 err.log | grep \"not uptodate\"\n+'\n+\n test_expect_success 'setup 3 different branches' '\n     git reset --hard second &&\n     git branch branch1 &&\n@@ -156,6 +232,18 @@ test_expect_success '\"reset --merge HEAD^\" is ok with pending merge' '\n #\n #           working index HEAD target         working index HEAD\n #           ----------------------------------------------------\n+# file1:     X       U     B    C     --keep   (disallowed)\n+test_expect_success '\"reset --keep HEAD^\" fails with pending merge' '\n+    git reset --hard third &&\n+    test_must_fail git merge branch1 &&\n+    test_must_fail git reset --keep HEAD^ 2>err.log &&\n+    grep file1 err.log | grep \"overwritten by merge\"\n+'\n+\n+# The next test will test the following:\n+#\n+#           working index HEAD target         working index HEAD\n+#           ----------------------------------------------------\n # file1:     X       U     B    B     --merge  B       B     B\n test_expect_success '\"reset --merge HEAD\" is ok with pending merge' '\n     git reset --hard third &&\n@@ -166,6 +254,21 @@ test_expect_success '\"reset --merge HEAD\" is ok with pending merge' '\n     test -z \"$(git diff)\"\n '\n \n+# The next test will test the following:\n+#\n+#           working index HEAD target         working index HEAD\n+#           ----------------------------------------------------\n+# file1:     X       U     B    B     --keep  X       B     B\n+test_expect_success '\"reset --keep HEAD\" is ok with pending merge' '\n+    git reset --hard third &&\n+    test_must_fail git merge branch1 &&\n+    cat file1 >orig_file1 &&\n+    git reset --keep HEAD &&\n+    test \"$(git rev-parse HEAD)\" = \"$(git rev-parse third)\" &&\n+    test -z \"$(git diff --cached)\" &&\n+    test_cmp file1 orig_file1\n+'\n+\n test_expect_success '--merge with added/deleted' '\n     git reset --hard third &&\n     rm -f file2 &&\n@@ -180,4 +283,18 @@ test_expect_success '--merge with added/deleted' '\n     git diff --exit-code --cached\n '\n \n+test_expect_success '--keep with added/deleted' '\n+    git reset --hard third &&\n+    rm -f file2 &&\n+    test_must_fail git merge branch3 &&\n+    ! test -f file2 &&\n+    test -f file3 &&\n+    git diff --exit-code file3 &&\n+    git diff --exit-code branch3 file3 &&\n+    git reset --keep HEAD &&\n+    test -f file3 &&\n+    ! test -f file2 &&\n+    git diff --exit-code --cached\n+'\n+\n test_done\ndiff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh\nindex de896c9..2ebda97 100755\n--- a/t/t7111-reset-table.sh\n+++ b/t/t7111-reset-table.sh\n@@ -44,26 +44,32 @@ A B C D soft   A B D\n A B C D mixed  A D D\n A B C D hard   D D D\n A B C D merge  XXXXX\n+A B C D keep   XXXXX\n A B C C soft   A B C\n A B C C mixed  A C C\n A B C C hard   C C C\n A B C C merge  XXXXX\n+A B C C keep   A C C\n B B C D soft   B B D\n B B C D mixed  B D D\n B B C D hard   D D D\n B B C D merge  D D D\n+B B C D keep   XXXXX\n B B C C soft   B B C\n B B C C mixed  B C C\n B B C C hard   C C C\n B B C C merge  C C C\n+B B C C keep   B C C\n B C C D soft   B C D\n B C C D mixed  B D D\n B C C D hard   D D D\n B C C D merge  XXXXX\n+B C C D keep   XXXXX\n B C C C soft   B C C\n B C C C mixed  B C C\n B C C C hard   C C C\n B C C C merge  B C C\n+B C C C keep   B C C\n EOF\n \n test_expect_success 'setting up branches to test with unmerged entries' '\n@@ -104,10 +110,12 @@ X U B C soft   XXXXX\n X U B C mixed  X C C\n X U B C hard   C C C\n X U B C merge  C C C\n+X U B C keep   XXXXX\n X U B B soft   XXXXX\n X U B B mixed  X B B\n X U B B hard   B B B\n X U B B merge  B B B\n+X U B B keep   X B B\n EOF\n \n test_done\n-- \n1.6.6.271.gc8799\n"},{"id":"132062","messageId":"20100119042602.4510.8666.chriscool@tuxfamily.org","threadId":"22279","inReplyTo":"20100119042404.4510.48855.chriscool@tuxfamily.org","subject":"[PATCH v2 3/5] Documentation: reset: describe new \"--keep\" option","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2010-01-19T04:25:59Z","receivedAt":"2010-01-19T04:25:59Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"and give an example to show how it can be used.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-reset.txt |   47 ++++++++++++++++++++++++++++++++++++++++++-\n 1 files changed, 46 insertions(+), 1 deletions(-)\n\ndiff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt\nindex 168db08..90239f5 100644\n--- a/Documentation/git-reset.txt\n+++ b/Documentation/git-reset.txt\n@@ -8,7 +8,7 @@ git-reset - Reset current HEAD to the specified state\n SYNOPSIS\n --------\n [verse]\n-'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]\n+'git reset' [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]\n 'git reset' [-q] [<commit>] [--] <paths>...\n 'git reset' --patch [<commit>] [--] [<paths>...]\n \n@@ -52,6 +52,11 @@ OPTIONS\n \tand updates the files that are different between the named commit\n \tand the current commit in the working tree.\n \n+--keep::\n+\tResets the index to match the tree recorded by the named commit,\n+\tbut keep changes in the working tree. Aborts if the reset would\n+\tchange files that are already modified in the working tree.\n+\n -p::\n --patch::\n \tInteractively select hunks in the difference between the index\n@@ -93,6 +98,7 @@ in the index and in state D in HEAD.\n \t\t\t\t--mixed  A       D     D\n \t\t\t\t--hard   D       D     D\n \t\t\t\t--merge (disallowed)\n+\t\t\t\t--keep  (disallowed)\n \n       working index HEAD target         working index HEAD\n       ----------------------------------------------------\n@@ -100,6 +106,7 @@ in the index and in state D in HEAD.\n \t\t\t\t--mixed  A       C     C\n \t\t\t\t--hard   C       C     C\n \t\t\t\t--merge (disallowed)\n+\t\t\t\t--keep   A       C     C\n \n       working index HEAD target         working index HEAD\n       ----------------------------------------------------\n@@ -107,6 +114,7 @@ in the index and in state D in HEAD.\n \t\t\t\t--mixed  B       D     D\n \t\t\t\t--hard   D       D     D\n \t\t\t\t--merge  D       D     D\n+\t\t\t\t--keep  (disallowed)\n \n       working index HEAD target         working index HEAD\n       ----------------------------------------------------\n@@ -114,6 +122,7 @@ in the index and in state D in HEAD.\n \t\t\t\t--mixed  B       C     C\n \t\t\t\t--hard   C       C     C\n \t\t\t\t--merge  C       C     C\n+\t\t\t\t--keep   B       C     C\n \n       working index HEAD target         working index HEAD\n       ----------------------------------------------------\n@@ -121,6 +130,7 @@ in the index and in state D in HEAD.\n \t\t\t\t--mixed  B       D     D\n \t\t\t\t--hard   D       D     D\n \t\t\t\t--merge (disallowed)\n+\t\t\t\t--keep  (disallowed)\n \n       working index HEAD target         working index HEAD\n       ----------------------------------------------------\n@@ -128,6 +138,7 @@ in the index and in state D in HEAD.\n \t\t\t\t--mixed  B       C     C\n \t\t\t\t--hard   C       C     C\n \t\t\t\t--merge  B       C     C\n+\t\t\t\t--keep   B       C     C\n \n \"reset --merge\" is meant to be used when resetting out of a conflicted\n merge. Any mergy operation guarantees that the work tree file that is\n@@ -138,6 +149,14 @@ between the index and the work tree, then it means that we are not\n resetting out from a state that a mergy operation left after failing\n with a conflict. That is why we disallow --merge option in this case.\n \n+\"reset --keep\" is meant to be used when remove some of the last\n+commits in the current branch while keep changes in the working tree.\n+If there could be conflicts between the changes in the commit we want\n+to remove and the changes in the working tree we want to keep, the\n+reset is disallowed. That's why it is disallowed if there are both\n+changes between the working tree and HEAD, and between HEAD and the\n+target.\n+\n The following tables show what happens when there are unmerged\n entries:\n \n@@ -147,6 +166,7 @@ entries:\n \t\t\t\t--mixed  X       B     B\n \t\t\t\t--hard   B       B     B\n \t\t\t\t--merge  B       B     B\n+\t\t\t\t--keep  (disallowed)\n \n       working index HEAD target         working index HEAD\n       ----------------------------------------------------\n@@ -154,6 +174,7 @@ entries:\n \t\t\t\t--mixed  X       A     A\n \t\t\t\t--hard   A       A     A\n \t\t\t\t--merge  A       A     A\n+\t\t\t\t--keep   X       A     A\n \n X means any state and U means an unmerged index.\n \n@@ -325,6 +346,30 @@ $ git add frotz.c                           <3>\n <2> This commits all other changes in the index.\n <3> Adds the file to the index again.\n \n+Keep changes in working tree while discarding some previous commits::\n++\n+Suppose you are working on something and you commit it, and then you\n+continue working a bit more, but now you think that what you have in\n+your working tree should be in another branch that has nothing to do\n+with what you commited previously. You can start a new branch and\n+reset it while keeping the changes in your work tree.\n++\n+------------\n+$ git tag start\n+$ git branch branch1\n+$ edit\n+$ git commit ...                            <1>\n+$ edit\n+$ git branch branch2                        <2>\n+$ git reset --keep start                    <3>\n+------------\n++\n+<1> This commits your first edits in branch1.\n+<2> This creates branch2, but unfortunately it contains the previous\n+commit that you don't want in this branch.\n+<3> This removes the unwanted previous commit, but this keeps the\n+changes in your working tree.\n+\n Author\n ------\n Written by Junio C Hamano <gitster@pobox.com> and Linus Torvalds <torvalds@osdl.org>\n-- \n1.6.6.271.gc8799\n"},{"id":"132067","messageId":"20100119042602.4510.2378.chriscool@tuxfamily.org","threadId":"22279","inReplyTo":"20100119042404.4510.48855.chriscool@tuxfamily.org","subject":"[PATCH v2 4/5] reset: disallow \"reset --keep\" outside a work tree","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2010-01-19T04:26:00Z","receivedAt":"2010-01-19T04:26:00Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"It is safer and consistent with \"--merge\" and \"--hard\" resets to disallow\n\"git reset --keep\" outside a work tree.\n\nSo let's just do that and add some tests while at it.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin-reset.c       |    2 +-\n t/t7103-reset-bare.sh |   25 +++++++++++++++++--------\n 2 files changed, 18 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin-reset.c b/builtin-reset.c\nindex da61f20..52584af 100644\n--- a/builtin-reset.c\n+++ b/builtin-reset.c\n@@ -319,7 +319,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == NONE)\n \t\treset_type = MIXED; /* by default */\n \n-\tif (reset_type == HARD || reset_type == MERGE)\n+\tif (reset_type != SOFT && reset_type != MIXED)\n \t\tsetup_work_tree();\n \n \tif (reset_type == MIXED && is_bare_repository())\ndiff --git a/t/t7103-reset-bare.sh b/t/t7103-reset-bare.sh\nindex afb55b3..1eef93c 100755\n--- a/t/t7103-reset-bare.sh\n+++ b/t/t7103-reset-bare.sh\n@@ -11,21 +11,26 @@ test_expect_success 'setup non-bare' '\n \tgit commit -a -m two\n '\n \n-test_expect_success 'hard reset requires a worktree' '\n+test_expect_success '\"hard\" reset requires a worktree' '\n \t(cd .git &&\n \t test_must_fail git reset --hard)\n '\n \n-test_expect_success 'merge reset requires a worktree' '\n+test_expect_success '\"merge\" reset requires a worktree' '\n \t(cd .git &&\n \t test_must_fail git reset --merge)\n '\n \n-test_expect_success 'mixed reset is ok' '\n+test_expect_success '\"keep\" reset requires a worktree' '\n+\t(cd .git &&\n+\t test_must_fail git reset --keep)\n+'\n+\n+test_expect_success '\"mixed\" reset is ok' '\n \t(cd .git && git reset)\n '\n \n-test_expect_success 'soft reset is ok' '\n+test_expect_success '\"soft\" reset is ok' '\n \t(cd .git && git reset --soft)\n '\n \n@@ -40,19 +45,23 @@ test_expect_success 'setup bare' '\n \tcd bare.git\n '\n \n-test_expect_success 'hard reset is not allowed in bare' '\n+test_expect_success '\"hard\" reset is not allowed in bare' '\n \ttest_must_fail git reset --hard HEAD^\n '\n \n-test_expect_success 'merge reset is not allowed in bare' '\n+test_expect_success '\"merge\" reset is not allowed in bare' '\n \ttest_must_fail git reset --merge HEAD^\n '\n \n-test_expect_success 'mixed reset is not allowed in bare' '\n+test_expect_success '\"keep\" reset is not allowed in bare' '\n+\ttest_must_fail git reset --keep HEAD^\n+'\n+\n+test_expect_success '\"mixed\" reset is not allowed in bare' '\n \ttest_must_fail git reset --mixed HEAD^\n '\n \n-test_expect_success 'soft reset is allowed in bare' '\n+test_expect_success '\"soft\" reset is allowed in bare' '\n \tgit reset --soft HEAD^ &&\n \ttest \"`git show --pretty=format:%s | head -n 1`\" = \"one\"\n '\n-- \n1.6.6.271.gc8799\n"},{"id":"132065","messageId":"20100119042602.4510.53789.chriscool@tuxfamily.org","threadId":"22279","inReplyTo":"20100119042404.4510.48855.chriscool@tuxfamily.org","subject":"[PATCH v2 5/5] reset: disallow using --keep when there are unmerged entries","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2010-01-19T04:26:01Z","receivedAt":"2010-01-19T04:26:01Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"The use case for --keep option is to remove previous commits unrelated\nto the current changes in the working tree. So in this use case we are\nnot supposed to have unmerged entries. This is why it seems safer to\njust disallow using --keep when there are unmerged entries.\n\nAnd this patch changes the error message when --keep was disallowed and\nthere were some unmerged entries from:\n\n    error: Entry 'file1' would be overwritten by merge. Cannot merge.\n    fatal: Could not reset index file to revision 'HEAD^'.\n\nto:\n\n    fatal: Cannot do a keep reset in the middle of a merge.\n\nwhich is nicer.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-reset.txt |    5 +++--\n builtin-reset.c             |   17 +++++++++++++----\n t/t7110-reset-merge.sh      |   23 +++++++++--------------\n t/t7111-reset-table.sh      |    2 +-\n 4 files changed, 26 insertions(+), 21 deletions(-)\n\ndiff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt\nindex 90239f5..d2f6880 100644\n--- a/Documentation/git-reset.txt\n+++ b/Documentation/git-reset.txt\n@@ -155,7 +155,8 @@ If there could be conflicts between the changes in the commit we want\n to remove and the changes in the working tree we want to keep, the\n reset is disallowed. That's why it is disallowed if there are both\n changes between the working tree and HEAD, and between HEAD and the\n-target.\n+target. To be safe, it is also disallowed when there are unmerged\n+entries.\n \n The following tables show what happens when there are unmerged\n entries:\n@@ -174,7 +175,7 @@ entries:\n \t\t\t\t--mixed  X       A     A\n \t\t\t\t--hard   A       A     A\n \t\t\t\t--merge  A       A     A\n-\t\t\t\t--keep   X       A     A\n+\t\t\t\t--keep  (disallowed)\n \n X means any state and U means an unmerged index.\n \ndiff --git a/builtin-reset.c b/builtin-reset.c\nindex 52584af..441ac1b 100644\n--- a/builtin-reset.c\n+++ b/builtin-reset.c\n@@ -224,6 +224,14 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)\n \t\twarning(\"Reflog action message too long: %.*s...\", 50, buf);\n }\n \n+static void die_if_unmerged_cache(int reset_type)\n+{\n+\tif (is_merge() || read_cache() < 0 || unmerged_cache())\n+\t\tdie(\"Cannot do a %s reset in the middle of a merge.\",\n+\t\t    reset_type_names[reset_type]);\n+\n+}\n+\n int cmd_reset(int argc, const char **argv, const char *prefix)\n {\n \tint i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;\n@@ -329,10 +337,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t/* Soft reset does not touch the index file nor the working tree\n \t * at all, but requires them in a good order.  Other resets reset\n \t * the index file to the tree object we are switching to. */\n-\tif (reset_type == SOFT) {\n-\t\tif (is_merge() || read_cache() < 0 || unmerged_cache())\n-\t\t\tdie(\"Cannot do a soft reset in the middle of a merge.\");\n-\t} else {\n+\tif (reset_type == SOFT)\n+\t\tdie_if_unmerged_cache(reset_type);\n+\telse {\n+\t\tif (reset_type == KEEP)\n+\t\t\tdie_if_unmerged_cache(reset_type);\n \t\tint err = reset_index_file(sha1, reset_type, quiet);\n \t\tif (reset_type == KEEP)\n \t\t\terr = err || reset_index_file(sha1, MIXED, quiet);\ndiff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh\nindex 1a9c1c7..70cdd8e 100755\n--- a/t/t7110-reset-merge.sh\n+++ b/t/t7110-reset-merge.sh\n@@ -237,7 +237,7 @@ test_expect_success '\"reset --keep HEAD^\" fails with pending merge' '\n     git reset --hard third &&\n     test_must_fail git merge branch1 &&\n     test_must_fail git reset --keep HEAD^ 2>err.log &&\n-    grep file1 err.log | grep \"overwritten by merge\"\n+    grep \"middle of a merge\" err.log\n '\n \n # The next test will test the following:\n@@ -258,18 +258,15 @@ test_expect_success '\"reset --merge HEAD\" is ok with pending merge' '\n #\n #           working index HEAD target         working index HEAD\n #           ----------------------------------------------------\n-# file1:     X       U     B    B     --keep  X       B     B\n-test_expect_success '\"reset --keep HEAD\" is ok with pending merge' '\n+# file1:     X       U     B    B     --keep   (disallowed)\n+test_expect_success '\"reset --keep HEAD\" fails with pending merge' '\n     git reset --hard third &&\n     test_must_fail git merge branch1 &&\n-    cat file1 >orig_file1 &&\n-    git reset --keep HEAD &&\n-    test \"$(git rev-parse HEAD)\" = \"$(git rev-parse third)\" &&\n-    test -z \"$(git diff --cached)\" &&\n-    test_cmp file1 orig_file1\n+    test_must_fail git reset --keep HEAD 2>err.log &&\n+    grep \"middle of a merge\" err.log\n '\n \n-test_expect_success '--merge with added/deleted' '\n+test_expect_success '--merge is ok with added/deleted merge' '\n     git reset --hard third &&\n     rm -f file2 &&\n     test_must_fail git merge branch3 &&\n@@ -283,7 +280,7 @@ test_expect_success '--merge with added/deleted' '\n     git diff --exit-code --cached\n '\n \n-test_expect_success '--keep with added/deleted' '\n+test_expect_success '--keep fails with added/deleted merge' '\n     git reset --hard third &&\n     rm -f file2 &&\n     test_must_fail git merge branch3 &&\n@@ -291,10 +288,8 @@ test_expect_success '--keep with added/deleted' '\n     test -f file3 &&\n     git diff --exit-code file3 &&\n     git diff --exit-code branch3 file3 &&\n-    git reset --keep HEAD &&\n-    test -f file3 &&\n-    ! test -f file2 &&\n-    git diff --exit-code --cached\n+    test_must_fail git reset --keep HEAD 2>err.log &&\n+    grep \"middle of a merge\" err.log\n '\n \n test_done\ndiff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh\nindex 2ebda97..ce421ad 100755\n--- a/t/t7111-reset-table.sh\n+++ b/t/t7111-reset-table.sh\n@@ -115,7 +115,7 @@ X U B B soft   XXXXX\n X U B B mixed  X B B\n X U B B hard   B B B\n X U B B merge  B B B\n-X U B B keep   X B B\n+X U B B keep   XXXXX\n EOF\n \n test_done\n-- \n1.6.6.271.gc8799\n"},{"id":"132072","messageId":"7vockqbq44.fsf@alter.siamese.dyndns.org","threadId":"22279","inReplyTo":"20100119042602.4510.24100.chriscool@tuxfamily.org","subject":"Re: [PATCH v2 1/5] reset: add option \"--keep\" to \"git reset\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-01-19T05:50:03Z","receivedAt":"2010-01-19T05:50:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <chriscool@tuxfamily.org> writes:\n\n> The purpose of this new option is to discard some of the\n> last commits but to keep current changes in the work tree.\n>\n> The use case is when you work on something and commit\n> that work. And then you work on something else that touches\n> other files, but you don't commit it yet. Then you realize\n> that what you commited when you worked on the first thing\n> is not good or belongs to another branch.\n>\n> So you want to get rid of the previous commits (at least in\n> the current branch) but you want to make sure that you keep\n> the changes you have in the work tree.\n\nYou did this:\n\n\tgit checkout master\n\twork; git commit ; work; git commit ; work; git commit\n\tedit ; git add ; ... (but not commit)\n\nand noticed the three commits should not be on master but on a new branch.\n\nI think we currently teach users to do something like this:\n\n\tgit stash\n        git branch topic\n        git reset --hard HEAD~3\n        git stash pop\n\nInstead you want to do this:\n\n\tgit branch topic\n        git reset --keep HEAD~3\n\nSurely you halved the number of command involved, but is this really an\nimprovement?  At least I can visualize (and more importantly, explain to\nnew users) how the \"stash, flip and unstash\" works, why it is safe, and\nhow to recover when \"pop\" stops in conflicts, but I have no confidence in\nexplaining what \"reset --keep\" does and how to recover when it refuses to\nwork to new users.\n\nAnother way to accomplish the same thing might be:\n\n\tgit branch -m topic\n        git checkout -b master HEAD~3\n\nand with the same number of commands, conceptually it may be easier to\nunderstand than \"reset --keep\".  What you committed so far belongs to\nanother branch \"topic\", so you name the current history that way, and then\nyou switch branches with \"checkout\" that keeps your local modifications.\nIt also opens the possibility of retrying with \"-m\" after checkout refuses\nto acti, to take the same mix-up risk CVS/SVN users have, if you are very\nconfident that your local change conflicts only minimally with the change\nmade on the topic and you can resolve them.\n\nOf course, when you are not interested in keeping the topmost commits at\nall, you either\n\n\tgit stash ; git reset --hard HEAD~3 ; git stash pop\n\nor\n\n\tgit reset --keep HEAD~3\n\nbut even in this case, I think \"stash, flip and unstash\" is easier to\nexplain, especially when teaching what to do if things go wrong.\n\nI dunno.  Is this really an useful addition that helps real-world\nworkflows and is worth a five patch series, or is this just \"because we\ncan\" patch?\n"},{"id":"132450","messageId":"201001230518.29049.chriscool@tuxfamily.org","threadId":"22279","inReplyTo":"7vockqbq44.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2 1/5] reset: add option \"--keep\" to \"git reset\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2010-01-23T04:18:28Z","receivedAt":"2010-01-23T04:18:28Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On mardi 19 janvier 2010, Junio C Hamano wrote:\n> Christian Couder <chriscool@tuxfamily.org> writes:\n> > The purpose of this new option is to discard some of the\n> > last commits but to keep current changes in the work tree.\n> >\n> > The use case is when you work on something and commit\n> > that work. And then you work on something else that touches\n> > other files, but you don't commit it yet. Then you realize\n> > that what you commited when you worked on the first thing\n> > is not good or belongs to another branch.\n> >\n> > So you want to get rid of the previous commits (at least in\n> > the current branch) but you want to make sure that you keep\n> > the changes you have in the work tree.\n>\n> You did this:\n>\n> \tgit checkout master\n> \twork; git commit ; work; git commit ; work; git commit\n> \tedit ; git add ; ... (but not commit)\n>\n> and noticed the three commits should not be on master but on a new\n> branch.\n>\n> I think we currently teach users to do something like this:\n>\n> \tgit stash\n>         git branch topic\n>         git reset --hard HEAD~3\n>         git stash pop\n>\n> Instead you want to do this:\n>\n> \tgit branch topic\n>         git reset --keep HEAD~3\n>\n> Surely you halved the number of command involved, but is this really an\n> improvement?  At least I can visualize (and more importantly, explain to\n> new users) how the \"stash, flip and unstash\" works, why it is safe, and\n> how to recover when \"pop\" stops in conflicts, but I have no confidence in\n> explaining what \"reset --keep\" does and how to recover when it refuses to\n> work to new users.\n\nOf course new users should be told about \"git stash\" before being told \nabout \"git reset --keep\" and \"git reset --merge\". These 2 options are \nmostly for advanced users who want shortcuts and are ready to learn a few \nmore options to speed up some of their common tasks.\n\nCommit 9e8eceab (\"Add 'merge' mode to 'git reset'\", 2008-12-01), ended with:\n\n-------------------\n    Yes, yes, with a dirty tree you could always do\n\n        git stash\n        git reset --hard\n        git stash apply\n\n    instead, but isn't \"git reset --merge\" a nice way to handle one \nparticular simple case?\n-------------------\n\nand I claim that the same is true for \"--keep\". It is just a nice way to \nhandle one particular simple case.\n\n> Another way to accomplish the same thing might be:\n>\n> \tgit branch -m topic\n>         git checkout -b master HEAD~3\n>\n> and with the same number of commands, conceptually it may be easier to\n> understand than \"reset --keep\".  What you committed so far belongs to\n> another branch \"topic\", so you name the current history that way, and\n> then you switch branches with \"checkout\" that keeps your local\n> modifications. It also opens the possibility of retrying with \"-m\" after\n> checkout refuses to acti, to take the same mix-up risk CVS/SVN users\n> have, if you are very confident that your local change conflicts only\n> minimally with the change made on the topic and you can resolve them.\n\nSorry but I don't find that easier to understand. On the contrary I find \nawkward to have to rename the current branch first.\n\nMy first reaction when I realize that my current work belongs to another \nbranch is just to create another branch with a good name, and then I try to \nfind a way to make the new branch clean. It would be strange and perhaps a \nlittle bit unsafe, at least for me, to have to rename the current branch \nand then recreate it with some good content.\n\n> Of course, when you are not interested in keeping the topmost commits at\n> all, you either\n>\n> \tgit stash ; git reset --hard HEAD~3 ; git stash pop\n>\n> or\n>\n> \tgit reset --keep HEAD~3\n>\n> but even in this case, I think \"stash, flip and unstash\" is easier to\n> explain, especially when teaching what to do if things go wrong.\n>\n> I dunno.  Is this really an useful addition that helps real-world\n> workflows and is worth a five patch series, or is this just \"because we\n> can\" patch?\n\nI know that I will use the new option. At least I will use it much more \nthan --merge that I never used at $dayjob, probably because I don't merge a \nlot of stuff.\n\nBest regards,\nChristian.\n"}]}