Skip to content

Commit

Permalink
Merge branch 'cc/reset-keep'
Browse files Browse the repository at this point in the history
* 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"
  • Loading branch information
Junio C Hamano committed Mar 20, 2010
2 parents 2e5b98d + eaf436c commit 49559ca
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 20 deletions.
53 changes: 52 additions & 1 deletion Documentation/git-reset.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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>...]

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -93,41 +101,47 @@ 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
----------------------------------------------------
A B C C --soft A B C
--mixed A C C
--hard C C C
--merge (disallowed)
--keep A C C

working index HEAD target working index HEAD
----------------------------------------------------
B B C D --soft B B D
--mixed B D D
--hard D D D
--merge D D D
--keep (disallowed)

working index HEAD target working index HEAD
----------------------------------------------------
B B C C --soft B B C
--mixed B C C
--hard C C C
--merge C C C
--keep B C C

working index HEAD target working index HEAD
----------------------------------------------------
B C C D --soft B C D
--mixed B D D
--hard D D D
--merge (disallowed)
--keep (disallowed)

working index HEAD target working index HEAD
----------------------------------------------------
B C C C --soft B C C
--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
Expand All @@ -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:

Expand All @@ -147,13 +170,15 @@ entries:
--mixed X B B
--hard B B B
--merge B B B
--keep (disallowed)

working index HEAD target working index HEAD
----------------------------------------------------
X U A A --soft (disallowed)
--mixed X A A
--hard A A A
--merge A A A
--keep (disallowed)

X means any state and U means an unmerged index.

Expand Down Expand Up @@ -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>
Expand Down
47 changes: 38 additions & 9 deletions builtin/reset.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand All @@ -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))
Expand Down Expand Up @@ -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;
Expand All @@ -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()
};
Expand Down Expand Up @@ -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())
Expand All @@ -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. */
Expand Down
25 changes: 17 additions & 8 deletions t/t7103-reset-bare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
'

Expand All @@ -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"
'
Expand Down
Loading

0 comments on commit 49559ca

Please sign in to comment.