Skip to content

Commit aea0243

Browse files
committed
Added test for aggressive_tree_merge
1 parent c0ef65b commit aea0243

File tree

4 files changed

+175
-70
lines changed

4 files changed

+175
-70
lines changed

lib/git/index/fun.py

+65-64
Original file line numberDiff line numberDiff line change
@@ -218,71 +218,72 @@ def aggressive_tree_merge(odb, tree_shas):
218218
for entry in traverse_tree_recursive(odb, tree_shas[-1], ''):
219219
out_append(_tree_entry_to_baseindexentry(entry, 0))
220220
# END for each entry
221-
elif len(tree_shas) == 3:
222-
for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''):
223-
if base is not None:
224-
# base version exists
225-
if ours is not None:
226-
# ours exists
227-
if theirs is not None:
228-
# it exists in all branches, if it was changed in both
229-
# its a conflict, otherwise we take the changed version
230-
# This should be the most common branch, so it comes first
231-
if( base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0] ) or \
232-
( base[1] != ours[1] and base[1] != theirs[1] and ourse[1] != theirs[1] ):
233-
# changed by both
234-
out_append(_tree_entry_to_baseindexentry(base, 1))
235-
out_append(_tree_entry_to_baseindexentry(ours, 2))
236-
out_append(_tree_entry_to_baseindexentry(theirs, 3))
237-
elif base[0] != ours[0] or base[1] != ours[1]:
238-
# only we changed it
239-
out_append(_tree_entry_to_baseindexentry(ours, 0))
240-
else:
241-
# either nobody changed it, or they did. In either
242-
# case, use theirs
243-
out_append(_tree_entry_to_baseindexentry(theirs, 0))
244-
# END handle modification
221+
return out
222+
# END handle single tree
223+
224+
if len(tree_shas) > 3:
225+
raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
226+
227+
# three trees
228+
for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''):
229+
if base is not None:
230+
# base version exists
231+
if ours is not None:
232+
# ours exists
233+
if theirs is not None:
234+
# it exists in all branches, if it was changed in both
235+
# its a conflict, otherwise we take the changed version
236+
# This should be the most common branch, so it comes first
237+
if( base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0] ) or \
238+
( base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1] ):
239+
# changed by both
240+
out_append(_tree_entry_to_baseindexentry(base, 1))
241+
out_append(_tree_entry_to_baseindexentry(ours, 2))
242+
out_append(_tree_entry_to_baseindexentry(theirs, 3))
243+
elif base[0] != ours[0] or base[1] != ours[1]:
244+
# only we changed it
245+
out_append(_tree_entry_to_baseindexentry(ours, 0))
245246
else:
246-
247-
if ours[0] != base[0] or ours[1] != base[1]:
248-
# they deleted it, we changed it, conflict
249-
out_append(_tree_entry_to_baseindexentry(base, 1))
250-
out_append(_tree_entry_to_baseindexentry(ours, 2))
251-
out_append(_tree_entry_to_baseindexentry(theirs, 3))
252-
# else:
253-
# we didn't change it, ignore
254-
# pass
255-
# END handle our change
256-
# END handle theirs
247+
# either nobody changed it, or they did. In either
248+
# case, use theirs
249+
out_append(_tree_entry_to_baseindexentry(theirs, 0))
250+
# END handle modification
257251
else:
258-
if theirs is None:
259-
# deleted in both, its fine - its out
260-
pass
261-
else:
262-
if theirs[0] != base[0] or theirs[1] != base[1]:
263-
# deleted in ours, changed theirs, conflict
264-
out_append(_tree_entry_to_baseindexentry(base, 1))
265-
out_append(_tree_entry_to_baseindexentry(ours, 2))
266-
out_append(_tree_entry_to_baseindexentry(theirs, 3))
267-
# END theirs changed
268-
#else:
269-
# theirs didnt change
270-
# pass
271-
# END handle theirs
272-
# END handle ours
252+
253+
if ours[0] != base[0] or ours[1] != base[1]:
254+
# they deleted it, we changed it, conflict
255+
out_append(_tree_entry_to_baseindexentry(base, 1))
256+
out_append(_tree_entry_to_baseindexentry(ours, 2))
257+
# else:
258+
# we didn't change it, ignore
259+
# pass
260+
# END handle our change
261+
# END handle theirs
273262
else:
274-
# all three can't be None
275-
if ours is None:
276-
# added in their branch
277-
out_append(_tree_entry_to_baseindexentry(theirs, 0))
278-
elif theirs is None:
279-
# added in our branch
280-
out_append(_tree_entry_to_baseindexentry(ours, 0))
281-
# END hanle heads
282-
# END handle base exists
283-
# END for each entries tuple
284-
else:
285-
raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
286-
# END handle tree shas
287-
263+
if theirs is None:
264+
# deleted in both, its fine - its out
265+
pass
266+
else:
267+
if theirs[0] != base[0] or theirs[1] != base[1]:
268+
# deleted in ours, changed theirs, conflict
269+
out_append(_tree_entry_to_baseindexentry(base, 1))
270+
out_append(_tree_entry_to_baseindexentry(theirs, 3))
271+
# END theirs changed
272+
#else:
273+
# theirs didnt change
274+
# pass
275+
# END handle theirs
276+
# END handle ours
277+
else:
278+
# all three can't be None
279+
if ours is None:
280+
# added in their branch
281+
out_append(_tree_entry_to_baseindexentry(theirs, 0))
282+
elif theirs is None:
283+
# added in our branch
284+
out_append(_tree_entry_to_baseindexentry(ours, 0))
285+
# END hanle heads
286+
# END handle base exists
287+
# END for each entries tuple
288+
288289
return out

