~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge_core.py

  • Committer: Martin Pool
  • Date: 2006-02-22 04:29:54 UTC
  • mfrom: (1566 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1569.
  • Revision ID: mbp@sourcefrog.net-20060222042954-60333f08dd56a646
[merge] from bzr.dev before integration
Fix undefined ordering in sign_my_revisions breaking tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
import stat
4
4
import sys
5
5
 
 
6
import bzrlib
6
7
from bzrlib.add import smart_add_tree
7
 
from bzrlib.branch import ScratchBranch, Branch
8
8
from bzrlib.builtins import merge
9
9
from bzrlib.errors import (NotBranchError, NotVersionedError,
10
 
                           WorkingTreeNotRevision, BzrCommandError)
 
10
                           WorkingTreeNotRevision, BzrCommandError, NoDiff3)
11
11
from bzrlib.inventory import RootEntry
12
12
import bzrlib.inventory as inventory
 
13
from bzrlib.merge import Merge3Merger, Diff3Merger, WeaveMerger
13
14
from bzrlib.osutils import file_kind, rename, sha_file, pathjoin, mkdtemp
14
 
from bzrlib import _changeset as changeset
15
 
from bzrlib._merge_core import (ApplyMerge3, make_merge_changeset,
16
 
                               BackupBeforeChange, ExecFlagMerge, WeaveMerge)
17
 
from bzrlib._changeset import Inventory, apply_changeset, invert_dict, \
18
 
    get_contents, ReplaceContents, ChangeExecFlag, Diff3Merge
19
 
from bzrlib.tests import TestCaseWithTransport, TestCase
 
15
from bzrlib.transform import TreeTransform
 
16
from bzrlib.tests import TestCaseWithTransport, TestCase, TestSkipped
20
17
from bzrlib.workingtree import WorkingTree
21
18
 
22
19
 
23
 
class FalseTree(object):
24
 
    def __init__(self, realtree):
25
 
        self._realtree = realtree
26
 
        self.inventory = self
27
 
 
28
 
    def __getitem__(self, file_id):
29
 
        entry = self.make_inventory_entry(file_id)
30
 
        if entry is None:
31
 
            raise KeyError(file_id)
32
 
        return entry
33
 
        
34
 
    def make_inventory_entry(self, file_id):
35
 
        path = self._realtree.inventory_dict.get(file_id)
36
 
        if path is None:
37
 
            return None
38
 
        if path == "":
39
 
            return RootEntry(file_id)
40
 
        dir, name = os.path.split(path)
41
 
        kind = file_kind(self._realtree.abs_path(path))
42
 
        for parent_id, path in self._realtree.inventory_dict.iteritems():
43
 
            if path == dir:
44
 
                break
45
 
        if path != dir:
46
 
            raise Exception("Can't find parent for %s" % name)
47
 
        if kind not in ('directory', 'file', 'symlink'):
48
 
            raise ValueError('unknown kind %r' % kind)
49
 
        if kind == 'directory':
50
 
            return inventory.InventoryDirectory(file_id, name, parent_id)
51
 
        elif kind == 'file':
52
 
            return inventory.InventoryFile(file_id, name, parent_id)
53
 
        else:
54
 
            return inventory.InventoryLink(file_id, name, parent_id)
55
 
 
56
 
 
57
 
class MergeTree(object):
58
 
    def __init__(self, dir):
59
 
        self.dir = dir;
60
 
        os.mkdir(dir)
61
 
        self.inventory_dict = {'0': ""}
62
 
        self.inventory = FalseTree(self)
63
 
    
64
 
    def child_path(self, parent, name):
65
 
        return pathjoin(self.inventory_dict[parent], name)
66
 
 
67
 
    def add_file(self, id, parent, name, contents, mode):
68
 
        path = self.child_path(parent, name)
69
 
        full_path = self.abs_path(path)
70
 
        assert not os.path.exists(full_path)
71
 
        file(full_path, "wb").write(contents)
72
 
        os.chmod(self.abs_path(path), mode)
73
 
        self.inventory_dict[id] = path
74
 
 
75
 
    def add_symlink(self, id, parent, name, target):
76
 
        path = self.child_path(parent, name)
77
 
        full_path = self.abs_path(path)
78
 
        assert not os.path.exists(full_path)
79
 
        os.symlink(target, full_path)
80
 
        self.inventory_dict[id] = path
81
 
 
82
 
    def remove_file(self, id):
83
 
        os.unlink(self.full_path(id))
84
 
        del self.inventory_dict[id]
85
 
 
86
 
    def add_dir(self, id, parent, name, mode):
87
 
        path = self.child_path(parent, name)
88
 
        full_path = self.abs_path(path)
89
 
        assert not os.path.exists(full_path)
90
 
        os.mkdir(self.abs_path(path))
91
 
        os.chmod(self.abs_path(path), mode)
92
 
        self.inventory_dict[id] = path
93
 
 
94
 
    def abs_path(self, path):
95
 
        return pathjoin(self.dir, path)
96
 
 
97
 
    def full_path(self, id):
98
 
        try:
99
 
            tree_path = self.inventory_dict[id]
100
 
        except KeyError:
101
 
            return None
102
 
        return self.abs_path(tree_path)
103
 
 
104
 
    def readonly_path(self, id):
105
 
        return self.full_path(id)
