/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
493 by Martin Pool
- Merge aaron's merge command
1
import changeset
2
from changeset import Inventory, apply_changeset, invert_dict
3
import os.path
974.1.8 by Aaron Bentley
Added default backups for merge-revert
4
from osutils import backup_file
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
5
from merge3 import Merge3
6
7
class ApplyMerge3:
8
    """Contents-change wrapper around merge3.Merge3"""
9
    def __init__(self, base_file, other_file):
10
        self.base_file = base_file
11
        self.other_file = other_file
12
 
13
    def __eq__(self, other):
14
        if not isinstance(other, ApplyMerge3):
15
            return False
16
        return (self.base_file == other.base_file and 
17
                self.other_file == other.other_file)
18
19
    def __ne__(self, other):
20
        return not (self == other)
21
22
23
    def apply(self, filename, conflict_handler, reverse=False):
24
        new_file = filename+".new" 
25
        if not reverse:
26
            base = self.base_file
27
            other = self.other_file
28
        else:
29
            base = self.other_file
30
            other = self.base_file
31
        m3 = Merge3(file(base, "rb").readlines(), 
32
                    file(filename, "rb").readlines(), 
33
                    file(other, "rb").readlines())
34
35
        new_conflicts = False
36
        output_file = file(new_file, "wb")
37
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
38
        for line in m3.merge_lines(name_a = "TREE", name_b = "MERGE-SOURCE", 
39
                       start_marker=start_marker):
40
            if line.startswith(start_marker):
41
                new_conflicts = True
42
                output_file.write(line.replace(start_marker, '<<<<<<<<'))
43
            else:
44
                output_file.write(line)
45
        output_file.close()
46
        if not new_conflicts:
47
            os.chmod(new_file, os.stat(filename).st_mode)
48
            os.rename(new_file, filename)
49
            return
50
        else:
51
            conflict_handler.merge_conflict(new_file, filename, base, other)
493 by Martin Pool
- Merge aaron's merge command
52
974.1.8 by Aaron Bentley
Added default backups for merge-revert
53
54
class BackupBeforeChange:
55
    """Contents-change wrapper to back up file first"""
56
    def __init__(self, contents_change):
57
        self.contents_change = contents_change
58
 
59
    def __eq__(self, other):
60
        if not isinstance(other, BackupBeforeChange):
61
            return False
62
        return (self.contents_change == other.contents_change)
63
64
    def __ne__(self, other):
65
        return not (self == other)
66
67
    def apply(self, filename, conflict_handler, reverse=False):
68
        backup_file(filename)
69
        self.contents_change.apply(filename, conflict_handler, reverse)
70
71
558 by Martin Pool
- All top-level classes inherit from object
72
class ThreewayInventory(object):
493 by Martin Pool
- Merge aaron's merge command
73
    def __init__(self, this_inventory, base_inventory, other_inventory):
74
        self.this = this_inventory
75
        self.base = base_inventory
76
        self.other = other_inventory
77
def invert_invent(inventory):
78
    invert_invent = {}
79
    for key, value in inventory.iteritems():
80
        invert_invent[value.id] = key
81
    return invert_invent
82
83
def make_inv(inventory):
84
    return Inventory(invert_invent(inventory))
85
        
86
87
def merge_flex(this, base, other, changeset_function, inventory_function,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
88
               conflict_handler, merge_factory):
493 by Martin Pool
- Merge aaron's merge command
89
    this_inventory = inventory_function(this)
90
    base_inventory = inventory_function(base)
91
    other_inventory = inventory_function(other)
92
    inventory = ThreewayInventory(make_inv(this_inventory),
93
                                  make_inv(base_inventory), 
94
                                  make_inv(other_inventory))
95
    cset = changeset_function(base, other, base_inventory, other_inventory)
96
    new_cset = make_merge_changeset(cset, inventory, this, base, other, 
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
97
                                    conflict_handler, merge_factory)
622 by Martin Pool
Updated merge patch from Aaron
98
    result = apply_changeset(new_cset, invert_invent(this_inventory),
99
                             this.root, conflict_handler, False)
