Skip to content

Commit dc96c5e

Browse files
committed
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
2 parents 73d6632 + d7eed8c commit dc96c5e

File tree

7 files changed

+435
-20
lines changed

7 files changed

+435
-20
lines changed

Documentation/git-reset.txt

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,95 @@ linkgit:git-add[1]).
6868
<commit>::
6969
Commit to make the current HEAD. If not given defaults to HEAD.
7070

71+
DISCUSSION
72+
----------
73+
74+
The tables below show what happens when running:
75+
76+
----------
77+
git reset --option target
78+
----------
79+
80+
to reset the HEAD to another commit (`target`) with the different
81+
reset options depending on the state of the files.
82+
83+
In these tables, A, B, C and D are some different states of a
84+
file. For example, the first line of the first table means that if a
85+
file is in state A in the working tree, in state B in the index, in
86+
state C in HEAD and in state D in the target, then "git reset --soft
87+
target" will put the file in state A in the working tree, in state B
88+
in the index and in state D in HEAD.
89+
90+
working index HEAD target working index HEAD
91+
----------------------------------------------------
92+
A B C D --soft A B D
93+
--mixed A D D
94+
--hard D D D
95+
--merge (disallowed)
96+
97+
working index HEAD target working index HEAD
98+
----------------------------------------------------
99+
A B C C --soft A B C
100+
--mixed A C C
101+
--hard C C C
102+
--merge (disallowed)
103+
104+
working index HEAD target working index HEAD
105+
----------------------------------------------------
106+
B B C D --soft B B D
107+
--mixed B D D
108+
--hard D D D
109+
--merge D D D
110+
111+
working index HEAD target working index HEAD
112+
----------------------------------------------------
113+
B B C C --soft B B C
114+
--mixed B C C
115+
--hard C C C
116+
--merge C C C
117+
118+
working index HEAD target working index HEAD
119+
----------------------------------------------------
120+
B C C D --soft B C D
121+
--mixed B D D
122+
--hard D D D
123+
--merge (disallowed)
124+
125+
working index HEAD target working index HEAD
126+
----------------------------------------------------
127+
B C C C --soft B C C
128+
--mixed B C C
129+
--hard C C C
130+
--merge B C C
131+
132+
"reset --merge" is meant to be used when resetting out of a conflicted
133+
merge. Any mergy operation guarantees that the work tree file that is
134+
involved in the merge does not have local change wrt the index before
135+
it starts, and that it writes the result out to the work tree. So if
136+
we see some difference between the index and the target and also
137+
between the index and the work tree, then it means that we are not
138+
resetting out from a state that a mergy operation left after failing
139+
with a conflict. That is why we disallow --merge option in this case.
140+
141+
The following tables show what happens when there are unmerged
142+
entries:
143+
144+
working index HEAD target working index HEAD
145+
----------------------------------------------------
146+
X U A B --soft (disallowed)
147+
--mixed X B B
148+
--hard B B B
149+
--merge B B B
150+
151+
working index HEAD target working index HEAD
152+
----------------------------------------------------
153+
X U A A --soft (disallowed)
154+
--mixed X A A
155+
--hard A A A
156+
--merge A A A
157+
158+
X means any state and U means an unmerged index.
159+
71160
Examples
72161
--------
73162

builtin-reset.c

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "tree.h"
1919
#include "branch.h"
2020
#include "parse-options.h"
21+
#include "unpack-trees.h"
22+
#include "cache-tree.h"
2123