106
 
 
107
 
    def __contains__(self, file_id):
108
 
        return file_id in self.inventory_dict
109
 
 
110
 
    def has_or_had_id(self, file_id):
111
 
        return file_id in self
112
 
 
113
 
    def get_file(self, file_id):
114
 
        path = self.full_path(file_id)
115
 
        return file(path, "rb")
116
 
 
117
 
    def id2path(self, file_id):
118
 
        return self.inventory_dict[file_id]
119
 
 
120
 
    def id2abspath(self, id):
121
 
        return self.full_path(id)
122
 
 
123
 
    def change_path(self, id, path):
124
 
        old_path = pathjoin(self.dir, self.inventory_dict[id])
125
 
        rename(old_path, self.abs_path(path))
126
 
        self.inventory_dict[id] = path
127
 
 
128
 
    def is_executable(self, file_id):
129
 
        mode = os.lstat(self.full_path(file_id)).st_mode
130
 
        return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
131
 
 
132
 
    def kind(self, file_id):
133
 
        return file_kind(self.full_path(file_id))
134
 
 
135
 
    def get_symlink_target(self, file_id):
136
 
        return os.readlink(self.full_path(file_id))
137
 
 
138
 
    def get_file_sha1(self, file_id):
139
 
        return sha_file(file(self.full_path(file_id), "rb"))
140
 
 
141
 
 
142
20
class MergeBuilder(object):
143
21
    def __init__(self):
144
 
        self.dir = mkdtemp(prefix="BaZing")
145
 
        self.base = MergeTree(pathjoin(self.dir, "base"))
146
 
        self.this = MergeTree(pathjoin(self.dir, "this"))
147
 
        self.other = MergeTree(pathjoin(self.dir, "other"))
148
 
        
149
 
        self.cset = changeset.Changeset()
150
 
        self.cset.add_entry(changeset.ChangesetEntry("0", 
151
 
                                                     changeset.NULL_ID, "./."))
 
22
        self.dir = mkdtemp(prefix="merge-test")
 
23
        def wt(name):
 
24
           path = pathjoin(self.dir, name)
 
25
           os.mkdir(path)
 
26
           wt = bzrlib.bzrdir.BzrDir.create_standalone_workingtree(path)
 
27
           tt = TreeTransform(wt)
 
28
           return wt, tt
 
29
        self.base, self.base_tt = wt('base') 
 
30
        self.this, self.this_tt = wt('this')
 
31
        self.other, self.other_tt = wt('other')
152
32
 
153
33
    def get_cset_path(self, parent, name):
154
34
        if name is None:
156
36
            return None
157
37
        return pathjoin(self.cset.entries[parent].path, name)
158
38
 
159
 
    def add_file(self, id, parent, name, contents, mode):
160
 
        self.base.add_file(id, parent, name, contents, mode)
161
 
        self.this.add_file(id, parent, name, contents, mode)
162
 
        self.other.add_file(id, parent, name, contents, mode)
163
 
        path = self.get_cset_path(parent, name)
164
 
        self.cset.add_entry(changeset.ChangesetEntry(id, parent, path))
 
39
    def add_file(self, id, parent, name, contents, executable):
 
40
        def new_file(tt):
 
41
            parent_id = tt.trans_id_file_id(parent)
 
42
            tt.new_file(name, parent_id, contents, id, executable)
 
43
        for tt in (self.this_tt, self.base_tt, self.other_tt):
 
44
            new_file(tt)
 
45
 
 
46
    def merge(self, merge_type=Merge3Merger):
 
47
        self.base_tt.apply()
 
48
        self.base.commit('base commit')
 
49
        for tt, wt in ((self.this_tt, self.this), (self.other_tt, self.other)):
 
50
            wt.branch.pull(self.base.branch)
 
51
            tt.apply()
 
52
            wt.commit('branch commit')
 
53
            assert len(wt.branch.revision_history()) == 2
 
54
        self.this.branch.fetch(self.other.branch)
 
55
        other_basis = self.other.branch.basis_tree()
 
56
        merger = merge_type(self.this, self.this, self.base, other_basis)
 
57
        return merger.cooked_conflicts
 
58
 
 
59
    def list_transforms(self):
 
60
        return [self.this_tt, self.base_tt, self.other_tt]
 
61
 
 
62
    def selected_transforms(self, this, base, other):
 
63
        pairs = [(this, self.this_tt), (base, self.base_tt), 
 
64
                 (other, self.other_tt)]
 
65
        return [(v, tt) for (v, tt) in pairs if v is not None]
165
66
 
166
67
    def add_symlink(self, id, parent, name, contents):
167
 
        self.base.add_symlink(id, parent, name, contents)
168
 
        self.this.add_symlink(id, parent, name, contents)
169
 
        self.other.add_symlink(id, parent, name, contents)
170
 
        path = self.get_cset_path(parent, name)
171
 
        self.cset.add_entry(changeset.ChangesetEntry(id, parent, path))
172
 
 
173
 
    def remove_file(self, id, base=False, this=False, other=False):
174
 
        for option, tree in ((base, self.base), (this, self.this), 
175
 
                             (other, self.other)):
176
 
            if option:
177
 
                tree.remove_file(id)
178
 
            if other or base:
179
 
                change = self.cset.entries[id].contents_change
180
 
                if change is None:
