Skip to content

Commit e11d7b5

Browse files
committed
"reset --merge": fix unmerged case
Commit 9e8ecea (Add 'merge' mode to 'git reset', 2008-12-01) disallowed "git reset --merge" when there was unmerged entries. But it wished if unmerged entries were reset as if --hard (instead of --merge) has been used. This makes sense because all "mergy" operations makes sure that any path involved in the merge does not have local modifications before starting, so resetting such a path away won't lose any information. The previous commit changed the behavior of --merge to accept resetting unmerged entries if they are reset to a different state than HEAD, but it did not reset the changes in the work tree, leaving the conflict markers in the resulting file in the work tree. Fix it by doing three things: - Update the documentation to match the wish of original "reset --merge" better, namely, "An unmerged entry is a sign that the path didn't have any local modification and can be safely resetted to whatever the new HEAD records"; - Update read_index_unmerged(), which reads the index file into the cache while dropping any higher-stage entries down to stage #0, not to copy the object name from the higher stage entry. The code used to take the object name from the a stage entry ("base" if you happened to have stage #1, or "ours" if both sides added, etc.), which essentially meant that you are getting random results depending on what the merge did. The _only_ reason we want to keep a previously unmerged entry in the index at stage #0 is so that we don't forget the fact that we have corresponding file in the work tree in order to be able to remove it when the tree we are resetting to does not have the path. In order to differentiate such an entry from ordinary cache entry, the cache entry added by read_index_unmerged() is marked as CE_CONFLICTED. - Update merged_entry() and deleted_entry() so that they pay attention to cache entries marked as CE_CONFLICTED. They are previously unmerged entries, and the files in the work tree that correspond to them are resetted away by oneway_merge() to the version from the tree we are resetting to. Signed-off-by: Junio C Hamano <[email protected]>
1 parent d0f379c commit e11d7b5

File tree

5 files changed

+49
-20
lines changed

5 files changed

+49
-20
lines changed

Documentation/git-reset.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,14 @@ entries:
122122
X U A B --soft (disallowed)
123123
--mixed X B B
124124
--hard B B B
125-
--merge X B B
125+
--merge B B B
126126

127127
working index HEAD target working index HEAD
128128
----------------------------------------------------
129129
X U A A --soft (disallowed)
130130
--mixed X A A
131131
--hard A A A
132-
--merge (disallowed)
132+
--merge A A A
133133

134134
X means any state and U means an unmerged index.
135135

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 (0x400000)
180181

181182
/*
182183
* Extended on-disk flags

read-cache.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,9 +1606,8 @@ int read_index_unmerged(struct index_state *istate)
16061606
len = strlen(ce->name);
16071607
size = cache_entry_size(len);
16081608
new_ce = xcalloc(1, size);
1609-
hashcpy(new_ce->sha1, ce->sha1);
16101609
memcpy(new_ce->name, ce->name, len);
1611-
new_ce->ce_flags = create_ce_flags(len, 0);
1610+
new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
16121611
new_ce->ce_mode = ce->ce_mode;
16131612
if (add_index_entry(istate, new_ce, 0))
16141613
return error("%s: cannot drop to stage #0",

t/t7110-reset-merge.sh

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,11 @@ test_expect_success 'reset --merge fails with changes in file it touches' '
116116
grep file1 err.log | grep "not uptodate"
117117
'
118118

119-
test_expect_success 'setup 2 different branches' '
119+
test_expect_success 'setup 3 different branches' '
120120
git reset --hard second &&
121121
git branch branch1 &&
122122
git branch branch2 &&
123+
git branch branch3 &&
123124
git checkout branch1 &&
124125
echo "line 5 in branch1" >> file1 &&
125126
test_tick &&
@@ -128,34 +129,55 @@ test_expect_success 'setup 2 different branches' '
128129
echo "line 5 in branch2" >> file1 &&
129130
test_tick &&
130131
git commit -a -m "change in branch2" &&
131-
git tag third
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"
132139
'
133140

134141
# The next test will test the following:
135142
#
136143
# working index HEAD target working index HEAD
137144
# ----------------------------------------------------
138-
# file1: X U B C --merge X C C
145+
# file1: X U B C --merge C C C
139146
test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
147+
git checkout third &&
140148
test_must_fail git merge branch1 &&
141-
cat file1 >orig_file1 &&
142149
git reset --merge HEAD^ &&
143150
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
144151
test -z "$(git diff --cached)" &&
145-
test_cmp file1 orig_file1
152+
test -z "$(git diff)"
146153
'
147154

148155
# The next test will test the following:
149156
#
150157
# working index HEAD target working index HEAD
151158
# ----------------------------------------------------
152-
# file1: X U B B --merge (disallowed)
153-
test_expect_success '"reset --merge HEAD" fails with pending merge' '
159+
# file1: X U B B --merge B B B
160+
test_expect_success '"reset --merge HEAD" is ok with pending merge' '
154161
git reset --hard third &&
155162
test_must_fail git merge branch1 &&
156-
test_must_fail git reset --merge HEAD &&
163+
git reset --merge HEAD &&
157164
test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
158-
test -n "$(git diff --cached)"
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
159181
'
160182

161183
test_done

unpack-trees.c

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,8 @@ static int same(struct cache_entry *a, struct cache_entry *b)
436436
return 0;
437437
if (!a && !b)
438438
return 1;
439+
if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED)
440+
return 0;
439441
return a->ce_mode == b->ce_mode &&
440442
!hashcmp(a->sha1, b->sha1);
441443
}
@@ -666,7 +668,11 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
666668
{
667669
int update = CE_UPDATE;
668670

669-
if (old) {
671+
if (!old) {
672+
if (verify_absent(merge, "overwritten", o))
673+
return -1;
674+
invalidate_ce_path(merge, o);
675+
} else if (!(old->ce_flags & CE_CONFLICTED)) {
670676
/*
671677
* See if we can re-use the old CE directly?
672678
* That way we get the uptodate stat info.
@@ -682,11 +688,12 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
682688
return -1;
683689
invalidate_ce_path(old, o);
684690
}
685-
}
686-
else {
687-
if (verify_absent(merge, "overwritten", o))
688-
return -1;
689-
invalidate_ce_path(merge, o);
691+
} else {
692+
/*
693+
* Previously unmerged entry left as an existence
694+
* marker by read_index_unmerged();
695+
*/
696+
invalidate_ce_path(old, o);
690697
}
691698

692699
add_entry(o, merge, update, CE_STAGEMASK);
@@ -702,7 +709,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
702709
return -1;
703710
return 0;
704711
}
705-
if (verify_uptodate(old, o))
712+
if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
706713
return -1;
707714
add_entry(o, ce, CE_REMOVE, 0);
708715
invalidate_ce_path(ce, o);

0 commit comments

Comments
 (0)