2224
static const char * const git_reset_usage[] = {
2325
"git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
@@ -54,27 +56,44 @@ static inline int is_merge(void)
5456

5557
static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
5658
{
57-
int i = 0;
58-
const char *args[6];
59+
int nr = 1;
60+
int newfd;
61+
struct tree_desc desc[2];
62+
struct unpack_trees_options opts;
63+
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
5964

60-
args[i++] = "read-tree";
65+
memset(&opts, 0, sizeof(opts));
66+
opts.head_idx = 1;
67+
opts.src_index = &the_index;
68+
opts.dst_index = &the_index;
69+
opts.fn = oneway_merge;
70+
opts.merge = 1;
6171
if (!quiet)
62-
args[i++] = "-v";
72+
opts.verbose_update = 1;
6373
switch (reset_type) {
6474
case MERGE:
65-
args[i++] = "-u";
66-
args[i++] = "-m";
75+
opts.update = 1;
6776
break;
6877
case HARD:
69-
args[i++] = "-u";
78+
opts.update = 1;
7079
/* fallthrough */
7180
default:
72-
args[i++] = "--reset";
81+
opts.reset = 1;
7382
}
74-
args[i++] = sha1_to_hex(sha1);
75-
args[i] = NULL;
7683

77-
return run_command_v_opt(args, RUN_GIT_CMD);
84+
newfd = hold_locked_index(lock, 1);
85+
86+
read_cache_unmerged();
87+
88+
if (!fill_tree_descriptor(desc + nr - 1, sha1))
89+
return error("Failed to find tree of %s.", sha1_to_hex(sha1));
90+
if (unpack_trees(nr, desc, &opts))
91+
return -1;
92+
if (write_cache(newfd, active_cache, active_nr) ||
93+
commit_locked_index(lock))
94+
return error("Could not write new index file.");
95+
96+
return 0;
7897
}
7998

8099
static void print_new_head_line(struct commit *commit)
@@ -288,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
288307
if (reset_type == HARD || reset_type == MERGE)
289308
setup_work_tree();
290309

310+
if (reset_type == MIXED && is_bare_repository())
311+
die("%s reset is not allowed in a bare repository",
312+
reset_type_names[reset_type]);
313+
291314
/* Soft reset does not touch the index file nor the working tree
292315
* at all, but requires them in a good order. Other resets reset
293316
* the index file to the tree object we are switching to. */

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ struct cache_entry {
177177

178178
#define CE_HASHED (0x100000)
179179
#define CE_UNHASHED (0x200000)
180+
#define CE_CONFLICTED (0x800000)
180181

181182
/* Only remove in work directory, not index */
182183
#define CE_WT_REMOVE (0x400000)

read-cache.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,9 +1617,8 @@ int read_index_unmerged(struct index_state *istate)
16171617
len = strlen(ce->name);
16181618
size = cache_entry_size(len);
16191619
new_ce = xcalloc(1, size);
1620-
hashcpy(new_ce->sha1, ce->sha1);
16211620
memcpy(new_ce->name, ce->name, len);
1622-
new_ce->ce_flags = create_ce_flags(len, 0);
1621+
new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
16231622
new_ce->ce_mode = ce->ce_mode;
16241623
if (add_index_entry(istate, new_ce, 0))
16251624
return error("%s: cannot drop to stage #0",

t/t7110-reset-merge.sh

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (c) 2009 Christian Couder
4+
#
5+
6+
test_description='Tests for "git reset --merge"'
7+
8+
. ./test-lib.sh
9+
10+
test_expect_success setup '
11+
for i in 1 2 3; do echo line $i; done >file1 &&
12+
cat file1 >file2 &&
13+
git add file1 file2 &&
14+
test_tick &&
15+
git commit -m "Initial commit" &&
16+
git tag initial &&
17+
echo line 4 >>file1 &&
18+
cat file1 >file2 &&
19+
test_tick &&
20+
git commit -m "add line 4 to file1" file1 &&
21+
git tag second
22+
'
23+
24+
# The next test will test the following:
25+
#
26+
# working index HEAD target working index HEAD
27+
# ----------------------------------------------------
28+
# file1: C C C D --merge D D D
29+
# file2: C D D D --merge C D D
30+
test_expect_success 'reset --merge is ok with changes in file it does not touch' '
31+
git reset --merge HEAD^ &&
32+
! grep 4 file1 &&
33+
grep 4 file2 &&
34+
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
35+
test -z "$(git diff --cached)"
36+
'
37+
38+
test_expect_success 'reset --merge is ok when switching back' '
39+
git reset --merge second &&
40+
grep 4 file1 &&
41+
grep 4 file2 &&
42+
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
43+
test -z "$(git diff --cached)"
44+
'
45+
46+
# The next test will test the following:
47+
#
48+
# working index HEAD target working index HEAD
49+
# ----------------------------------------------------
50+
# file1: B B C D --merge D D D
51+
# file2: C D D D --merge C D D
52+
test_expect_success 'reset --merge discards changes added to index (1)' '
53+
git reset --hard second &&
54+
cat file1 >file2 &&
55+
echo "line 5" >> file1 &&
56+
git add file1 &&
57+
git reset --merge HEAD^ &&
58+
! grep 4 file1 &&
59+
! grep 5 file1 &&
60+
grep 4 file2 &&
61+
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
62+
test -z "$(git diff --cached)"
63+
'
64+
65+
test_expect_success 'reset --merge is ok again when switching back (1)' '
66+
git reset --hard initial &&
67+
echo "line 5" >> file2 &&
68+
git add file2 &&
69+
git reset --merge second &&
70+
! grep 4 file2 &&
71+
! grep 5 file1 &&
72+
grep 4 file1 &&
73+
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
74+
test -z "$(git diff --cached)"
75+
'
76+
77+
# The next test will test the following:
78+
#
79+
# working index HEAD target working index HEAD
80+
# ----------------------------------------------------
81+
# file1: C C C D --merge D D D
82+
# file2: C C D D --merge D D D
83+
test_expect_success 'reset --merge discards changes added to index (2)' '
84+
git reset --hard second &&
85+
echo "line 4" >> file2 &&
86+
git add file2 &&
87+
git reset --merge HEAD^ &&
88+
! grep 4 file2 &&
89+
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
90+
test -z "$(git diff)" &&
91+
test -z "$(git diff --cached)"
92+
'
93+
94+
test_expect_success 'reset --merge is ok again when switching back (2)' '
95+
git reset --hard initial &&
96+
git reset --merge second &&
97+
! grep 4 file2 &&
98+
grep 4 file1 &&
99+
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
100+
test -z "$(git diff --cached)"
101+
'
102+
103+
# The next test will test the following:
104+
#
105+
# working index HEAD target working index HEAD
106+
# ----------------------------------------------------
107+
# file1: A B B C --merge (disallowed)
108+
test_expect_success 'reset --merge fails with changes in file it touches' '
109+
git reset --hard second &&
110+
echo "line 5" >> file1 &&
111+
test_tick &&
112+
git commit -m "add line 5" file1 &&
113+
sed -e "s/line 1/changed line 1/" <file1 >file3 &&
114+
mv file3 file1 &&
115+
test_must_fail git reset --merge HEAD^ 2>err.log &&
116+
grep file1 err.log | grep "not uptodate"
117+
'
118+
119+
test_expect_success 'setup 3 different branches' '
120+
git reset --hard second &&
121+
git branch branch1 &&
122+
git branch branch2 &&
123+
git branch branch3 &&
124+
git checkout branch1 &&
125+
echo "line 5 in branch1" >> file1 &&
126+
test_tick &&
127+
git commit -a -m "change in branch1" &&
128+
git checkout branch2 &&
129+
echo "line 5 in branch2" >> file1 &&
130+
test_tick &&
131+
git commit -a -m "change in branch2" &&
132+
git tag third &&
133+
git checkout branch3 &&
134+
echo a new file >file3 &&
135+
rm -f file1 &&
136+
git add file3 &&
137+
test_tick &&
138+
git commit -a -m "change in branch3"
139+
'
140+
141+
# The next test will test the following:
142+
#
143+
# working index HEAD target working index HEAD
144+
# ----------------------------------------------------
145+
# file1: X U B C --merge C C C
146+
test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
147+
git checkout third &&
148+
test_must_fail git merge branch1 &&
149+
git reset --merge HEAD^ &&
150+
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
151+
test -z "$(git diff --cached)" &&
152+
test -z "$(git diff)"
153+
'
154+
155+
# The next test will test the following:
156+
#
157+
# working index HEAD target working index HEAD
158+
# ----------------------------------------------------
159+
# file1: X U B B --merge B B B
160+
test_expect_success '"reset --merge HEAD" is ok with pending merge' '
161+
git reset --hard third &&
162+
test_must_fail git merge branch1 &&
163+
git reset --merge HEAD &&
164+
test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
165+
test -z "$(git diff --cached)" &&
166+
test -z "$(git diff)"
167+
'
168+
169+
test_expect_success '--merge with added/deleted' '
170+
git reset --hard third &&
171+
rm -f file2 &&
172+
test_must_fail git merge branch3 &&
173+
! test -f file2 &&
174+
test -f file3 &&
175+
git diff --exit-code file3 &&
176+
git diff --exit-code branch3 file3 &&
177+
git reset --merge HEAD &&
178+
! test -f file3 &&
179+
! test -f file2 &&
180+
git diff --exit-code --cached
181+
'
182+
183+
test_done

0 commit comments

Comments
 (0)