181
 
                    change = changeset.ReplaceContents(None, None)
182
 
                    self.cset.entries[id].contents_change = change
183
 
                    def create_file(tree):
184
 
                        return changeset.TreeFileCreate(tree, id)
185
 
                    if not other:
186
 
                        change.new_contents = create_file(self.other)
187
 
                    if not base:
188
 
                        change.old_contents = create_file(self.base)
189
 
                else:
190
 
                    assert isinstance(change, changeset.ReplaceContents)
191
 
                if other:
192
 
                    change.new_contents=None
193
 
                if base:
194
 
                    change.old_contents=None
195
 
                if change.old_contents is None and change.new_contents is None:
196
 
                    change = None
197
 
 
198
 
    def add_dir(self, id, parent, name, mode):
199
 
        path = self.get_cset_path(parent, name)
200
 
        self.base.add_dir(id, parent, name, mode)
201
 
        self.cset.add_entry(changeset.ChangesetEntry(id, parent, path))
202
 
        self.this.add_dir(id, parent, name, mode)
203
 
        self.other.add_dir(id, parent, name, mode)
 
68
        for tt in self.list_transforms():
 
69
            parent_id = tt.trans_id_file_id(parent)
 
70
            tt.new_symlink(name, parent_id, contents, id)
 
71
 
 
72
    def remove_file(self, file_id, base=False, this=False, other=False):
 
73
        for option, tt in self.selected_transforms(this, base, other):
 
74
            if option is True:
 
75
                trans_id = tt.trans_id_file_id(file_id)
 
76
                tt.cancel_creation(trans_id)
 
77
                tt.cancel_versioning(trans_id)
 
78
                tt.set_executability(None, trans_id)
 
79
 
 
80
    def add_dir(self, file_id, parent, name):
 
81
        for tt in self.list_transforms():
 
82
            parent_id = tt.trans_id_file_id(parent)
 
83
            tt.new_directory(name, parent_id, file_id)
204
84
 
205
85
    def change_name(self, id, base=None, this=None, other=None):
206
 
        if base is not None:
207
 
            self.change_name_tree(id, self.base, base)
208
 
            self.cset.entries[id].name = base
209
 
 
210
 
        if this is not None:
211
 
            self.change_name_tree(id, self.this, this)
212
 
 
213
 
        if other is not None:
214
 
            self.change_name_tree(id, self.other, other)
215
 
            self.cset.entries[id].new_name = other
216
 
 
217
 
    def change_parent(self, id, base=None, this=None, other=None):
218
 
        if base is not None:
219
 
            self.change_parent_tree(id, self.base, base)
220
 
            self.cset.entries[id].parent = base
221
 
            self.cset.entries[id].dir = self.cset.entries[base].path
222
 
 
223
 
        if this is not None:
224
 
            self.change_parent_tree(id, self.this, this)
225
 
 
226
 
        if other is not None:
227
 
            self.change_parent_tree(id, self.other, other)
228
 
            self.cset.entries[id].new_parent = other
229
 
            self.cset.entries[id].new_dir = \
230
 
                self.cset.entries[other].new_path
231
 
 
232
 
    def change_contents(self, id, base=None, this=None, other=None):
233
 
        if base is not None:
234
 
            self.change_contents_tree(id, self.base, base)
235
 
 
236
 
        if this is not None:
237
 
            self.change_contents_tree(id, self.this, this)
238
 
 
239
 
        if other is not None:
240
 
            self.change_contents_tree(id, self.other, other)
241
 
 
242
 
        if base is not None or other is not None:
243
 
            old_contents = file(self.base.full_path(id)).read()
244
 
            new_contents = file(self.other.full_path(id)).read()
245
 
            contents = changeset.ReplaceFileContents(self.base, self.other, id)
246
 
            self.cset.entries[id].contents_change = contents
 
86
        for val, tt in ((base, self.base_tt), (this, self.this_tt), 
 
87
                        (other, self.other_tt)):
 
88
            if val is None:
 
89
                continue
 
90
            trans_id = tt.trans_id_file_id(id)
 
91
            parent_id = tt.final_parent(trans_id)
 
92
            tt.adjust_path(val, parent_id, trans_id)
 
93
 
 
94
    def change_parent(self, file_id, base=None, this=None, other=None):
 
95
        for parent, tt in self.selected_transforms(this, base, other):
 
96
            trans_id  = tt.trans_id_file_id(file_id)
 
97
            parent_id = tt.trans_id_file_id(parent)
 
98
            tt.adjust_path(tt.final_name(trans_id), parent_id, trans_id)
 
99
 
 
100
    def change_contents(self, file_id, base=None, this=None, other=None):
 
101
        for contents, tt in self.selected_transforms(this, base, other):
 
102
            trans_id = tt.trans_id_file_id(file_id)
 
103
            tt.cancel_creation(trans_id)
 
104
            tt.create_file(contents, trans_id)
247
105
 
248
106
    def change_target(self, id, base=None, this=None, other=None):
249
 
        if base is not None:
250
 
            self.change_target_tree(id, self.base, base)
251
 
 
252
 
        if this is not None:
253
 
            self.change_target_tree(id, self.this, this)
254
 
 
255
 
        if other is not None:
256
 
            self.change_target_tree(id, self.other, other)