lib/git/index/typ.py

+3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class BaseIndexEntry(tuple):
5656

5757
def __str__(self):
5858
return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path)
59+
60+
def __repr__(self):
61+
return "(%o, %s, %i, %s)" % (self.mode, self.hexsha, self.stage, self.path)
5962

6063
@property
6164
def mode(self):

lib/git/repo.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ def clone(self, path, **kwargs):
742742
# we at least give a proper error instead of letting git fail
743743
prev_cwd = None
744744
prev_path = None
745-
odbt = kwargs.pop('odbt', GitCmdObjectDB)
745+
odbt = kwargs.pop('odbt', type(self.odb))
746746
if os.name == 'nt':
747747
if '~' in path:
748748
raise OSError("Git cannot handle the ~ character in path %r correctly" % path)

test/git/test_fun.py

+106-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
from test.testlib import *
22
from git.objects.fun import (
33
traverse_tree_recursive,
4-
traverse_trees_recursive
4+
traverse_trees_recursive,
5+
tree_to_stream
56
)
67

78
from git.index.fun import (
89
aggressive_tree_merge
910
)
1011

11-
from git.index import IndexFile
12+
from gitdb.base import IStream
13+
from gitdb.typ import str_tree_type
14+
1215
from stat import (
1316
S_IFDIR,
1417
S_IFREG,
1518
S_IFLNK
1619
)
1720

21+
from git.index import IndexFile
22+
from cStringIO import StringIO
23+
1824
class TestFun(TestBase):
1925

2026
def _assert_index_entries(self, entries, trees):
@@ -55,18 +61,113 @@ def test_aggressive_tree_merge(self):
5561
trees = [B.sha, H.sha, M.sha]
5662
self._assert_index_entries(aggressive_tree_merge(odb, trees), trees)
5763

58-
def make_tree(odb, entries):
64+
def mktree(self, odb, entries):
5965
"""create a tree from the given tree entries and safe it to the database"""
60-
66+
sio = StringIO()
67+
tree_to_stream(entries, sio.write)
68+
sio.seek(0)
69+
istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
70+
return istream.sha
6171

