summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/git/index/fun.py129
-rw-r--r--lib/git/index/typ.py3
-rw-r--r--lib/git/repo.py2
-rw-r--r--test/git/test_fun.py111
4 files changed, 175 insertions, 70 deletions
diff --git a/lib/git/index/fun.py b/lib/git/index/fun.py
index 79fcfddb..b04d018f 100644
--- a/lib/git/index/fun.py
+++ b/lib/git/index/fun.py
@@ -218,71 +218,72 @@ def aggressive_tree_merge(odb, tree_shas):
for entry in traverse_tree_recursive(odb, tree_shas[-1], ''):
out_append(_tree_entry_to_baseindexentry(entry, 0))
# END for each entry
- elif len(tree_shas) == 3:
- for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''):
- if base is not None:
- # base version exists
- if ours is not None:
- # ours exists
- if theirs is not None:
- # it exists in all branches, if it was changed in both
- # its a conflict, otherwise we take the changed version
- # This should be the most common branch, so it comes first
- if( base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0] ) or \
- ( base[1] != ours[1] and base[1] != theirs[1] and ourse[1] != theirs[1] ):
- # changed by both
- out_append(_tree_entry_to_baseindexentry(base, 1))
- out_append(_tree_entry_to_baseindexentry(ours, 2))
- out_append(_tree_entry_to_baseindexentry(theirs, 3))
- elif base[0] != ours[0] or base[1] != ours[1]:
- # only we changed it
- out_append(_tree_entry_to_baseindexentry(ours, 0))
- else:
- # either nobody changed it, or they did. In either
- # case, use theirs
- out_append(_tree_entry_to_baseindexentry(theirs, 0))
- # END handle modification
+ return out
+ # END handle single tree
+
+ if len(tree_shas) > 3:
+ raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
+
+ # three trees
+ for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''):
+ if base is not None:
+ # base version exists
+ if ours is not None:
+ # ours exists
+ if theirs is not None:
+ # it exists in all branches, if it was changed in both
+ # its a conflict, otherwise we take the changed version
+ # This should be the most common branch, so it comes first
+ if( base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0] ) or \
+ ( base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1] ):
+ # changed by both
+ out_append(_tree_entry_to_baseindexentry(base, 1))
+ out_append(_tree_entry_to_baseindexentry(ours, 2))
+ out_append(_tree_entry_to_baseindexentry(theirs, 3))
+ elif base[0] != ours[0] or base[1] != ours[1]:
+ # only we changed it
+ out_append(_tree_entry_to_baseindexentry(ours, 0))
else:
-
- if ours[0] != base[0] or ours[1] != base[1]:
- # they deleted it, we changed it, conflict
- out_append(_tree_entry_to_baseindexentry(base, 1))
- out_append(_tree_entry_to_baseindexentry(ours, 2))
- out_append(_tree_entry_to_baseindexentry(theirs, 3))
- # else:
- # we didn't change it, ignore
- # pass
- # END handle our change
- # END handle theirs
+ # either nobody changed it, or they did. In either
+ # case, use theirs
+ out_append(_tree_entry_to_baseindexentry(theirs, 0))
+ # END handle modification
else:
- if theirs is None:
- # deleted in both, its fine - its out
- pass
- else:
- if theirs[0] != base[0] or theirs[1] != base[1]:
- # deleted in ours, changed theirs, conflict
- out_append(_tree_entry_to_baseindexentry(base, 1))
- out_append(_tree_entry_to_baseindexentry(ours, 2))
- out_append(_tree_entry_to_baseindexentry(theirs, 3))
- # END theirs changed
- #else:
- # theirs didnt change
- # pass
- # END handle theirs
- # END handle ours
+
+ if ours[0] != base[0] or ours[1] != base[1]:
+ # they deleted it, we changed it, conflict
+ out_append(_tree_entry_to_baseindexentry(base, 1))
+ out_append(_tree_entry_to_baseindexentry(ours, 2))
+ # else:
+ # we didn't change it, ignore
+ # pass
+ # END handle our change
+ # END handle theirs
else:
- # all three can't be None
- if ours is None:
- # added in their branch
- out_append(_tree_entry_to_baseindexentry(theirs, 0))
- elif theirs is None:
- # added in our branch
- out_append(_tree_entry_to_baseindexentry(ours, 0))
- # END hanle heads
- # END handle base exists
- # END for each entries tuple
- else:
- raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
- # END handle tree shas
-
+ if theirs is None:
+ # deleted in both, its fine - its out
+ pass
+ else:
+ if theirs[0] != base[0] or theirs[1] != base[1]:
+ # deleted in ours, changed theirs, conflict
+ out_append(_tree_entry_to_baseindexentry(base, 1))
+ out_append(_tree_entry_to_baseindexentry(theirs, 3))
+ # END theirs changed
+ #else:
+ # theirs didnt change
+ # pass
+ # END handle theirs
+ # END handle ours
+ else:
+ # all three can't be None
+ if ours is None:
+ # added in their branch
+ out_append(_tree_entry_to_baseindexentry(theirs, 0))
+ elif theirs is None:
+ # added in our branch
+ out_append(_tree_entry_to_baseindexentry(ours, 0))
+ # END hanle heads
+ # END handle base exists
+ # END for each entries tuple
+
return out
diff --git a/lib/git/index/typ.py b/lib/git/index/typ.py
index 6ef1d2f2..d9cafa2e 100644
--- a/lib/git/index/typ.py
+++ b/lib/git/index/typ.py
@@ -56,6 +56,9 @@ class BaseIndexEntry(tuple):
def __str__(self):
return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path)
+
+ def __repr__(self):
+ return "(%o, %s, %i, %s)" % (self.mode, self.hexsha, self.stage, self.path)
@property
def mode(self):
diff --git a/lib/git/repo.py b/lib/git/repo.py
index f97126ea..79275106 100644
--- a/lib/git/repo.py
+++ b/lib/git/repo.py
@@ -742,7 +742,7 @@ class Repo(object):
# we at least give a proper error instead of letting git fail
prev_cwd = None
prev_path = None
- odbt = kwargs.pop('odbt', GitCmdObjectDB)
+ odbt = kwargs.pop('odbt', type(self.odb))
if os.name == 'nt':
if '~' in path:
raise OSError("Git cannot handle the ~ character in path %r correctly" % path)
diff --git a/test/git/test_fun.py b/test/git/test_fun.py
index 4ddc1910..ce610014 100644
--- a/test/git/test_fun.py
+++ b/test/git/test_fun.py
@@ -1,20 +1,26 @@
from test.testlib import *
from git.objects.fun import (
traverse_tree_recursive,
- traverse_trees_recursive
+ traverse_trees_recursive,
+ tree_to_stream
)
from git.index.fun import (
aggressive_tree_merge
)
-from git.index import IndexFile
+from gitdb.base import IStream
+from gitdb.typ import str_tree_type
+
from stat import (
S_IFDIR,
S_IFREG,
S_IFLNK
)
+from git.index import IndexFile
+from cStringIO import StringIO
+
class TestFun(TestBase):
def _assert_index_entries(self, entries, trees):
@@ -55,18 +61,113 @@ class TestFun(TestBase):
trees = [B.sha, H.sha, M.sha]
self._assert_index_entries(aggressive_tree_merge(odb, trees), trees)
- def make_tree(odb, entries):
+ def mktree(self, odb, entries):
"""create a tree from the given tree entries and safe it to the database"""
-
+ sio = StringIO()
+ tree_to_stream(entries, sio.write)
+ sio.seek(0)
+ istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
+ return istream.sha
@with_rw_repo('0.1.6')
def test_three_way_merge(self, rwrepo):
def mkfile(name, sha, executable=0):
- return (sha, S_IFREG | 644 | executable*0111, name)
+ return (sha, S_IFREG | 0644 | executable*0111, name)
def mkcommit(name, sha):
return (sha, S_IFDIR | S_IFLNK, name)
+ def assert_entries(entries, num_entries, has_conflict=False):
+ assert len(entries) == num_entries
+ assert has_conflict == (len([e for e in entries if e.stage != 0]) > 0)
+ mktree = self.mktree
+
+ shaa = "\1"*20
+ shab = "\2"*20
+ shac = "\3"*20
+
odb = rwrepo.odb
+ # base tree
+ bfn = 'basefile'
+ fbase = mkfile(bfn, shaa)
+ tb = mktree(odb, [fbase])
+
+ # non-conflicting new files, same data
+ fa = mkfile('1', shab)
+ th = mktree(odb, [fbase, fa])
+ fb = mkfile('2', shac)
+ tm = mktree(odb, [fbase, fb])
+
+ # two new files, same base file
+ trees = [tb, th, tm]
+ assert_entries(aggressive_tree_merge(odb, trees), 3)
+
+ # both delete same file, add own one
+ fa = mkfile('1', shab)
+ th = mktree(odb, [fa])
+ fb = mkfile('2', shac)
+ tm = mktree(odb, [fb])
+
+ # two new files
+ trees = [tb, th, tm]
+ assert_entries(aggressive_tree_merge(odb, trees), 2)
+
+ # modify same base file, differently
+ fa = mkfile(bfn, shab)
+ th = mktree(odb, [fa])
+ fb = mkfile(bfn, shac)
+ tm = mktree(odb, [fb])
+
+ # conflict, 3 versions on 3 stages
+ trees = [tb, th, tm]
+ assert_entries(aggressive_tree_merge(odb, trees), 3, True)
+
+
+ # change mode on same base file, by making one a commit, the other executable
+ # no content change ( this is totally unlikely to happen in the real world )
+ fa = mkcommit(bfn, shaa)
+ th = mktree(odb, [fa])
+ fb = mkfile(bfn, shaa, executable=1)
+ tm = mktree(odb, [fb])
+
+ # conflict, 3 versions on 3 stages, because of different mode
+ trees = [tb, th, tm]
+ assert_entries(aggressive_tree_merge(odb, trees), 3, True)
+
+ for is_them in range(2):
+ # only we/they change contents
+ fa = mkfile(bfn, shab)
+ th = mktree(odb, [fa])
+
+ trees = [tb, th, tb]
+ if is_them:
+ trees = [tb, tb, th]
+ entries = aggressive_tree_merge(odb, trees)
+ assert len(entries) == 1 and entries[0].binsha == shab
+
+ # only we/they change the mode
+ fa = mkcommit(bfn, shaa)
+ th = mktree(odb, [fa])
+
+ trees = [tb, th, tb]
+ if is_them:
+ trees = [tb, tb, th]
+ entries = aggressive_tree_merge(odb, trees)
+ assert len(entries) == 1 and entries[0].binsha == shaa and entries[0].mode == fa[1]
+
+ # one side deletes, the other changes = conflict
+ fa = mkfile(bfn, shab)
+ th = mktree(odb, [fa])
+ tm = mktree(odb, [])
+ trees = [tb, th, tm]
+ if is_them:
+ trees = [tb, tm, th]
+ # as one is deleted, there are only 2 entries
+ assert_entries(aggressive_tree_merge(odb, trees), 2, True)
+ # END handle ours, theirs
+
+
+
+
def _assert_tree_entries(self, entries, num_trees):
assert len(entries[0]) == num_trees