257
 
 
258
 
        if base is not None or other is not None:
259
 
            old_contents = get_contents(self.base, id)
260
 
            new_contents = get_contents(self.other, id)
261
 
            contents = ReplaceContents(old_contents, new_contents)
262
 
            self.cset.entries[id].contents_change = contents
 
107
        for target, tt in self.selected_transforms(this, base, other):
 
108
            trans_id = tt.trans_id_file_id(id)
 
109
            tt.cancel_creation(trans_id)
 
110
            tt.create_symlink(target, trans_id)
263
111
 
264
112
    def change_perms(self, id, base=None, this=None, other=None):
265
 
        if base is not None:
266
 
            self.change_perms_tree(id, self.base, base)
267
 
 
268
 
        if this is not None:
269
 
            self.change_perms_tree(id, self.this, this)
270
 
 
271
 
        if other is not None:
272
 
            self.change_perms_tree(id, self.other, other)
273
 
 
274
 
        if base is not None or other is not None:
275
 
            old_exec = self.base.is_executable(id)
276
 
            new_exec = self.other.is_executable(id)
277
 
            metadata = changeset.ChangeExecFlag(old_exec, new_exec)
278
 
            self.cset.entries[id].metadata_change = metadata
279
 
 
280
 
    def change_name_tree(self, id, tree, name):
281
 
        new_path = tree.child_path(self.cset.entries[id].parent, name)
282
 
        tree.change_path(id, new_path)
283
 
 
284
 
    def change_parent_tree(self, id, tree, parent):
285
 
        new_path = tree.child_path(parent, self.cset.entries[id].name)
286
 
        tree.change_path(id, new_path)
287
 
 
288
 
    def change_contents_tree(self, id, tree, contents):
289
 
        path = tree.full_path(id)
290
 
        mode = os.stat(path).st_mode
291
 
        file(path, "w").write(contents)
292
 
        os.chmod(path, mode)
293
 
 
294
 
    def change_target_tree(self, id, tree, target):
295
 
        path = tree.full_path(id)
296
 
        os.unlink(path)
297
 
        os.symlink(target, path)
 
113
        for executability, tt in self.selected_transforms(this, base, other):
 
114
            trans_id = tt.trans_id_file_id(id)
 
115
            tt.set_executability(None, trans_id)
 
116
            tt.set_executability(executability, trans_id)
298
117
 
299
118
    def change_perms_tree(self, id, tree, mode):
300
119
        os.chmod(tree.full_path(id), mode)
359
178
    def test_change_name(self):
360
179
        """Test renames"""
361
180
        builder = MergeBuilder()
362
 
        builder.add_file("1", "0", "name1", "hello1", 0755)
 
181
        builder.add_file("1", "TREE_ROOT", "name1", "hello1", True)
363
182
        builder.change_name("1", other="name2")
364
 
        builder.add_file("2", "0", "name3", "hello2", 0755)
 
183
        builder.add_file("2", "TREE_ROOT", "name3", "hello2", True)
365
184
        builder.change_name("2", base="name4")
366
 
        builder.add_file("3", "0", "name5", "hello3", 0755)
 
185
        builder.add_file("3", "TREE_ROOT", "name5", "hello3", True)
367
186
        builder.change_name("3", this="name6")
368
 
        cset = builder.merge_changeset(ApplyMerge3)
369
 
        self.failUnless(cset.entries["2"].is_boring())
370
 
        self.assertEqual(cset.entries["1"].name, "name1")
371
 
        self.assertEqual(cset.entries["1"].new_name, "name2")
372
 
        self.failUnless(cset.entries["3"].is_boring())
373
 
        for tree in (builder.this, builder.other, builder.base):
374
 
            self.assertNotEqual(tree.dir, builder.dir)
375
 
            self.assertStartsWith(tree.dir, builder.dir)
376
 
            for path in tree.inventory_dict.itervalues():
377
 
                fullpath = tree.abs_path(path)
378
 
                self.assertStartsWith(fullpath, tree.dir)
379
 
                self.failIf(path.startswith(tree.dir))
380
 
                self.failUnless(os.path.lexists(fullpath))
381
 
        builder.apply_changeset(cset)
 
187
        builder.merge()
382
188
        builder.cleanup()
383
189
        builder = MergeBuilder()
384
 
        builder.add_file("1", "0", "name1", "hello1", 0644)
 
190
        builder.add_file("1", "TREE_ROOT", "name1", "hello1", False)
385
191
        builder.change_name("1", other="name2", this="name3")
386
 
        self.assertRaises(changeset.RenameConflict, 
387
 
                          builder.merge_changeset, ApplyMerge3)
 
192
        conflicts = builder.merge()
 
193
        self.assertEqual(conflicts, [('path conflict', '1', 'name3', 'name2')])
388
194
        builder.cleanup()
389
195
        
390
196
    def test_file_moves(self):
391
197
        """Test moves"""
392
198
        builder = MergeBuilder()
393
 
        builder.add_dir("1", "0", "dir1", 0755)
394
 
        builder.add_dir("2", "0", "dir2", 0755)
395
 
        builder.add_file("3", "1", "file1", "hello1", 0644)
396
 
        builder.add_file("4", "1", "file2", "hello2", 0644)
397
 
        builder.add_file("5", "1", "file3", "hello3", 0644)
 