100
    conflict_handler.finalize()
101
    return result
493 by Martin Pool
- Merge aaron's merge command
102
103
    
104
105
def make_merge_changeset(cset, inventory, this, base, other, 
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
106
                         conflict_handler, merge_factory):
493 by Martin Pool
- Merge aaron's merge command
107
    new_cset = changeset.Changeset()
108
    def get_this_contents(id):
109
        path = os.path.join(this.root, inventory.this.get_path(id))
110
        if os.path.isdir(path):
111
            return changeset.dir_create
112
        else:
113
            return changeset.FileCreate(file(path, "rb").read())
114
115
    for entry in cset.entries.itervalues():
116
        if entry.is_boring():
117
            new_cset.add_entry(entry)
118
        else:
850 by Martin Pool
- Merge merge updates from aaron
119
            new_entry = make_merged_entry(entry, inventory, conflict_handler)
909.1.4 by Aaron Bentley
Fixed conflict handling for missing merge targets
120
            new_contents = make_merged_contents(entry, this, base, other, 
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
121
                                                inventory, conflict_handler, 
122
                                                merge_factory)
850 by Martin Pool
- Merge merge updates from aaron
123
            new_entry.contents_change = new_contents
124
            new_entry.metadata_change = make_merged_metadata(entry, base, other)
125
            new_cset.add_entry(new_entry)
126
493 by Martin Pool
- Merge aaron's merge command
127
    return new_cset
128
850 by Martin Pool
- Merge merge updates from aaron
129
def make_merged_entry(entry, inventory, conflict_handler):
909.1.2 by aaron.bentley at utoronto
Fixed rename handling in merge
130
    from bzrlib.trace import mutter
493 by Martin Pool
- Merge aaron's merge command
131
    this_name = inventory.this.get_name(entry.id)
132
    this_parent = inventory.this.get_parent(entry.id)
133
    this_dir = inventory.this.get_dir(entry.id)
134
    if this_dir is None:
135
        this_dir = ""
136
137
    base_name = inventory.base.get_name(entry.id)
138
    base_parent = inventory.base.get_parent(entry.id)
139
    base_dir = inventory.base.get_dir(entry.id)
140
    if base_dir is None:
141
        base_dir = ""
142
    other_name = inventory.other.get_name(entry.id)
143
    other_parent = inventory.other.get_parent(entry.id)
144
    other_dir = inventory.base.get_dir(entry.id)
145
    if other_dir is None:
146
        other_dir = ""
909.1.2 by aaron.bentley at utoronto
Fixed rename handling in merge
147
    mutter("Dirs: this, base, other %r %r %r" % (this_dir, base_dir, other_dir))
148
    mutter("Names: this, base, other %r %r %r" % (this_name, base_name, other_name))
493 by Martin Pool
- Merge aaron's merge command
149
    if base_name == other_name:
150
        old_name = this_name
151
        new_name = this_name
152
    else:
153
        if this_name != base_name and this_name != other_name:
154
            conflict_handler.rename_conflict(entry.id, this_name, base_name,
155
                                             other_name)
156
        else:
157
            old_name = this_name
158
            new_name = other_name
159
160
    if base_parent == other_parent:
161
        old_parent = this_parent
162
        new_parent = this_parent
163
        old_dir = this_dir
164
        new_dir = this_dir
165
    else:
166
        if this_parent != base_parent and this_parent != other_parent:
167
            conflict_handler.move_conflict(entry.id, inventory)
168
        else:
169
            old_parent = this_parent
170
            old_dir = this_dir
171
            new_parent = other_parent
172
            new_dir = other_dir
850 by Martin Pool
- Merge merge updates from aaron
173
    if old_name is not None and old_parent is not None:
174
        old_path = os.path.join(old_dir, old_name)
175
    else:
176
        old_path = None
909.1.2 by aaron.bentley at utoronto
Fixed rename handling in merge
177
    new_entry = changeset.ChangesetEntry(entry.id, old_parent, old_path)
