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
23
class FalseTree(object):
24
def __init__(self, realtree):
25
self._realtree = realtree
28
def __getitem__(self, file_id):
29
entry = self.make_inventory_entry(file_id)
31
raise KeyError(file_id)
34
def make_inventory_entry(self, file_id):
35
path = self._realtree.inventory_dict.get(file_id)
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():
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)
52
return inventory.InventoryFile(file_id, name, parent_id)
54
return inventory.InventoryLink(file_id, name, parent_id)
57
class MergeTree(object):
58
def __init__(self, dir):
61
self.inventory_dict = {'0': ""}
62
self.inventory = FalseTree(self)
64
def child_path(self, parent, name):
65
return pathjoin(self.inventory_dict[parent], name)
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
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
82
def remove_file(self, id):
83
os.unlink(self.full_path(id))
84
del self.inventory_dict[id]
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
94
def abs_path(self, path):
95
return pathjoin(self.dir, path)
97
def full_path(self, id):
99
tree_path = self.inventory_dict[id]
102
return self.abs_path(tree_path)
104
def readonly_path(self, id):
105
return self.full_path(id)
107
def __contains__(self, file_id):
108
return file_id in self.inventory_dict
110
def has_or_had_id(self, file_id):
111
return file_id in self
113
def get_file(self, file_id):
114
path = self.full_path(file_id)
115
return file(path, "rb")
117
def id2path(self, file_id):
118
return self.inventory_dict[file_id]
120
def id2abspath(self, id):
121
return self.full_path(id)
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
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)
132
def kind(self, file_id):
133
return file_kind(self.full_path(file_id))
135
def get_symlink_target(self, file_id):
136
return os.readlink(self.full_path(file_id))
138
def get_file_sha1(self, file_id):
139
return sha_file(file(self.full_path(file_id), "rb"))
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"))
149
self.cset = changeset.Changeset()
150
self.cset.add_entry(changeset.ChangesetEntry("0",
151
changeset.NULL_ID, "./."))
22
self.dir = mkdtemp(prefix="merge-test")
24
path = pathjoin(self.dir, name)
26
wt = bzrlib.bzrdir.BzrDir.create_standalone_workingtree(path)
27
tt = TreeTransform(wt)
29
self.base, self.base_tt = wt('base')
30
self.this, self.this_tt = wt('this')
31
self.other, self.other_tt = wt('other')
153
33
def get_cset_path(self, parent, name):
157
37
return pathjoin(self.cset.entries[parent].path, name)
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):
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):
46
def merge(self, merge_type=Merge3Merger):
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)
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
59
def list_transforms(self):
60
return [self.this_tt, self.base_tt, self.other_tt]
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]
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))
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)):
179
change = self.cset.entries[id].contents_change
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)
186
change.new_contents = create_file(self.other)
188
change.old_contents = create_file(self.base)
190
assert isinstance(change, changeset.ReplaceContents)
192
change.new_contents=None
194
change.old_contents=None
195
if change.old_contents is None and change.new_contents is None:
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)
72
def remove_file(self, file_id, base=False, this=False, other=False):
73
for option, tt in self.selected_transforms(this, base, other):
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)
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)
205
85
def change_name(self, id, base=None, this=None, other=None):
207
self.change_name_tree(id, self.base, base)
208
self.cset.entries[id].name = base
211
self.change_name_tree(id, self.this, this)
213
if other is not None:
214
self.change_name_tree(id, self.other, other)
215
self.cset.entries[id].new_name = other
217
def change_parent(self, id, base=None, this=None, other=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
224
self.change_parent_tree(id, self.this, this)
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
232
def change_contents(self, id, base=None, this=None, other=None):
234
self.change_contents_tree(id, self.base, base)
237
self.change_contents_tree(id, self.this, this)
239
if other is not None:
240
self.change_contents_tree(id, self.other, other)
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)):
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)
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)
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)
248
106
def change_target(self, id, base=None, this=None, other=None):
250
self.change_target_tree(id, self.base, base)
253
self.change_target_tree(id, self.this, this)
255
if other is not None:
256
self.change_target_tree(id, self.other, other)
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)
264
112
def change_perms(self, id, base=None, this=None, other=None):
266
self.change_perms_tree(id, self.base, base)
269
self.change_perms_tree(id, self.this, this)
271
if other is not None:
272
self.change_perms_tree(id, self.other, other)
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
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)
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)
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)
294
def change_target_tree(self, id, tree, target):
295
path = tree.full_path(id)
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)
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)
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()
390
196
def test_file_moves(self):
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)
410
208
builder.cleanup()
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()
422
222
def test_contents_merge(self):
423
223
"""Test merge3 merging"""
424
self.do_contents_test(ApplyMerge3)
224
self.do_contents_test(Merge3Merger)
426
226
def test_contents_merge2(self):
427
227
"""Test diff3 merging"""
428
self.do_contents_test(changeset.Diff3Merge)
229
self.do_contents_test(Diff3Merger)
231
raise TestSkipped("diff3 not available")
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"))
235
self.do_contents_test(WeaveMerger)
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)
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,
465
self.assert_(isinstance(cset.entries["2"].contents_change,
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)
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)
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,
264
conflicts = builder.merge(merge_factory)
265
self.assertEqual(conflicts, [('text conflict', '1', 'name1')])
485
266
builder.cleanup()
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()
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)
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()
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)
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)
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();
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"))
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)
322
os.lstat(builder.this.id2abspath("2"))
547
326
class FunctionalMergeTest(TestCaseWithTransport):