Merge branch 'cc/reset-more'
* cc/reset-more: t7111: check that reset options work as described in the tables Documentation: reset: add some missing tables Fix bit assignment for CE_CONFLICTED "reset --merge": fix unmerged case reset: use "unpack_trees()" directly instead of "git read-tree" reset: add a few tests for "git reset --merge" Documentation: reset: add some tables to describe the different options reset: improve mixed reset error message when in a bare repo
This commit is contained in:
commit
dc96c5ee70
@ -68,6 +68,95 @@ linkgit:git-add[1]).
|
||||
<commit>::
|
||||
Commit to make the current HEAD. If not given defaults to HEAD.
|
||||
|
||||
DISCUSSION
|
||||
----------
|
||||
|
||||
The tables below show what happens when running:
|
||||
|
||||
----------
|
||||
git reset --option target
|
||||
----------
|
||||
|
||||
to reset the HEAD to another commit (`target`) with the different
|
||||
reset options depending on the state of the files.
|
||||
|
||||
In these tables, A, B, C and D are some different states of a
|
||||
file. For example, the first line of the first table means that if a
|
||||
file is in state A in the working tree, in state B in the index, in
|
||||
state C in HEAD and in state D in the target, then "git reset --soft
|
||||
target" will put the file in state A in the working tree, in state B
|
||||
in the index and in state D in HEAD.
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
A B C D --soft A B D
|
||||
--mixed A D D
|
||||
--hard D D D
|
||||
--merge (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)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
"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
|
||||
involved in the merge does not have local change wrt the index before
|
||||
it starts, and that it writes the result out to the work tree. So if
|
||||
we see some difference between the index and the target and also
|
||||
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.
|
||||
|
||||
The following tables show what happens when there are unmerged
|
||||
entries:
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
X U A B --soft (disallowed)
|
||||
--mixed X B B
|
||||
--hard B B B
|
||||
--merge B B B
|
||||
|
||||
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
|
||||
|
||||
X means any state and U means an unmerged index.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include "tree.h"
|
||||
#include "branch.h"
|
||||
#include "parse-options.h"
|
||||
#include "unpack-trees.h"
|
||||
#include "cache-tree.h"
|
||||
|
||||
static const char * const git_reset_usage[] = {
|
||||
"git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
|
||||
@ -54,27 +56,44 @@ static inline int is_merge(void)
|
||||
|
||||
static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
|
||||
{
|
||||
int i = 0;
|
||||
const char *args[6];
|
||||
int nr = 1;
|
||||
int newfd;
|
||||
struct tree_desc desc[2];
|
||||
struct unpack_trees_options opts;
|
||||
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
|
||||
|
||||
args[i++] = "read-tree";
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.head_idx = 1;
|
||||
opts.src_index = &the_index;
|
||||
opts.dst_index = &the_index;
|
||||
opts.fn = oneway_merge;
|
||||
opts.merge = 1;
|
||||
if (!quiet)
|
||||
args[i++] = "-v";
|
||||
opts.verbose_update = 1;
|
||||
switch (reset_type) {
|
||||
case MERGE:
|
||||
args[i++] = "-u";
|
||||
args[i++] = "-m";
|
||||
opts.update = 1;
|
||||
break;
|
||||
case HARD:
|
||||
args[i++] = "-u";
|
||||
opts.update = 1;
|
||||
/* fallthrough */
|
||||
default:
|
||||
args[i++] = "--reset";
|
||||
opts.reset = 1;
|
||||
}
|
||||
args[i++] = sha1_to_hex(sha1);
|
||||
args[i] = NULL;
|
||||
|
||||
return run_command_v_opt(args, RUN_GIT_CMD);
|
||||
newfd = hold_locked_index(lock, 1);
|
||||
|
||||
read_cache_unmerged();
|
||||
|
||||
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))
|
||||
return -1;
|
||||
if (write_cache(newfd, active_cache, active_nr) ||
|
||||
commit_locked_index(lock))
|
||||
return error("Could not write new index file.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_new_head_line(struct commit *commit)
|
||||
@ -288,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||
if (reset_type == HARD || reset_type == MERGE)
|
||||
setup_work_tree();
|
||||
|
||||
if (reset_type == MIXED && is_bare_repository())
|
||||
die("%s reset is not allowed in a bare repository",
|
||||
reset_type_names[reset_type]);
|
||||
|
||||
/* 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. */
|
||||
|
1
cache.h
1
cache.h
@ -177,6 +177,7 @@ struct cache_entry {
|
||||
|
||||
#define CE_HASHED (0x100000)
|
||||
#define CE_UNHASHED (0x200000)
|
||||
#define CE_CONFLICTED (0x800000)
|
||||
|
||||
/* Only remove in work directory, not index */
|
||||
#define CE_WT_REMOVE (0x400000)
|
||||
|
@ -1617,9 +1617,8 @@ int read_index_unmerged(struct index_state *istate)
|
||||
len = strlen(ce->name);
|
||||
size = cache_entry_size(len);
|
||||
new_ce = xcalloc(1, size);
|
||||
hashcpy(new_ce->sha1, ce->sha1);
|
||||
memcpy(new_ce->name, ce->name, len);
|
||||
new_ce->ce_flags = create_ce_flags(len, 0);
|
||||
new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
|
||||
new_ce->ce_mode = ce->ce_mode;
|
||||
if (add_index_entry(istate, new_ce, 0))
|
||||
return error("%s: cannot drop to stage #0",
|
||||
|
183
t/t7110-reset-merge.sh
Executable file
183
t/t7110-reset-merge.sh
Executable file
@ -0,0 +1,183 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2009 Christian Couder
|
||||
#
|
||||
|
||||
test_description='Tests for "git reset --merge"'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success setup '
|
||||
for i in 1 2 3; do echo line $i; done >file1 &&
|
||||
cat file1 >file2 &&
|
||||
git add file1 file2 &&
|
||||
test_tick &&
|
||||
git commit -m "Initial commit" &&
|
||||
git tag initial &&
|
||||
echo line 4 >>file1 &&
|
||||
cat file1 >file2 &&
|
||||
test_tick &&
|
||||
git commit -m "add line 4 to file1" file1 &&
|
||||
git tag second
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: C C C D --merge D D D
|
||||
# file2: C D D D --merge C D D
|
||||
test_expect_success 'reset --merge is ok with changes in file it does not touch' '
|
||||
git reset --merge 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 --merge is ok when switching back' '
|
||||
git reset --merge 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
|
||||
# ----------------------------------------------------
|
||||
# file1: B B C D --merge D D D
|
||||
# file2: C D D D --merge C D D
|
||||
test_expect_success 'reset --merge discards changes added to index (1)' '
|
||||
git reset --hard second &&
|
||||
cat file1 >file2 &&
|
||||
echo "line 5" >> file1 &&
|
||||
git add file1 &&
|
||||
git reset --merge HEAD^ &&
|
||||
! grep 4 file1 &&
|
||||
! grep 5 file1 &&
|
||||
grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
test_expect_success 'reset --merge is ok again when switching back (1)' '
|
||||
git reset --hard initial &&
|
||||
echo "line 5" >> file2 &&
|
||||
git add file2 &&
|
||||
git reset --merge second &&
|
||||
! grep 4 file2 &&
|
||||
! grep 5 file1 &&
|
||||
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
|
||||
# ----------------------------------------------------
|
||||
# file1: C C C D --merge D D D
|
||||
# file2: C C D D --merge D D D
|
||||
test_expect_success 'reset --merge discards changes added to index (2)' '
|
||||
git reset --hard second &&
|
||||
echo "line 4" >> file2 &&
|
||||
git add file2 &&
|
||||
git reset --merge HEAD^ &&
|
||||
! grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
|
||||
test -z "$(git diff)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
test_expect_success 'reset --merge is ok again when switching back (2)' '
|
||||
git reset --hard initial &&
|
||||
git reset --merge 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
|
||||
# ----------------------------------------------------
|
||||
# file1: A B B C --merge (disallowed)
|
||||
test_expect_success 'reset --merge 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 --merge 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 &&
|
||||
git branch branch2 &&
|
||||
git branch branch3 &&
|
||||
git checkout branch1 &&
|
||||
echo "line 5 in branch1" >> file1 &&
|
||||
test_tick &&
|
||||
git commit -a -m "change in branch1" &&
|
||||
git checkout branch2 &&
|
||||
echo "line 5 in branch2" >> file1 &&
|
||||
test_tick &&
|
||||
git commit -a -m "change in branch2" &&
|
||||
git tag third &&
|
||||
git checkout branch3 &&
|
||||
echo a new file >file3 &&
|
||||
rm -f file1 &&
|
||||
git add file3 &&
|
||||
test_tick &&
|
||||
git commit -a -m "change in branch3"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: X U B C --merge C C C
|
||||
test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
|
||||
git checkout third &&
|
||||
test_must_fail git merge branch1 &&
|
||||
git reset --merge HEAD^ &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
|
||||
test -z "$(git diff --cached)" &&
|
||||
test -z "$(git diff)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: X U B B --merge B B B
|
||||
test_expect_success '"reset --merge HEAD" is ok with pending merge' '
|
||||
git reset --hard third &&
|
||||
test_must_fail git merge branch1 &&
|
||||
git reset --merge HEAD &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
|
||||
test -z "$(git diff --cached)" &&
|
||||
test -z "$(git diff)"
|
||||
'
|
||||
|
||||
test_expect_success '--merge with added/deleted' '
|
||||
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 &&
|
||||
git reset --merge HEAD &&
|
||||
! test -f file3 &&
|
||||
! test -f file2 &&
|
||||
git diff --exit-code --cached
|
||||
'
|
||||
|
||||
test_done
|
113
t/t7111-reset-table.sh
Executable file
113
t/t7111-reset-table.sh
Executable file
@ -0,0 +1,113 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2010 Christian Couder
|
||||
#
|
||||
|
||||
test_description='Tests to check that "reset" options follow a known table'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
|
||||
test_expect_success 'creating initial commits' '
|
||||
test_commit E file1 &&
|
||||
test_commit D file1 &&
|
||||
test_commit C file1
|
||||
'
|
||||
|
||||
while read W1 I1 H1 T opt W2 I2 H2
|
||||
do
|
||||
test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
|
||||
git reset --hard C &&
|
||||
if test "$I1" != "$H1"
|
||||
then
|
||||
echo "$I1" >file1 &&
|
||||
git add file1
|
||||
fi &&
|
||||
if test "$W1" != "$I1"
|
||||
then
|
||||
echo "$W1" >file1
|
||||
fi &&
|
||||
if test "$W2" != "XXXXX"
|
||||
then
|
||||
git reset --$opt $T &&
|
||||
test "$(cat file1)" = "$W2" &&
|
||||
git checkout-index -f -- file1 &&
|
||||
test "$(cat file1)" = "$I2" &&
|
||||
git checkout -f HEAD -- file1 &&
|
||||
test "$(cat file1)" = "$H2"
|
||||
else
|
||||
test_must_fail git reset --$opt $T
|
||||
fi
|
||||
'
|
||||
done <<\EOF
|
||||
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 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
|
||||
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 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 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 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
|
||||
EOF
|
||||
|
||||
test_expect_success 'setting up branches to test with unmerged entries' '
|
||||
git reset --hard C &&
|
||||
git branch branch1 &&
|
||||
git branch branch2 &&
|
||||
git checkout branch1 &&
|
||||
test_commit B1 file1 &&
|
||||
git checkout branch2 &&
|
||||
test_commit B2 file1
|
||||
'
|
||||
|
||||
while read W1 I1 H1 T opt W2 I2 H2
|
||||
do
|
||||
test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
|
||||
git reset --hard B2 &&
|
||||
test_must_fail git merge branch1 &&
|
||||
cat file1 >X_file1 &&
|
||||
if test "$W2" != "XXXXX"
|
||||
then
|
||||
git reset --$opt $T &&
|
||||
if test "$W2" = "X"
|
||||
then
|
||||
test_cmp file1 X_file1
|
||||
else
|
||||
test "$(cat file1)" = "$W2"
|
||||
fi &&
|
||||
git checkout-index -f -- file1 &&
|
||||
test "$(cat file1)" = "$I2" &&
|
||||
git checkout -f HEAD -- file1 &&
|
||||
test "$(cat file1)" = "$H2"
|
||||
else
|
||||
test_must_fail git reset --$opt $T
|
||||
fi
|
||||
'
|
||||
done <<\EOF
|
||||
X U C D soft XXXXX
|
||||
X U C D mixed X D D
|
||||
X U C D hard D D D
|
||||
X U C D merge D D D
|
||||
X U C C soft XXXXX
|
||||
X U C C mixed X C C
|
||||
X U C C hard C C C
|
||||
X U C C merge C C C
|
||||
EOF
|
||||
|
||||
test_done
|
@ -550,6 +550,8 @@ static int same(struct cache_entry *a, struct cache_entry *b)
|
||||
return 0;
|
||||
if (!a && !b)
|
||||
return 1;
|
||||
if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED)
|
||||
return 0;
|
||||
return a->ce_mode == b->ce_mode &&
|
||||
!hashcmp(a->sha1, b->sha1);
|
||||
}
|
||||
@ -809,7 +811,11 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
|
||||
{
|
||||
int update = CE_UPDATE;
|
||||
|
||||
if (old) {
|
||||
if (!old) {
|
||||
if (verify_absent(merge, "overwritten", o))
|
||||
return -1;
|
||||
invalidate_ce_path(merge, o);
|
||||
} else if (!(old->ce_flags & CE_CONFLICTED)) {
|
||||
/*
|
||||
* See if we can re-use the old CE directly?
|
||||
* That way we get the uptodate stat info.
|
||||
@ -827,11 +833,12 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
|
||||
update |= CE_SKIP_WORKTREE;
|
||||
invalidate_ce_path(old, o);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (verify_absent(merge, "overwritten", o))
|
||||
return -1;
|
||||
invalidate_ce_path(merge, o);
|
||||
} else {
|
||||
/*
|
||||
* Previously unmerged entry left as an existence
|
||||
* marker by read_index_unmerged();
|
||||
*/
|
||||
invalidate_ce_path(old, o);
|
||||
}
|
||||
|
||||
add_entry(o, merge, update, CE_STAGEMASK);
|
||||
@ -847,7 +854,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (verify_uptodate(old, o))
|
||||
if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
|
||||
return -1;
|
||||
add_entry(o, ce, CE_REMOVE, 0);
|
||||
invalidate_ce_path(ce, o);
|
||||
|
Loading…
Reference in New Issue
Block a user