850 by Martin Pool
- Merge merge updates from aaron
178
    if new_name is not None and new_parent is not None:
493 by Martin Pool
- Merge aaron's merge command
179
        new_entry.new_path = os.path.join(new_dir, new_name)
180
    else:
181
        new_entry.new_path = None
182
    new_entry.new_parent = new_parent
909.1.2 by aaron.bentley at utoronto
Fixed rename handling in merge
183
    mutter(repr(new_entry))
850 by Martin Pool
- Merge merge updates from aaron
184
    return new_entry
185
186
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
187
def make_merged_contents(entry, this, base, other, inventory, conflict_handler,
188
                         merge_factory):
850 by Martin Pool
- Merge merge updates from aaron
189
    contents = entry.contents_change
190
    if contents is None:
191
        return None
192
    this_path = this.readonly_path(entry.id)
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
193
    def make_merge():
850 by Martin Pool
- Merge merge updates from aaron
194
        if this_path is None:
195
            return conflict_handler.missing_for_merge(entry.id, inventory)
196
        base_path = base.readonly_path(entry.id)
197
        other_path = other.readonly_path(entry.id)    
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
198
        return merge_factory(base_path, other_path)
850 by Martin Pool
- Merge merge updates from aaron
199
200
    if isinstance(contents, changeset.PatchApply):
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
201
        return make_merge()
850 by Martin Pool
- Merge merge updates from aaron
202
    if isinstance(contents, changeset.ReplaceContents):
203
        if contents.old_contents is None and contents.new_contents is None:
204
            return None
205
        if contents.new_contents is None:
206
            if this_path is not None and os.path.exists(this_path):
207
                return contents
208
            else:
209
                return None
210
        elif contents.old_contents is None:
211
            if this_path is None or not os.path.exists(this_path):
212
                return contents
213
            else:
214
                this_contents = file(this_path, "rb").read()
215
                if this_contents == contents.new_contents:
216
                    return None
217
                else:
218
                    other_path = other.readonly_path(entry.id)    
219
                    conflict_handler.new_contents_conflict(this_path, 
220
                                                           other_path)
221
        elif isinstance(contents.old_contents, changeset.FileCreate) and \
222
            isinstance(contents.new_contents, changeset.FileCreate):
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
223
            return make_merge()
850 by Martin Pool
- Merge merge updates from aaron
224
        else:
225
            raise Exception("Unhandled merge scenario")
226
227
def make_merged_metadata(entry, base, other):
228
    if entry.metadata_change is not None:
229
        base_path = base.readonly_path(entry.id)
230
        other_path = other.readonly_path(entry.id)    
231
        return PermissionsMerge(base_path, other_path)
493 by Martin Pool
- Merge aaron's merge command
232
    
233
558 by Martin Pool
- All top-level classes inherit from object
234
class PermissionsMerge(object):
493 by Martin Pool
- Merge aaron's merge command
235
    def __init__(self, base_path, other_path):
236
        self.base_path = base_path
237
        self.other_path = other_path
238
239
    def apply(self, filename, conflict_handler, reverse=False):
240
        if not reverse:
241
            base = self.base_path
242
            other = self.other_path
243
        else:
244
            base = self.other_path
245
            other = self.base_path
246
        base_stat = os.stat(base).st_mode
247
        other_stat = os.stat(other).st_mode
248
        this_stat = os.stat(filename).st_mode
249
        if base_stat &0777 == other_stat &0777:
250
            return
251
        elif this_stat &0777 == other_stat &0777:
252
            return
253
        elif this_stat &0777 == base_stat &0777:
254
            os.chmod(filename, other_stat)
255
        else:
256
            conflict_handler.permission_conflict(filename, base, other)
257
258
259
import unittest
260
import tempfile
261
import shutil
558 by Martin Pool
- All top-level classes inherit from object
262
class MergeTree(object):
493 by Martin Pool
- Merge aaron's merge command
263
    def __init__(self, dir):