199
        builder.add_dir("1", "TREE_ROOT", "dir1")
 
200
        builder.add_dir("2", "TREE_ROOT", "dir2")
 
201
        builder.add_file("3", "1", "file1", "hello1", True)
 
202
        builder.add_file("4", "1", "file2", "hello2", True)
 
203
        builder.add_file("5", "1", "file3", "hello3", True)
398
204
        builder.change_parent("3", other="2")
399
 
        self.assert_(Inventory(builder.other.inventory_dict).get_parent("3") == "2")
400
205
        builder.change_parent("4", this="2")
401
 
        self.assert_(Inventory(builder.this.inventory_dict).get_parent("4") == "2")
402
206
        builder.change_parent("5", base="2")
403
 
        self.assert_(Inventory(builder.base.inventory_dict).get_parent("5") == "2")
404
 
        cset = builder.merge_changeset(ApplyMerge3)
405
 
        for id in ("1", "2", "4", "5"):
406
 
            self.assert_(cset.entries[id].is_boring())
407
 
        self.assert_(cset.entries["3"].parent == "1")
408
 
        self.assert_(cset.entries["3"].new_parent == "2")
409
 
        builder.apply_changeset(cset)
 
207
        builder.merge()
410
208
        builder.cleanup()
411
209
 
412
210
        builder = MergeBuilder()
413
 
        builder.add_dir("1", "0", "dir1", 0755)
414
 
        builder.add_dir("2", "0", "dir2", 0755)
415
 
        builder.add_dir("3", "0", "dir3", 0755)
416
 
        builder.add_file("4", "1", "file1", "hello1", 0644)
 
211
        builder.add_dir("1", "TREE_ROOT", "dir1")
 
212
        builder.add_dir("2", "TREE_ROOT", "dir2")
 
213
        builder.add_dir("3", "TREE_ROOT", "dir3")
 
214
        builder.add_file("4", "1", "file1", "hello1", False)
417
215
        builder.change_parent("4", other="2", this="3")
418
 
        self.assertRaises(changeset.MoveConflict, 
419
 
                          builder.merge_changeset, ApplyMerge3)
 
216
        conflicts = builder.merge()
 
217
        path2 = pathjoin('dir2', 'file1')
 
218
        path3 = pathjoin('dir3', 'file1')
 
219
        self.assertEqual(conflicts, [('path conflict', '4', path3, path2)])
420
220
        builder.cleanup()
421
221
 
422
222
    def test_contents_merge(self):
423
223
        """Test merge3 merging"""
424
 
        self.do_contents_test(ApplyMerge3)
 
224
        self.do_contents_test(Merge3Merger)
425
225
 
426
226
    def test_contents_merge2(self):
427
227
        """Test diff3 merging"""
428
 
        self.do_contents_test(changeset.Diff3Merge)
 
228
        try:
 
229
            self.do_contents_test(Diff3Merger)
 
230
        except NoDiff3:
 
231
            raise TestSkipped("diff3 not available")
429
232
 
430
233
    def test_contents_merge3(self):
431
234
        """Test diff3 merging"""
432
 
        def backup_merge(file_id, base, other):
433
 
            return BackupBeforeChange(ApplyMerge3(file_id, base, other))
434
 
        builder = self.contents_test_success(backup_merge)
435
 
        def backup_exists(file_id):
436
 
            return os.path.exists(builder.this.full_path(file_id)+"~")
437
 
        self.assert_(backup_exists("1"))
438
 
        self.assert_(backup_exists("2"))
439
 
        self.assert_(not backup_exists("3"))
440
 
        builder.cleanup()
 
235
        self.do_contents_test(WeaveMerger)
441
236
 
442
237
    def do_contents_test(self, merge_factory):
443
238
        """Test merging with specified ContentsChange factory"""
446
241
        self.contents_test_conflicts(merge_factory)
447
242
 
448
243
    def contents_test_success(self, merge_factory):
449
 
        from inspect import isclass
450
244
        builder = MergeBuilder()
451
 
        builder.add_file("1", "0", "name1", "text1", 0755)
 
245
        builder.add_file("1", "TREE_ROOT", "name1", "text1", True)
452
246
        builder.change_contents("1", other="text4")
453
 
        builder.add_file("2", "0", "name3", "text2", 0655)
 
247
        builder.add_file("2", "TREE_ROOT", "name3", "text2", False)
454
248
        builder.change_contents("2", base="text5")
455
 
        builder.add_file("3", "0", "name5", "text3", 0744)
456
 
        builder.add_file("4", "0", "name6", "text4", 0744)
 
249
        builder.add_file("3", "TREE_ROOT", "name5", "text3", True)
 
250
        builder.add_file("4", "TREE_ROOT", "name6", "text4", True)
457
251
        builder.remove_file("4", base=True)
458
 
        self.assert_(not builder.cset.entries["4"].is_boring())
459
 
        builder.change_contents("3", this="text6")
460
 
        cset = builder.merge_changeset(merge_factory)
461
 
        self.assert_(cset.entries["1"].contents_change is not None)
462
 
        if isclass(merge_factory):
463
 
            self.assert_(isinstance(cset.entries["1"].contents_change,
464
 
                          merge_factory))
465
 
            self.assert_(isinstance(cset.entries["2"].contents_change,
466
 
                          merge_factory))
