зеркало из https://github.com/microsoft/git.git
Merge branch 'cc/reset-keep'
* cc/reset-keep: Documentation: improve description of "git reset --keep" reset: disallow using --keep when there are unmerged entries reset: disallow "reset --keep" outside a work tree Documentation: reset: describe new "--keep" option reset: add test cases for "--keep" option reset: add option "--keep" to "git reset"
This commit is contained in:
Коммит
49559cad6c
|
@ -8,7 +8,7 @@ git-reset - Reset current HEAD to the specified state
|
|||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
|
||||
'git reset' [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]
|
||||
'git reset' [-q] [<commit>] [--] <paths>...
|
||||
'git reset' --patch [<commit>] [--] [<paths>...]
|
||||
|
||||
|
@ -52,6 +52,14 @@ OPTIONS
|
|||
and updates the files that are different between the named commit
|
||||
and the current commit in the working tree.
|
||||
|
||||
--keep::
|
||||
Reset the index to the given commit, keeping local changes in
|
||||
the working tree since the current commit, while updating
|
||||
working tree files without local changes to what appears in
|
||||
the given commit. If a file that is different between the
|
||||
current commit and the given commit has local changes, reset
|
||||
is aborted.
|
||||
|
||||
-p::
|
||||
--patch::
|
||||
Interactively select hunks in the difference between the index
|
||||
|
@ -93,6 +101,7 @@ in the index and in state D in HEAD.
|
|||
--mixed A D D
|
||||
--hard D D D
|
||||
--merge (disallowed)
|
||||
--keep (disallowed)
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
|
@ -100,6 +109,7 @@ in the index and in state D in HEAD.
|
|||
--mixed A C C
|
||||
--hard C C C
|
||||
--merge (disallowed)
|
||||
--keep A C C
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
|
@ -107,6 +117,7 @@ in the index and in state D in HEAD.
|
|||
--mixed B D D
|
||||
--hard D D D
|
||||
--merge D D D
|
||||
--keep (disallowed)
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
|
@ -114,6 +125,7 @@ in the index and in state D in HEAD.
|
|||
--mixed B C C
|
||||
--hard C C C
|
||||
--merge C C C
|
||||
--keep B C C
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
|
@ -121,6 +133,7 @@ in the index and in state D in HEAD.
|
|||
--mixed B D D
|
||||
--hard D D D
|
||||
--merge (disallowed)
|
||||
--keep (disallowed)
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
|
@ -128,6 +141,7 @@ in the index and in state D in HEAD.
|
|||
--mixed B C C
|
||||
--hard C C C
|
||||
--merge B C C
|
||||
--keep B C C
|
||||
|
||||
"reset --merge" is meant to be used when resetting out of a conflicted
|
||||
merge. Any mergy operation guarantees that the work tree file that is
|
||||
|
@ -138,6 +152,15 @@ between the index and the work tree, then it means that we are not
|
|||
resetting out from a state that a mergy operation left after failing
|
||||
with a conflict. That is why we disallow --merge option in this case.
|
||||
|
||||
"reset --keep" is meant to be used when removing some of the last
|
||||
commits in the current branch while keeping changes in the working
|
||||
tree. If there could be conflicts between the changes in the commit we
|
||||
want to remove and the changes in the working tree we want to keep,
|
||||
the reset is disallowed. That's why it is disallowed if there are both
|
||||
changes between the working tree and HEAD, and between HEAD and the
|
||||
target. To be safe, it is also disallowed when there are unmerged
|
||||
entries.
|
||||
|
||||
The following tables show what happens when there are unmerged
|
||||
entries:
|
||||
|
||||
|
@ -147,6 +170,7 @@ entries:
|
|||
--mixed X B B
|
||||
--hard B B B
|
||||
--merge B B B
|
||||
--keep (disallowed)
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
|
@ -154,6 +178,7 @@ entries:
|
|||
--mixed X A A
|
||||
--hard A A A
|
||||
--merge A A A
|
||||
--keep (disallowed)
|
||||
|
||||
X means any state and U means an unmerged index.
|
||||
|
||||
|
@ -325,6 +350,32 @@ $ git add frotz.c <3>
|
|||
<2> This commits all other changes in the index.
|
||||
<3> Adds the file to the index again.
|
||||
|
||||
Keep changes in working tree while discarding some previous commits::
|
||||
+
|
||||
Suppose you are working on something and you commit it, and then you
|
||||
continue working a bit more, but now you think that what you have in
|
||||
your working tree should be in another branch that has nothing to do
|
||||
with what you commited previously. You can start a new branch and
|
||||
reset it while keeping the changes in your work tree.
|
||||
+
|
||||
------------
|
||||
$ git tag start
|
||||
$ git checkout -b branch1
|
||||
$ edit
|
||||
$ git commit ... <1>
|
||||
$ edit
|
||||
$ git checkout -b branch2 <2>
|
||||
$ git reset --keep start <3>
|
||||
------------
|
||||
+
|
||||
<1> This commits your first edits in branch1.
|
||||
<2> In the ideal world, you could have realized that the earlier
|
||||
commit did not belong to the new topic when you created and switched
|
||||
to branch2 (i.e. "git checkout -b branch2 start"), but nobody is
|
||||
perfect.
|
||||
<3> But you can use "reset --keep" to remove the unwanted commit after
|
||||
you switched to "branch2".
|
||||
|
||||
Author
|
||||
------
|
||||
Written by Junio C Hamano <gitster@pobox.com> and Linus Torvalds <torvalds@osdl.org>
|
||||
|
|
|
@ -22,13 +22,15 @@
|
|||
#include "cache-tree.h"
|
||||
|
||||
static const char * const git_reset_usage[] = {
|
||||
"git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
|
||||
"git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]",
|
||||
"git reset [--mixed] <commit> [--] <paths>...",
|
||||
NULL
|
||||
};
|
||||
|
||||
enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
|
||||
static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
|
||||
enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE };
|
||||
static const char *reset_type_names[] = {
|
||||
"mixed", "soft", "hard", "merge", "keep", NULL
|
||||
};
|
||||
|
||||
static char *args_to_str(const char **argv)
|
||||
{
|
||||
|
@ -71,6 +73,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
|
|||
if (!quiet)
|
||||
opts.verbose_update = 1;
|
||||
switch (reset_type) {
|
||||
case KEEP:
|
||||
case MERGE:
|
||||
opts.update = 1;
|
||||
break;
|
||||
|
@ -85,6 +88,16 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
|
|||
|
||||
read_cache_unmerged();
|
||||
|
||||
if (reset_type == KEEP) {
|
||||
unsigned char head_sha1[20];
|
||||
if (get_sha1("HEAD", head_sha1))
|
||||
return error("You do not have a valid HEAD.");
|
||||
if (!fill_tree_descriptor(desc, head_sha1))
|
||||
return error("Failed to find tree of HEAD.");
|
||||
nr++;
|
||||
opts.fn = twoway_merge;
|
||||
}
|
||||
|
||||
if (!fill_tree_descriptor(desc + nr - 1, sha1))
|
||||
return error("Failed to find tree of %s.", sha1_to_hex(sha1));
|
||||
if (unpack_trees(nr, desc, &opts))
|
||||
|
@ -211,6 +224,14 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
|
|||
warning("Reflog action message too long: %.*s...", 50, buf);
|
||||
}
|
||||
|
||||
static void die_if_unmerged_cache(int reset_type)
|
||||
{
|
||||
if (is_merge() || read_cache() < 0 || unmerged_cache())
|
||||
die("Cannot do a %s reset in the middle of a merge.",
|
||||
reset_type_names[reset_type]);
|
||||
|
||||
}
|
||||
|
||||
int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
|
||||
|
@ -229,6 +250,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
|||
"reset HEAD, index and working tree", HARD),
|
||||
OPT_SET_INT(0, "merge", &reset_type,
|
||||
"reset HEAD, index and working tree", MERGE),
|
||||
OPT_SET_INT(0, "keep", &reset_type,
|
||||
"reset HEAD but keep local changes", KEEP),
|
||||
OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
|
||||
OPT_END()
|
||||
};
|
||||
|
@ -304,7 +327,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
|||
if (reset_type == NONE)
|
||||
reset_type = MIXED; /* by default */
|
||||
|
||||
if (reset_type == HARD || reset_type == MERGE)
|
||||
if (reset_type != SOFT && reset_type != MIXED)
|
||||
setup_work_tree();
|
||||
|
||||
if (reset_type == MIXED && is_bare_repository())
|
||||
|
@ -314,12 +337,18 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
|||
/* Soft reset does not touch the index file nor the working tree
|
||||
* at all, but requires them in a good order. Other resets reset
|
||||
* the index file to the tree object we are switching to. */
|
||||
if (reset_type == SOFT) {
|
||||
if (is_merge() || read_cache() < 0 || unmerged_cache())
|
||||
die("Cannot do a soft reset in the middle of a merge.");
|
||||
if (reset_type == SOFT)
|
||||
die_if_unmerged_cache(reset_type);
|
||||
else {
|
||||
int err;
|
||||
if (reset_type == KEEP)
|
||||
die_if_unmerged_cache(reset_type);
|
||||
err = reset_index_file(sha1, reset_type, quiet);
|
||||
if (reset_type == KEEP)
|
||||
err = err || reset_index_file(sha1, MIXED, quiet);
|
||||
if (err)
|
||||
die("Could not reset index file to revision '%s'.", rev);
|
||||
}
|
||||
else if (reset_index_file(sha1, reset_type, quiet))
|
||||
die("Could not reset index file to revision '%s'.", rev);
|
||||
|
||||
/* Any resets update HEAD to the head being switched to,
|
||||
* saving the previous head in ORIG_HEAD before. */
|
||||
|
|
|
@ -11,21 +11,26 @@ test_expect_success 'setup non-bare' '
|
|||
git commit -a -m two
|
||||
'
|
||||
|
||||
test_expect_success 'hard reset requires a worktree' '
|
||||
test_expect_success '"hard" reset requires a worktree' '
|
||||
(cd .git &&
|
||||
test_must_fail git reset --hard)
|
||||
'
|
||||
|
||||
test_expect_success 'merge reset requires a worktree' '
|
||||
test_expect_success '"merge" reset requires a worktree' '
|
||||
(cd .git &&
|
||||
test_must_fail git reset --merge)
|
||||
'
|
||||
|
||||
test_expect_success 'mixed reset is ok' '
|
||||
test_expect_success '"keep" reset requires a worktree' '
|
||||
(cd .git &&
|
||||
test_must_fail git reset --keep)
|
||||
'
|
||||
|
||||
test_expect_success '"mixed" reset is ok' '
|
||||
(cd .git && git reset)
|
||||
'
|
||||
|
||||
test_expect_success 'soft reset is ok' '
|
||||
test_expect_success '"soft" reset is ok' '
|
||||
(cd .git && git reset --soft)
|
||||
'
|
||||
|
||||
|
@ -40,19 +45,23 @@ test_expect_success 'setup bare' '
|
|||
cd bare.git
|
||||
'
|
||||
|
||||
test_expect_success 'hard reset is not allowed in bare' '
|
||||
test_expect_success '"hard" reset is not allowed in bare' '
|
||||
test_must_fail git reset --hard HEAD^
|
||||
'
|
||||
|
||||
test_expect_success 'merge reset is not allowed in bare' '
|
||||
test_expect_success '"merge" reset is not allowed in bare' '
|
||||
test_must_fail git reset --merge HEAD^
|
||||
'
|
||||
|
||||
test_expect_success 'mixed reset is not allowed in bare' '
|
||||
test_expect_success '"keep" reset is not allowed in bare' '
|
||||
test_must_fail git reset --keep HEAD^
|
||||
'
|
||||
|
||||
test_expect_success '"mixed" reset is not allowed in bare' '
|
||||
test_must_fail git reset --mixed HEAD^
|
||||
'
|
||||
|
||||
test_expect_success 'soft reset is allowed in bare' '
|
||||
test_expect_success '"soft" reset is allowed in bare' '
|
||||
git reset --soft HEAD^ &&
|
||||
test "`git show --pretty=format:%s | head -n 1`" = "one"
|
||||
'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Copyright (c) 2009 Christian Couder
|
||||
#
|
||||
|
||||
test_description='Tests for "git reset --merge"'
|
||||
test_description='Tests for "git reset" with "--merge" and "--keep" options'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
|
@ -43,6 +43,30 @@ test_expect_success 'reset --merge is ok when switching back' '
|
|||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: C C C D --keep D D D
|
||||
# file2: C D D D --keep C D D
|
||||
test_expect_success 'reset --keep is ok with changes in file it does not touch' '
|
||||
git reset --hard second &&
|
||||
cat file1 >file2 &&
|
||||
git reset --keep HEAD^ &&
|
||||
! grep 4 file1 &&
|
||||
grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
test_expect_success 'reset --keep is ok when switching back' '
|
||||
git reset --keep second &&
|
||||
grep 4 file1 &&
|
||||
grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
|
@ -74,6 +98,18 @@ test_expect_success 'reset --merge is ok again when switching back (1)' '
|
|||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: B B C D --keep (disallowed)
|
||||
test_expect_success 'reset --keep fails with changes in index in files it touches' '
|
||||
git reset --hard second &&
|
||||
echo "line 5" >> file1 &&
|
||||
git add file1 &&
|
||||
test_must_fail git reset --keep HEAD^
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
|
@ -100,6 +136,30 @@ test_expect_success 'reset --merge is ok again when switching back (2)' '
|
|||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: C C C D --keep D D D
|
||||
# file2: C C D D --keep C D D
|
||||
test_expect_success 'reset --keep keeps changes it does not touch' '
|
||||
git reset --hard second &&
|
||||
echo "line 4" >> file2 &&
|
||||
git add file2 &&
|
||||
git reset --keep HEAD^ &&
|
||||
grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
test_expect_success 'reset --keep keeps changes when switching back' '
|
||||
git reset --keep second &&
|
||||
grep 4 file2 &&
|
||||
grep 4 file1 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
|
@ -116,6 +176,22 @@ test_expect_success 'reset --merge fails with changes in file it touches' '
|
|||
grep file1 err.log | grep "not uptodate"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: A B B C --keep (disallowed)
|
||||
test_expect_success 'reset --keep fails with changes in file it touches' '
|
||||
git reset --hard second &&
|
||||
echo "line 5" >> file1 &&
|
||||
test_tick &&
|
||||
git commit -m "add line 5" file1 &&
|
||||
sed -e "s/line 1/changed line 1/" <file1 >file3 &&
|
||||
mv file3 file1 &&
|
||||
test_must_fail git reset --keep HEAD^ 2>err.log &&
|
||||
grep file1 err.log | grep "not uptodate"
|
||||
'
|
||||
|
||||
test_expect_success 'setup 3 different branches' '
|
||||
git reset --hard second &&
|
||||
git branch branch1 &&
|
||||
|
@ -152,6 +228,18 @@ test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
|
|||
test -z "$(git diff)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: X U B C --keep (disallowed)
|
||||
test_expect_success '"reset --keep HEAD^" fails with pending merge' '
|
||||
git reset --hard third &&
|
||||
test_must_fail git merge branch1 &&
|
||||
test_must_fail git reset --keep HEAD^ 2>err.log &&
|
||||
grep "middle of a merge" err.log
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
|
@ -166,7 +254,19 @@ test_expect_success '"reset --merge HEAD" is ok with pending merge' '
|
|||
test -z "$(git diff)"
|
||||
'
|
||||
|
||||
test_expect_success '--merge with added/deleted' '
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: X U B B --keep (disallowed)
|
||||
test_expect_success '"reset --keep HEAD" fails with pending merge' '
|
||||
git reset --hard third &&
|
||||
test_must_fail git merge branch1 &&
|
||||
test_must_fail git reset --keep HEAD 2>err.log &&
|
||||
grep "middle of a merge" err.log
|
||||
'
|
||||
|
||||
test_expect_success '--merge is ok with added/deleted merge' '
|
||||
git reset --hard third &&
|
||||
rm -f file2 &&
|
||||
test_must_fail git merge branch3 &&
|
||||
|
@ -180,4 +280,16 @@ test_expect_success '--merge with added/deleted' '
|
|||
git diff --exit-code --cached
|
||||
'
|
||||
|
||||
test_expect_success '--keep fails with added/deleted merge' '
|
||||
git reset --hard third &&
|
||||
rm -f file2 &&
|
||||
test_must_fail git merge branch3 &&
|
||||
! test -f file2 &&
|
||||
test -f file3 &&
|
||||
git diff --exit-code file3 &&
|
||||
git diff --exit-code branch3 file3 &&
|
||||
test_must_fail git reset --keep HEAD 2>err.log &&
|
||||
grep "middle of a merge" err.log
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -44,26 +44,32 @@ A B C D soft A B D
|
|||
A B C D mixed A D D
|
||||
A B C D hard D D D
|
||||
A B C D merge XXXXX
|
||||
A B C D keep XXXXX
|
||||
A B C C soft A B C
|
||||
A B C C mixed A C C
|
||||
A B C C hard C C C
|
||||
A B C C merge XXXXX
|
||||
A B C C keep A C C
|
||||
B B C D soft B B D
|
||||
B B C D mixed B D D
|
||||
B B C D hard D D D
|
||||
B B C D merge D D D
|
||||
B B C D keep XXXXX
|
||||
B B C C soft B B C
|
||||
B B C C mixed B C C
|
||||
B B C C hard C C C
|
||||
B B C C merge C C C
|
||||
B B C C keep B C C
|
||||
B C C D soft B C D
|
||||
B C C D mixed B D D
|
||||
B C C D hard D D D
|
||||
B C C D merge XXXXX
|
||||
B C C D keep XXXXX
|
||||
B C C C soft B C C
|
||||
B C C C mixed B C C
|
||||
B C C C hard C C C
|
||||
B C C C merge B C C
|
||||
B C C C keep B C C
|
||||
EOF
|
||||
|
||||
test_expect_success 'setting up branches to test with unmerged entries' '
|
||||
|
@ -104,10 +110,12 @@ X U B C soft XXXXX
|
|||
X U B C mixed X C C
|
||||
X U B C hard C C C
|
||||
X U B C merge C C C
|
||||
X U B C keep XXXXX
|
||||
X U B B soft XXXXX
|
||||
X U B B mixed X B B
|
||||
X U B B hard B B B
|
||||
X U B B merge B B B
|
||||
X U B B keep XXXXX
|
||||
EOF
|
||||
|
||||
test_done
|
||||
|
|
Загрузка…
Ссылка в новой задаче