264
        self.dir = dir;
265
        os.mkdir(dir)
266
        self.inventory = {'0': ""}
267
    
268
    def child_path(self, parent, name):
269
        return os.path.join(self.inventory[parent], name)
270
271
    def add_file(self, id, parent, name, contents, mode):
272
        path = self.child_path(parent, name)
273
        full_path = self.abs_path(path)
274
        assert not os.path.exists(full_path)
275
        file(full_path, "wb").write(contents)
276
        os.chmod(self.abs_path(path), mode)
277
        self.inventory[id] = path
278
909.1.4 by Aaron Bentley
Fixed conflict handling for missing merge targets
279
    def remove_file(self, id):
280
        os.unlink(self.full_path(id))
281
        del self.inventory[id]
282
493 by Martin Pool
- Merge aaron's merge command
283
    def add_dir(self, id, parent, name, mode):
284
        path = self.child_path(parent, name)
285
        full_path = self.abs_path(path)
286
        assert not os.path.exists(full_path)
287
        os.mkdir(self.abs_path(path))
288
        os.chmod(self.abs_path(path), mode)
289
        self.inventory[id] = path
290
291
    def abs_path(self, path):
292
        return os.path.join(self.dir, path)
293
294
    def full_path(self, id):
909.1.4 by Aaron Bentley
Fixed conflict handling for missing merge targets
295
        try:
296
            tree_path = self.inventory[id]
297
        except KeyError:
298
            return None
299
        return self.abs_path(tree_path)
493 by Martin Pool
- Merge aaron's merge command
300
850 by Martin Pool
- Merge merge updates from aaron
301
    def readonly_path(self, id):
302
        return self.full_path(id)
303
493 by Martin Pool
- Merge aaron's merge command
304
    def change_path(self, id, path):
305
        new = os.path.join(self.dir, self.inventory[id])
306
        os.rename(self.abs_path(self.inventory[id]), self.abs_path(path))
307
        self.inventory[id] = path
308
558 by Martin Pool
- All top-level classes inherit from object
309
class MergeBuilder(object):
493 by Martin Pool
- Merge aaron's merge command
310
    def __init__(self):
311
        self.dir = tempfile.mkdtemp(prefix="BaZing")
312
        self.base = MergeTree(os.path.join(self.dir, "base"))
313
        self.this = MergeTree(os.path.join(self.dir, "this"))
314
        self.other = MergeTree(os.path.join(self.dir, "other"))
315
        
316
        self.cset = changeset.Changeset()
317
        self.cset.add_entry(changeset.ChangesetEntry("0", 
318
                                                     changeset.NULL_ID, "./."))
319
    def get_cset_path(self, parent, name):
320
        if name is None:
321
            assert (parent is None)
322
            return None
323
        return os.path.join(self.cset.entries[parent].path, name)
324
325
    def add_file(self, id, parent, name, contents, mode):
326
        self.base.add_file(id, parent, name, contents, mode)
327
        self.this.add_file(id, parent, name, contents, mode)
328
        self.other.add_file(id, parent, name, contents, mode)
329
        path = self.get_cset_path(parent, name)
330
        self.cset.add_entry(changeset.ChangesetEntry(id, parent, path))
331
909.1.4 by Aaron Bentley
Fixed conflict handling for missing merge targets
332
    def remove_file(self, id, base=False, this=False, other=False):
333
        for option, tree in ((base, self.base), (this, self.this), 
334
                             (other, self.other)):
335
            if option:
336
                tree.remove_file(id)
337
            if other or base:
338
                change = self.cset.entries[id].contents_change
339
                assert isinstance(change, changeset.ReplaceContents)
340
                if other:
341
                    change.new_contents=None
342
                if base:
343
                    change.old_contents=None
344
                if change.old_contents is None and change.new_contents is None:
345
                    change = None
346
347
493 by Martin Pool
- Merge aaron's merge command
348
    def add_dir(self, id, parent, name, mode):
349
        path = self.get_cset_path(parent, name)
350
        self.base.add_dir(id, parent, name, mode)