467
 
        self.assert_(cset.entries["3"].is_boring())
468
 
        self.assert_(cset.entries["4"].is_boring())
469
 
        builder.apply_changeset(cset)
470
 
        self.assert_(file(builder.this.full_path("1"), "rb").read() == "text4" )
471
 
        self.assert_(file(builder.this.full_path("2"), "rb").read() == "text2" )
472
 
        if sys.platform != "win32":
473
 
            self.assert_(os.stat(builder.this.full_path("1")).st_mode &0777 == 0755)
474
 
            self.assert_(os.stat(builder.this.full_path("2")).st_mode &0777 == 0655)
475
 
            self.assert_(os.stat(builder.this.full_path("3")).st_mode &0777 == 0744)
 
252
        builder.merge()
 
253
        self.assertEqual(builder.this.get_file("1").read(), "text4" )
 
254
        self.assertEqual(builder.this.get_file("2").read(), "text2" )
 
255
        self.assertIs(builder.this.is_executable("1"), True)
 
256
        self.assertIs(builder.this.is_executable("2"), False)
 
257
        self.assertIs(builder.this.is_executable("3"), True)
476
258
        return builder
477
259
 
478
260
    def contents_test_conflicts(self, merge_factory):
479
261
        builder = MergeBuilder()
480
 
        builder.add_file("1", "0", "name1", "text1", 0755)
 
262
        builder.add_file("1", "TREE_ROOT", "name1", "text1", True)
481
263
        builder.change_contents("1", other="text4", this="text3")
482
 
        cset = builder.merge_changeset(merge_factory)
483
 
        self.assertRaises(changeset.MergeConflict, builder.apply_changeset,
484
 
                          cset)
 
264
        conflicts = builder.merge(merge_factory)
 
265
        self.assertEqual(conflicts, [('text conflict', '1', 'name1')])
485
266
        builder.cleanup()
486
267
 
487
268
    def test_symlink_conflicts(self):
488
269
        if sys.platform != "win32":
489
270
            builder = MergeBuilder()
490
 
            builder.add_symlink("2", "0", "name2", "target1")
 
271
            builder.add_symlink("2", "TREE_ROOT", "name2", "target1")
491
272
            builder.change_target("2", other="target4", base="text3")
492
 
            self.assertRaises(changeset.ThreewayContentsConflict,
493
 
                              builder.merge_changeset, ApplyMerge3)
 
273
            conflicts = builder.merge()
 
274
            self.assertEqual(conflicts, [('contents conflict', '2', 'name2')])
494
275
            builder.cleanup()
495
276
 
496
277
    def test_symlink_merge(self):
497
278
        if sys.platform != "win32":
498
279
            builder = MergeBuilder()
499
 
            builder.add_symlink("1", "0", "name1", "target1")
500
 
            builder.add_symlink("2", "0", "name2", "target1")
501
 
            builder.add_symlink("3", "0", "name3", "target1")
 
280
            builder.add_symlink("1", "TREE_ROOT", "name1", "target1")
 
281
            builder.add_symlink("2", "TREE_ROOT", "name2", "target1")
 
282
            builder.add_symlink("3", "TREE_ROOT", "name3", "target1")
502
283
            builder.change_target("1", this="target2")
503
284
            builder.change_target("2", base="target2")
504
285
            builder.change_target("3", other="target2")
505
 
            self.assertNotEqual(builder.cset.entries['2'].contents_change,
506
 
                                builder.cset.entries['3'].contents_change)
507
 
            cset = builder.merge_changeset(ApplyMerge3)
508
 
            builder.apply_changeset(cset)
 
286
            builder.merge()
509
287
            self.assertEqual(builder.this.get_symlink_target("1"), "target2")
510
288
            self.assertEqual(builder.this.get_symlink_target("2"), "target1")
511
289
            self.assertEqual(builder.this.get_symlink_target("3"), "target2")
512
290
            builder.cleanup()
513
291
 
 
292
    def test_no_passive_add(self):
 
293
        builder = MergeBuilder()
 
294
        builder.add_file("1", "TREE_ROOT", "name1", "text1", True)
 
295
        builder.remove_file("1", this=True)
 
296
        builder.merge()
 
297
        builder.cleanup()
 
298
 
514
299
    def test_perms_merge(self):
515
300
        builder = MergeBuilder()
516
 
        builder.add_file("1", "0", "name1", "text1", 0755)
517
 
        builder.change_perms("1", other=0644)
518
 
        builder.add_file("2", "0", "name2", "text2", 0755)
519
 
        builder.change_perms("2", base=0644)
520
 
        builder.add_file("3", "0", "name3", "text3", 0755)
521
 
        builder.change_perms("3", this=0644)
522
 
        cset = builder.merge_changeset(ApplyMerge3)
523
 
        self.assert_(cset.entries["1"].metadata_change is not None)
524
 
        self.assert_(isinstance(cset.entries["1"].metadata_change, ExecFlagMerge))
525
 
        self.assert_(isinstance(cset.entries["2"].metadata_change, ExecFlagMerge))
526
 
        self.assert_(cset.entries["3"].is_boring())
527
 
        builder.apply_changeset(cset)
528
 
        if sys.platform != "win32":
529
 
            self.assert_(os.lstat(builder.this.full_path("1")).st_mode &0100 == 0000)