6272
@with_rw_repo('0.1.6')
6373
def test_three_way_merge(self, rwrepo):
6474
def mkfile(name, sha, executable=0):
65-
return (sha, S_IFREG | 644 | executable*0111, name)
75+
return (sha, S_IFREG | 0644 | executable*0111, name)
6676
def mkcommit(name, sha):
6777
return (sha, S_IFDIR | S_IFLNK, name)
78+
def assert_entries(entries, num_entries, has_conflict=False):
79+
assert len(entries) == num_entries
80+
assert has_conflict == (len([e for e in entries if e.stage != 0]) > 0)
81+
mktree = self.mktree
82+
83+
shaa = "\1"*20
84+
shab = "\2"*20
85+
shac = "\3"*20
86+
6887
odb = rwrepo.odb
6988

89+
# base tree
90+
bfn = 'basefile'
91+
fbase = mkfile(bfn, shaa)
92+
tb = mktree(odb, [fbase])
93+
94+
# non-conflicting new files, same data
95+
fa = mkfile('1', shab)
96+
th = mktree(odb, [fbase, fa])
97+
fb = mkfile('2', shac)
98+
tm = mktree(odb, [fbase, fb])
99+
100+
# two new files, same base file
101+
trees = [tb, th, tm]
102+
assert_entries(aggressive_tree_merge(odb, trees), 3)
103+
104+
# both delete same file, add own one
105+
fa = mkfile('1', shab)
106+
th = mktree(odb, [fa])
107+
fb = mkfile('2', shac)
108+
tm = mktree(odb, [fb])
109+
110+
# two new files
111+
trees = [tb, th, tm]
112+
assert_entries(aggressive_tree_merge(odb, trees), 2)
113+
114+
# modify same base file, differently
115+
fa = mkfile(bfn, shab)
116+
th = mktree(odb, [fa])
117+
fb = mkfile(bfn, shac)
118+
tm = mktree(odb, [fb])
119+
120+
# conflict, 3 versions on 3 stages
121+
trees = [tb, th, tm]
122+
assert_entries(aggressive_tree_merge(odb, trees), 3, True)
123+
124+
125+
# change mode on same base file, by making one a commit, the other executable
126+
# no content change ( this is totally unlikely to happen in the real world )
127+
fa = mkcommit(bfn, shaa)
128+
th = mktree(odb, [fa])
129+
fb = mkfile(bfn, shaa, executable=1)
130+
tm = mktree(odb, [fb])
131+
132+
# conflict, 3 versions on 3 stages, because of different mode
133+
trees = [tb, th, tm]
134+
assert_entries(aggressive_tree_merge(odb, trees), 3, True)
135+
136+
for is_them in range(2):
137+
# only we/they change contents
138+
fa = mkfile(bfn, shab)
139+
th = mktree(odb, [fa])
140+
141+
trees = [tb, th, tb]
142+
if is_them:
143+
trees = [tb, tb, th]
144+
entries = aggressive_tree_merge(odb, trees)
145+
assert len(entries) == 1 and entries[0].binsha == shab
146+
147+
# only we/they change the mode
148+
fa = mkcommit(bfn, shaa)
149+
th = mktree(odb, [fa])
150+
151+
trees = [tb, th, tb]
152+
if is_them:
153+
trees = [tb, tb, th]
154+
entries = aggressive_tree_merge(odb, trees)
155+
assert len(entries) == 1 and entries[0].binsha == shaa and entries[0].mode == fa[1]
156+
157+
# one side deletes, the other changes = conflict
158+
fa = mkfile(bfn, shab)
159+
th = mktree(odb, [fa])
160+
tm = mktree(odb, [])
161+
trees = [tb, th, tm]
162+
if is_them:
163+
trees = [tb, tm, th]
164+
# as one is deleted, there are only 2 entries
165+
assert_entries(aggressive_tree_merge(odb, trees), 2, True)
166+
# END handle ours, theirs
167+
168+
169+
170+
70171

71172
def _assert_tree_entries(self, entries, num_trees):
72173
assert len(entries[0]) == num_trees

0 commit comments

Comments
 (0)