351
        self.cset.add_entry(changeset.ChangesetEntry(id, parent, path))
352
        self.this.add_dir(id, parent, name, mode)
353
        self.other.add_dir(id, parent, name, mode)
354
355
356
    def change_name(self, id, base=None, this=None, other=None):
357
        if base is not None:
358
            self.change_name_tree(id, self.base, base)
359
            self.cset.entries[id].name = base
360
361
        if this is not None:
362
            self.change_name_tree(id, self.this, this)
363
364
        if other is not None:
365
            self.change_name_tree(id, self.other, other)
366
            self.cset.entries[id].new_name = other
367
368
    def change_parent(self, id, base=None, this=None, other=None):
369
        if base is not None:
370
            self.change_parent_tree(id, self.base, base)
371
            self.cset.entries[id].parent = base
372
            self.cset.entries[id].dir = self.cset.entries[base].path
373
374
        if this is not None:
375
            self.change_parent_tree(id, self.this, this)
376
377
        if other is not None:
378
            self.change_parent_tree(id, self.other, other)
379
            self.cset.entries[id].new_parent = other
380
            self.cset.entries[id].new_dir = \
381
                self.cset.entries[other].new_path
382
383
    def change_contents(self, id, base=None, this=None, other=None):
384
        if base is not None:
385
            self.change_contents_tree(id, self.base, base)
386
387
        if this is not None:
388
            self.change_contents_tree(id, self.this, this)
389
390
        if other is not None:
391
            self.change_contents_tree(id, self.other, other)
392
393
        if base is not None or other is not None:
394
            old_contents = file(self.base.full_path(id)).read()
395
            new_contents = file(self.other.full_path(id)).read()
396
            contents = changeset.ReplaceFileContents(old_contents, 
397
                                                     new_contents)
398
            self.cset.entries[id].contents_change = contents
399
400
    def change_perms(self, id, base=None, this=None, other=None):
401
        if base is not None:
402
            self.change_perms_tree(id, self.base, base)
403
404
        if this is not None:
405
            self.change_perms_tree(id, self.this, this)
406
407
        if other is not None:
408
            self.change_perms_tree(id, self.other, other)
409
410
        if base is not None or other is not None:
411
            old_perms = os.stat(self.base.full_path(id)).st_mode &077
412
            new_perms = os.stat(self.other.full_path(id)).st_mode &077
413
            contents = changeset.ChangeUnixPermissions(old_perms, 
414
                                                       new_perms)
415
            self.cset.entries[id].metadata_change = contents
416
417
    def change_name_tree(self, id, tree, name):
418
        new_path = tree.child_path(self.cset.entries[id].parent, name)
419
        tree.change_path(id, new_path)
420
421
    def change_parent_tree(self, id, tree, parent):
422
        new_path = tree.child_path(parent, self.cset.entries[id].name)
423
        tree.change_path(id, new_path)
424
425
    def change_contents_tree(self, id, tree, contents):
426
        path = tree.full_path(id)
427
        mode = os.stat(path).st_mode
428
        file(path, "w").write(contents)
429
        os.chmod(path, mode)
430
431
    def change_perms_tree(self, id, tree, mode):
432
        os.chmod(tree.full_path(id), mode)
433
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
434
    def merge_changeset(self, merge_factory):
493 by Martin Pool
- Merge aaron's merge command
435
        all_inventory = ThreewayInventory(Inventory(self.this.inventory),
436
                                          Inventory(self.base.inventory), 
437
                                          Inventory(self.other.inventory))
438
        conflict_handler = changeset.ExceptionConflictHandler(self.this.dir)
850 by Martin Pool
- Merge merge updates from aaron
439
        return make_merge_changeset(self.cset, all_inventory, self.this,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
440
                                    self.base, self.other, conflict_handler,
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
441
                                    merge_factory)
850 by Martin Pool
- Merge merge updates from aaron
442
443
    def apply_inv_change(self, inventory_change, orig_inventory):
444
        orig_inventory_by_path = {}