530
 
            self.assert_(os.lstat(builder.this.full_path("2")).st_mode &0100 == 0100)
531
 
            self.assert_(os.lstat(builder.this.full_path("3")).st_mode &0100 == 0000)
 
301
        builder.add_file("1", "TREE_ROOT", "name1", "text1", True)
 
302
        builder.change_perms("1", other=False)
 
303
        builder.add_file("2", "TREE_ROOT", "name2", "text2", True)
 
304
        builder.change_perms("2", base=False)
 
305
        builder.add_file("3", "TREE_ROOT", "name3", "text3", True)
 
306
        builder.change_perms("3", this=False)
 
307
        builder.add_file('4', 'TREE_ROOT', 'name4', 'text4', False)
 
308
        builder.change_perms('4', this=True)
 
309
        builder.remove_file('4', base=True)
 
310
        builder.merge()
 
311
        self.assertIs(builder.this.is_executable("1"), False)
 
312
        self.assertIs(builder.this.is_executable("2"), True)
 
313
        self.assertIs(builder.this.is_executable("3"), False)
532
314
        builder.cleanup();
533
315
 
534
316
    def test_new_suffix(self):
535
 
        for merge_type in ApplyMerge3, Diff3Merge:
536
 
            builder = MergeBuilder()
537
 
            builder.add_file("1", "0", "name1", "text1", 0755)
538
 
            builder.change_contents("1", other="text3")
539
 
            builder.add_file("2", "0", "name1.new", "text2", 0777)
540
 
            cset = builder.merge_changeset(ApplyMerge3)
541
 
            os.lstat(builder.this.full_path("2"))
542
 
            builder.apply_changeset(cset)
543
 
            os.lstat(builder.this.full_path("2"))
544
 
            builder.cleanup()
 
317
        builder = MergeBuilder()
 
318
        builder.add_file("1", "TREE_ROOT", "name1", "text1", True)
 
319
        builder.change_contents("1", other="text3")
 
320
        builder.add_file("2", "TREE_ROOT", "name1.new", "text2", True)
 
321
        builder.merge()
 
322
        os.lstat(builder.this.id2abspath("2"))
 
323
        builder.cleanup()
545
324
 
546
325
 
547
326
class FunctionalMergeTest(TestCaseWithTransport):
550
329
        """Test that merges in a star shape Just Work.""" 
551
330
        # John starts a branch
552
331
        self.build_tree(("original/", "original/file1", "original/file2"))
553
 
        tree = WorkingTree.create_standalone('original')
 
332
        tree = self.make_branch_and_tree('original')
554
333
        branch = tree.branch
555
334
        smart_add_tree(tree, ["original"])
556
335
        tree.commit("start branch.", verbose=False)
557
336
        # Mary branches it.
558
337
        self.build_tree(("mary/",))
559
 
        branch.clone("mary")
 
338
        branch.bzrdir.clone("mary")
560
339
        # Now John commits a change
561
340
        file = open("original/file1", "wt")
562
341
        file.write("John\n")
563
342
        file.close()
564
343
        tree.commit("change file1")
565
344
        # Mary does too
566
 
        mary_branch = Branch.open("mary")
 
345
        mary_tree = WorkingTree.open('mary')
 
346
        mary_branch = mary_tree.branch
567
347
        file = open("mary/file2", "wt")
568
348
        file.write("Mary\n")
569
349
        file.close()
570
 
        mary_branch.working_tree().commit("change file2")
 
350
        mary_tree.commit("change file2")
571
351
        # john should be able to merge with no conflicts.
572
 
        merge_type = ApplyMerge3
 
352
        merge_type = Merge3Merger
573
353
        base = [None, None]
574
354
        other = ("mary", -1)
575
355
        self.assertRaises(BzrCommandError, merge, other, base, check_clean=True,
576
 
                          merge_type=WeaveMerge, this_dir="original",
 
356
                          merge_type=WeaveMerger, this_dir="original",
577
357
                          show_base=True)
578
358
        merge(other, base, check_clean=True, merge_type=merge_type,
579
359
              this_dir="original")
582
362
 
583
363
    def test_conflicts(self):
584
364
        os.mkdir('a')
585
 
        wta = WorkingTree.create_standalone('a')
 
365
        wta = self.make_branch_and_tree('a')
586
366
        a = wta.branch
587
367
        file('a/file', 'wb').write('contents\n')
588
368
        wta.add('file')
589
369
        wta.commit('base revision', allow_pointless=False)
590
 
        b = a.clone('b')
 
370
        d_b = a.bzrdir.clone('b')
 
371
        b = d_b.open_branch()
591
372
        file('a/file', 'wb').write('other contents\n')
592
373
        wta.commit('other revision', allow_pointless=False)
593
374
        file('b/file', 'wb').write('this contents contents\n')
594
 
        b.working_tree().commit('this revision', allow_pointless=False)
 
375
        wtb = d_b.open_workingtree()
 
376
        wtb.commit('this revision', allow_pointless=False)
595
377
        self.assertEqual(merge(['a', -1], [None, None], this_dir='b'), 1)
596
378
        self.assert_(os.path.lexists('b/file.THIS'))
597
379
        self.assert_(os.path.lexists('b/file.BASE'))
598
380
        self.assert_(os.path.lexists('b/file.OTHER'))
599
381
        self.assertRaises(WorkingTreeNotRevision, merge, ['a', -1], 
600
382
                          [None, None], this_dir='b', check_clean=False,
601
 
                          merge_type=WeaveMerge)
602
 
        b.working_tree().revert([])
 
383
                          merge_type=WeaveMerger)
 
384
        wtb.revert([])
603
385
        os.unlink('b/file.THIS')
604
386
        os.unlink('b/file.OTHER')
605
387
        os.unlink('b/file.BASE')
606
388
        self.assertEqual(merge(['a', -1], [None, None], this_dir='b', 
607
 
                               check_clean=False, merge_type=WeaveMerge), 1)
 
389
                               check_clean=False, merge_type=WeaveMerger), 1)
608
390
        self.assert_(os.path.lexists('b/file'))
609
391
        self.assert_(os.path.lexists('b/file.THIS'))
610
392
        self.assert_(not os.path.lexists('b/file.BASE'))
653
435
        os.remove('a/file')
654
436
        wta.commit('removed file', allow_pointless=False)
655
437
        file('b/file', 'wb').write('changed contents\n')
656
 
        wtb = WorkingTree('b')
 
438
        wtb = WorkingTree.open('b')
657
439
        wtb.commit('changed file', allow_pointless=False)
658
440
        merge(['a', -1], ['a', 1], this_dir='b')
659
441
        self.failIf(os.path.lexists('b/file'))
665
447
        a_wt.add('file')
666
448
        a_wt.commit('r0')
667
449
        self.run_bzr('branch', 'a', 'b')
668
 
        b_wt = WorkingTree('b')
 
450
        b_wt = WorkingTree.open('b')
669
451
        os.chmod('b/file', 0755)
670
452
        os.remove('a/file')
671
453
        a_wt.commit('removed a')
679
461
        a_wt = self.make_branch_and_tree('a')
680
462
        file('a/un','wb').write('UN')
681
463
        file('a/deux','wb').write('DEUX')
682
 
        a_wt.add('un')
683
 
        a_wt.add('deux')
684
 
        a_wt.commit('r0')
 
464
        a_wt.add('un', 'un')
 
465
        a_wt.add('deux', 'deux')
 
466
        a_wt.commit('r0', rev_id='r0')
685
467
        self.run_bzr('branch', 'a', 'b')
686
 
        b_wt = WorkingTree('b')
 
468
        b_wt = WorkingTree.open('b')
687
469
        b_wt.rename_one('un','tmp')
688
470
        b_wt.rename_one('deux','un')
689
471
        b_wt.rename_one('tmp','deux')
690
 
        b_wt.commit('r1')
691
 
        merge(['b', -1],['b', 1],this_dir='a')
692
 
        self.assert_(os.path.exists('a/un'))
693
 
        self.assert_(os.path.exists('a/deux'))
 
472
        b_wt.commit('r1', rev_id='r1')
 
473
        self.assertEqual(0, merge(['b', -1], ['b', 1], this_dir='a'))
 
474
        self.failUnlessExists('a/un')
 
475
        self.failUnless('a/deux')
694
476
        self.assertFalse(os.path.exists('a/tmp'))
695
477
        self.assertEqual(file('a/un').read(),'DEUX')
696
478
        self.assertEqual(file('a/deux').read(),'UN')
701
483
        a_wt.add('file')
702
484
        a_wt.commit('r0')
703
485
        self.run_bzr('branch', 'a', 'b')
704
 
        b_wt = WorkingTree('b')
 
486
        b_wt = WorkingTree.open('b')
705
487
        os.remove('b/file')
706
488
        b_wt.commit('r1')
707
489
        file('b/file', 'wb').write('THAT')
730
512
        a_wt.add('foo')
731
513
        a_wt.commit('added foo')
732
514
        self.run_bzr('branch', 'a', 'b')
733
 
        b_wt = WorkingTree('b')
 
515
        b_wt = WorkingTree.open('b')
734
516
        b_wt.rename_one('foo', 'bar')
735
517
        file('b/foo', 'wb').write('B/FOO')
736
518
        b_wt.add('foo')
758
540
        a_wt.add('foo')
759
541
        a_wt.commit('added foo')
760
542
        self.run_bzr('branch', 'a', 'b')
761
 
        b_wt = WorkingTree('b')
 
543
        b_wt = WorkingTree.open('b')
762
544
        os.mkdir('b/bar')
763
545
        b_wt.add('bar')
764
546
        b_wt.rename_one('foo', 'bar/foo')
787
569
        a_wt.add('foo/bar')
788
570
        a_wt.commit('added foo/bar')
789
571
        self.run_bzr('branch', 'a', 'b')
790
 
        b_wt = WorkingTree('b')
 
572
        b_wt = WorkingTree.open('b')
791
573
        b_wt.rename_one('foo/bar', 'bar')
792
574
        os.rmdir('b/foo')
793
575
        b_wt.remove('foo')
816
598
        a_wt.add('bar')
817
599
        a_wt.commit('added foo and bar')
818
600
        self.run_bzr('branch', 'a', 'b')
819
 
        b_wt = WorkingTree('b')
 
601
        b_wt = WorkingTree.open('b')
820
602
        os.unlink('b/foo')
821
603
        b_wt.remove('foo')
822
604
        b_wt.rename_one('bar', 'foo')