445
        for file_id, path in orig_inventory.iteritems():
446
            orig_inventory_by_path[path] = file_id
447
448
        def parent_id(file_id):
449
            try:
450
                parent_dir = os.path.dirname(orig_inventory[file_id])
451
            except:
452
                print file_id
453
                raise
454
            if parent_dir == "":
455
                return None
456
            return orig_inventory_by_path[parent_dir]
457
        
458
        def new_path(file_id):
459
            if inventory_change.has_key(file_id):
460
                return inventory_change[file_id]
461
            else:
462
                parent = parent_id(file_id)
463
                if parent is None:
464
                    return orig_inventory[file_id]
465
                dirname = new_path(parent)
466
                return os.path.join(dirname, orig_inventory[file_id])
467
468
        new_inventory = {}
469
        for file_id in orig_inventory.iterkeys():
470
            path = new_path(file_id)
471
            if path is None:
472
                continue
473
            new_inventory[file_id] = path
474
475
        for file_id, path in inventory_change.iteritems():
476
            if orig_inventory.has_key(file_id):
477
                continue
478
            new_inventory[file_id] = path
479
        return new_inventory
480
481
        
482
493 by Martin Pool
- Merge aaron's merge command
483
    def apply_changeset(self, cset, conflict_handler=None, reverse=False):
850 by Martin Pool
- Merge merge updates from aaron
484
        inventory_change = changeset.apply_changeset(cset,
485
                                                     self.this.inventory,
486
                                                     self.this.dir,
487
                                                     conflict_handler, reverse)
488
        self.this.inventory =  self.apply_inv_change(inventory_change, 
489
                                                     self.this.inventory)
490
491
                    
492
        
493
493 by Martin Pool
- Merge aaron's merge command
494
        
495
    def cleanup(self):
496
        shutil.rmtree(self.dir)
497
498
499
class MergeTest(unittest.TestCase):
500
    def test_change_name(self):
501
        """Test renames"""
502
        builder = MergeBuilder()
503
        builder.add_file("1", "0", "name1", "hello1", 0755)
504
        builder.change_name("1", other="name2")
505
        builder.add_file("2", "0", "name3", "hello2", 0755)
506
        builder.change_name("2", base="name4")
507
        builder.add_file("3", "0", "name5", "hello3", 0755)
508
        builder.change_name("3", this="name6")
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
509
        cset = builder.merge_changeset(ApplyMerge3)
493 by Martin Pool
- Merge aaron's merge command
510
        assert(cset.entries["2"].is_boring())
511
        assert(cset.entries["1"].name == "name1")
512
        assert(cset.entries["1"].new_name == "name2")
513
        assert(cset.entries["3"].is_boring())
514
        for tree in (builder.this, builder.other, builder.base):
515
            assert(tree.dir != builder.dir and 
516
                   tree.dir.startswith(builder.dir))
517
            for path in tree.inventory.itervalues():
518
                fullpath = tree.abs_path(path)
519
                assert(fullpath.startswith(tree.dir))
520
                assert(not path.startswith(tree.dir))
521
                assert os.path.exists(fullpath)
522
        builder.apply_changeset(cset)
523
        builder.cleanup()
524
        builder = MergeBuilder()
525
        builder.add_file("1", "0", "name1", "hello1", 0644)
526
        builder.change_name("1", other="name2", this="name3")
527
        self.assertRaises(changeset.RenameConflict, 
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
528
                          builder.merge_changeset, ApplyMerge3)
493 by Martin Pool
- Merge aaron's merge command
529
        builder.cleanup()
530
        
531
    def test_file_moves(self):
532
        """Test moves"""
533
        builder = MergeBuilder()
534
        builder.add_dir("1", "0", "dir1", 0755)
535
        builder.add_dir("2", "0", "dir2", 0755)
536
        builder.add_file("3", "1", "file1", "hello1", 0644)
537
        builder.add_file("4", "1", "file2", "hello2", 0644)
538
        builder.add_file("5", "1", "file3", "hello3", 0644)
539
        builder.change_parent("3", other="2")
540
        assert(Inventory(builder.other.inventory).get_parent("3") == "2")
541
        builder.change_parent("4", this="2")
542
        assert(Inventory(builder.this.inventory).get_parent("4") == "2")
543
        builder.change_parent("5", base="2")
544
        assert(Inventory(builder.base.inventory).get_parent("5") == "2")
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
545
        cset = builder.merge_changeset(ApplyMerge3)
493 by Martin Pool
- Merge aaron's merge command
546
        for id in ("1", "2", "4", "5"):
547
            assert(cset.entries[id].is_boring())
548
        assert(cset.entries["3"].parent == "1")
549
        assert(cset.entries["3"].new_parent == "2")
550
        builder.apply_changeset(cset)
551
        builder.cleanup()
552
553
        builder = MergeBuilder()
554
        builder.add_dir("1", "0", "dir1", 0755)
555
        builder.add_dir("2", "0", "dir2", 0755)
556
        builder.add_dir("3", "0", "dir3", 0755)
557
        builder.add_file("4", "1", "file1", "hello1", 0644)
558
        builder.change_parent("4", other="2", this="3")
559
        self.assertRaises(changeset.MoveConflict, 
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
560
                          builder.merge_changeset, ApplyMerge3)
493 by Martin Pool
- Merge aaron's merge command
561
        builder.cleanup()
562
563
    def test_contents_merge(self):
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
564
        """Test merge3 merging"""
565
        self.do_contents_test(ApplyMerge3)
566
567
    def test_contents_merge2(self):
493 by Martin Pool
- Merge aaron's merge command
568
        """Test diff3 merging"""
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
569
        self.do_contents_test(changeset.Diff3Merge)
570
974.1.8 by Aaron Bentley
Added default backups for merge-revert
571
    def test_contents_merge3(self):
572
        """Test diff3 merging"""
573
        def backup_merge(base_file, other_file):
574
            return BackupBeforeChange(ApplyMerge3(base_file, other_file))
575
        builder = self.contents_test_success(backup_merge)
576
        def backup_exists(file_id):
577
            return os.path.exists(builder.this.full_path(file_id)+"~")
578
        assert backup_exists("1")
579
        assert backup_exists("2")
580
        assert not backup_exists("3")
581
        builder.cleanup()
582
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
583
    def do_contents_test(self, merge_factory):
584
        """Test merging with specified ContentsChange factory"""
974.1.8 by Aaron Bentley
Added default backups for merge-revert
585
        builder = self.contents_test_success(merge_factory)
586
        builder.cleanup()
587
        self.contents_test_conflicts(merge_factory)
588
589
    def contents_test_success(self, merge_factory):
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
590
        from inspect import isclass
493 by Martin Pool
- Merge aaron's merge command
591
        builder = MergeBuilder()
592
        builder.add_file("1", "0", "name1", "text1", 0755)
593
        builder.change_contents("1", other="text4")
594
        builder.add_file("2", "0", "name3", "text2", 0655)
595
        builder.change_contents("2", base="text5")
596
        builder.add_file("3", "0", "name5", "text3", 0744)
597
        builder.change_contents("3", this="text6")
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
598
        cset = builder.merge_changeset(merge_factory)
493 by Martin Pool
- Merge aaron's merge command
599
        assert(cset.entries["1"].contents_change is not None)
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
600
        if isclass(merge_factory):
601
            assert(isinstance(cset.entries["1"].contents_change,
602
                          merge_factory))
603
            assert(isinstance(cset.entries["2"].contents_change,
604
                          merge_factory))
493 by Martin Pool
- Merge aaron's merge command
605
        assert(cset.entries["3"].is_boring())
606
        builder.apply_changeset(cset)
607
        assert(file(builder.this.full_path("1"), "rb").read() == "text4" )
608
        assert(file(builder.this.full_path("2"), "rb").read() == "text2" )
609
        assert(os.stat(builder.this.full_path("1")).st_mode &0777 == 0755)
610
        assert(os.stat(builder.this.full_path("2")).st_mode &0777 == 0655)
611
        assert(os.stat(builder.this.full_path("3")).st_mode &0777 == 0744)
974.1.8 by Aaron Bentley
Added default backups for merge-revert
612
        return builder
493 by Martin Pool
- Merge aaron's merge command
613
974.1.8 by Aaron Bentley
Added default backups for merge-revert
614
    def contents_test_conflicts(self, merge_factory):
493 by Martin Pool
- Merge aaron's merge command
615
        builder = MergeBuilder()
616
        builder.add_file("1", "0", "name1", "text1", 0755)
617
        builder.change_contents("1", other="text4", this="text3")
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
618
        cset = builder.merge_changeset(merge_factory)
493 by Martin Pool
- Merge aaron's merge command
619
        self.assertRaises(changeset.MergeConflict, builder.apply_changeset,
620
                          cset)
621
        builder.cleanup()
622
909.1.4 by Aaron Bentley
Fixed conflict handling for missing merge targets
623
        builder = MergeBuilder()
624
        builder.add_file("1", "0", "name1", "text1", 0755)
625
        builder.change_contents("1", other="text4", base="text3")
626
        builder.remove_file("1", base=True)
627
        self.assertRaises(changeset.NewContentsConflict,
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
628
                          builder.merge_changeset, merge_factory)
909.1.4 by Aaron Bentley
Fixed conflict handling for missing merge targets
629
        builder.cleanup()
630
631
        builder = MergeBuilder()
632
        builder.add_file("1", "0", "name1", "text1", 0755)
633
        builder.change_contents("1", other="text4", base="text3")
634
        builder.remove_file("1", this=True)
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
635
        self.assertRaises(changeset.MissingForMerge, builder.merge_changeset, 
636
                          merge_factory)
909.1.4 by Aaron Bentley
Fixed conflict handling for missing merge targets
637
        builder.cleanup()
638
493 by Martin Pool
- Merge aaron's merge command
639
    def test_perms_merge(self):
640
        builder = MergeBuilder()
641
        builder.add_file("1", "0", "name1", "text1", 0755)
642
        builder.change_perms("1", other=0655)
643
        builder.add_file("2", "0", "name2", "text2", 0755)
644
        builder.change_perms("2", base=0655)
645
        builder.add_file("3", "0", "name3", "text3", 0755)
646
        builder.change_perms("3", this=0655)
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
647
        cset = builder.merge_changeset(ApplyMerge3)
493 by Martin Pool
- Merge aaron's merge command
648
        assert(cset.entries["1"].metadata_change is not None)
649
        assert(isinstance(cset.entries["1"].metadata_change,
650
                          PermissionsMerge))
651
        assert(isinstance(cset.entries["2"].metadata_change,
652
                          PermissionsMerge))
653
        assert(cset.entries["3"].is_boring())
654
        builder.apply_changeset(cset)
655
        assert(os.stat(builder.this.full_path("1")).st_mode &0777 == 0655)
656
        assert(os.stat(builder.this.full_path("2")).st_mode &0777 == 0755)
657
        assert(os.stat(builder.this.full_path("3")).st_mode &0777 == 0655)
658
        builder.cleanup();
659
        builder = MergeBuilder()
660
        builder.add_file("1", "0", "name1", "text1", 0755)
661
        builder.change_perms("1", other=0655, base=0555)
974.1.7 by Aaron Bentley
Fixed handling of merge factory in test suite
662
        cset = builder.merge_changeset(ApplyMerge3)
493 by Martin Pool
- Merge aaron's merge command
663
        self.assertRaises(changeset.MergePermissionConflict, 
664
                     builder.apply_changeset, cset)
665
        builder.cleanup()
666
667
def test():        
668
    changeset_suite = unittest.makeSuite(MergeTest, 'test_')
669
    runner = unittest.TextTestRunner()
670
    runner.run(changeset_suite)
671
        
672
if __name__ == "__main__":
673
    test()