/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Aaron Bentley
  • Date: 2005-10-21 19:32:07 UTC
  • mto: (1185.25.1)
  • mto: This revision was merged to the branch mainline in revision 1488.
  • Revision ID: abentley@panoramicfeedback.com-20051021193207-b995bd6ca86366ac
Refactored merge and merge_inner to use Merger

Show diffs side-by-side

added added

removed removed

Lines of Context:
267
267
    if this_dir is None:
268
268
        this_dir = '.'
269
269
    this_branch = Branch.open_containing(this_dir)[0]
270
 
    this_rev_id = this_branch.last_revision()
271
 
    if this_rev_id is None:
272
 
        raise BzrCommandError("This branch has no commits")
273
 
    if check_clean:
274
 
        changes = compare_trees(this_branch.working_tree(), 
275
 
                                this_branch.basis_tree(), False)
276
 
        if changes.has_changed():
277
 
            raise BzrCommandError("Working tree has uncommitted changes.")
278
 
    other_branch, other_tree = get_tree(other_revision, this_branch)
279
 
    if other_revision[1] == -1:
280
 
        other_rev_id = other_branch.last_revision()
281
 
        if other_rev_id is None:
282
 
            raise NoCommits(other_branch)
283
 
        other_basis = other_rev_id
284
 
    elif other_revision[1] is not None:
285
 
        other_rev_id = other_branch.get_rev_id(other_revision[1])
286
 
        other_basis = other_rev_id
287
 
    else:
288
 
        other_rev_id = None
289
 
        other_basis = other_branch.last_revision()
290
 
        if other_basis is None:
291
 
            raise NoCommits(other_branch)
292
 
    if base_revision == [None, None]:
293
 
        try:
294
 
            base_rev_id = common_ancestor(this_rev_id, other_basis, 
295
 
                                          this_branch)
296
 
        except NoCommonAncestor:
297
 
            raise UnrelatedBranches()
298
 
        base_tree = get_revid_tree(this_branch, base_rev_id, None)
299
 
        base_is_ancestor = True
300
 
    else:
301
 
        base_branch, base_tree = get_tree(base_revision)
302
 
        if base_revision[1] == -1:
303
 
            base_rev_id = base_branch.last_revision()
304
 
        elif base_revision[1] is None:
305
 
            base_rev_id = None
 
270
 
 
271
    merger = Merger(this_branch)
 
272
    merger.check_basis(check_clean)
 
273
    merger.set_other(other_revision)
 
274
    merger.set_base(base_revision)
 
275
    merger.backup_files = backup_files
 
276
    merger.merge_type = ApplyMerge3
 
277
    merger.set_interesting_files(file_list)
 
278
    merger.show_base = show_base 
 
279
    merger.conflict_handler = MergeConflictHandler(merger.this_tree, 
 
280
                                                   merger.base_tree, 
 
281
                                                   merger.other_tree,
 
282
                                                   ignore_zero=ignore_zero)
 
283
    conflicts = merger.do_merge()
 
284
    merger.set_pending()
 
285
    return conflicts
 
286
 
 
287
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
 
288
                backup_files=False, merge_type=ApplyMerge3, 
 
289
                interesting_ids=None, show_base=False):
 
290
    """Primary interface for merging. 
 
291
 
 
292
        typical use is probably 
 
293
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
294
                     branch.get_revision_tree(base_revision))'
 
295
        """
 
296
    merger = Merger(this_branch, other_tree, base_tree)
 
297
    merger.backup_files = False
 
298
    merger.merge_type = ApplyMerge3
 
299
    merger.interesting_ids = interesting_ids
 
300
    merger.show_base = show_base 
 
301
    merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree, 
 
302
                                                   other_tree,
 
303
                                                   ignore_zero=ignore_zero)
 
304
    return merger.do_merge()
 
305
 
 
306
 
 
307
class Merger(object):
 
308
    def __init__(self, this_branch, other_tree=None, base_tree=None):
 
309
        object.__init__(self)
 
310
        self.this_branch = this_branch
 
311
        self.this_basis = this_branch.last_revision()
 
312
        self.this_rev_id = None
 
313
        self.this_tree = this_branch.working_tree()
 
314
        self.other_tree = other_tree
 
315
        self.base_tree = base_tree
 
316
        self.ignore_zero = False
 
317
        self.backup_files = False
 
318
        self.interesting_ids = None
 
319
        self.show_base = False
 
320
        self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree, 
 
321
                                                     other_tree)
 
322
 
 
323
    def merge_factory(self, file_id, base, other):
 
324
        if self.show_base is True:
 
325
            contents_change = self.merge_type(file_id, base, other, 
 
326
                                              show_base=True)
306
327
        else:
307
 
            base_rev_id = base_branch.get_rev_id(base_revision[1])
308
 
        fetch(from_branch=base_branch, to_branch=this_branch)
309
 
        base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
310
 
                                       this_branch)
311
 
    if file_list is None:
312
 
        interesting_ids = None
313
 
    else:
 
328
            contents_change = self.merge_type(file_id, base, other)
 
329
        if self.backup_files:
 
330
            contents_change = BackupBeforeChange(contents_change)
 
331
        return contents_change
 
332
 
 
333
    def check_basis(self, check_clean):
 
334
        if self.this_basis is None:
 
335
            raise BzrCommandError("This branch has no commits")
 
336
        if check_clean:
 
337
            self.compare_basis()
 
338
            if self.this_basis != self.this_rev_id:
 
339
                raise BzrCommandError("Working tree has uncommitted changes.")
 
340
 
 
341
    def compare_basis(self):
 
342
        changes = compare_trees(self.this_branch.working_tree(), 
 
343
                                self.this_branch.basis_tree(), False)
 
344
        if not changes.has_changed():
 
345
            self.this_rev_id = self.this_basis
 
346
 
 
347
    def set_interesting_files(self, file_list):
 
348
        if file_list is None:
 
349
            self.interesting_ids = None
 
350
            return
 
351
 
314
352
        interesting_ids = set()
315
 
        this_tree = this_branch.working_tree()
316
353
        for fname in file_list:
317
 
            path = this_tree.relpath(fname)
 
354
            path = self.this_tree.relpath(fname)
318
355
            found_id = False
319
 
            for tree in (this_tree, base_tree, other_tree):
 
356
            for tree in (self.this_tree, self.base_tree, self.other_tree):
320
357
                file_id = tree.inventory.path2id(path)
321
358
                if file_id is not None:
322
359
                    interesting_ids.add(file_id)
324
361
            if not found_id:
325
362
                raise BzrCommandError("%s is not a source file in any"
326
363
                                      " tree." % fname)
327
 
    conflicts = merge_inner(this_branch, other_tree, base_tree,
328
 
                            ignore_zero=ignore_zero, backup_files=backup_files,
329
 
                            merge_type=merge_type,
330
 
                            interesting_ids=interesting_ids,
331
 
                            show_base=show_base)
332
 
    if base_is_ancestor and other_rev_id is not None\
333
 
        and other_rev_id not in this_branch.revision_history():
334
 
        this_branch.add_pending_merge(other_rev_id)
335
 
    return conflicts
336
 
 
337
 
 
338
 
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
339
 
                merge_type=ApplyMerge3, backup_files=False,
340
 
                interesting_ids=None, show_base=False):
341
 
    """Primary interface for merging. 
342
 
 
343
 
    typical use is probably 
344
 
    'merge_inner(branch, branch.get_revision_tree(other_revision),
345
 
                 branch.get_revision_tree(base_revision))'
346
 
    """
347
 
    def merge_factory(file_id, base, other):
348
 
        if show_base is True:
349
 
            contents_change = merge_type(file_id, base, other, show_base=True)
350
 
        else:
351
 
            contents_change = merge_type(file_id, base, other)
352
 
        if backup_files:
353
 
            contents_change = BackupBeforeChange(contents_change)
354
 
        return contents_change
355
 
 
356
 
    this_tree = get_tree((this_branch.base, None))[1]
357
 
 
358
 
    def get_inventory(tree):
359
 
        return tree.inventory
360
 
 
361
 
    conflict_handler = MergeConflictHandler(this_tree, base_tree, other_tree,
362
 
                                            ignore_zero=ignore_zero)
363
 
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
364
 
                             generate_changeset, get_inventory,
365
 
                             conflict_handler,
366
 
                             merge_factory=merge_factory, 
367
 
                             interesting_ids=interesting_ids)
368
 
 
369
 
    adjust_ids = []
370
 
    for id, path in inv_changes.iteritems():
371
 
        if path is not None:
372
 
            if path == '.':
373
 
                path = ''
 
364
        self.interesting_ids = interesting_ids
 
365
 
 
366
    def set_pending(self):
 
367
        if self.base_is_ancestor and self.other_rev_id is not None\
 
368
            and self.other_rev_id not in self.this_branch.revision_history():
 
369
            self.this_branch.add_pending_merge(self.other_rev_id)
 
370
 
 
371
    def set_other(self, other_revision):
 
372
        other_branch, self.other_tree = get_tree(other_revision, 
 
373
                                                 self.this_branch)
 
374
        if other_revision[1] == -1:
 
375
            self.other_rev_id = other_branch.last_revision()
 
376
            if self.other_rev_id is None:
 
377
                raise NoCommits(other_branch)
 
378
            self.other_basis = self.other_rev_id
 
379
        elif other_revision[1] is not None:
 
380
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
381
            self.other_basis = self.other_rev_id
 
382
        else:
 
383
            self.other_rev_id = None
 
384
            self.other_basis = other_branch.last_revision()
 
385
            if self.other_basis is None:
 
386
                raise NoCommits(other_branch)
 
387
 
 
388
    def set_base(self, base_revision):
 
389
        if base_revision == [None, None]:
 
390
            try:
 
391
                self.base_rev_id = common_ancestor(self.this_basis, 
 
392
                                                   self.other_basis, 
 
393
                                                   self.this_branch)
 
394
            except NoCommonAncestor:
 
395
                raise UnrelatedBranches()
 
396
            self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
 
397
                                            None)
 
398
            self.base_is_ancestor = True
 
399
        else:
 
400
            base_branch, self.base_tree = get_tree(base_revision)
 
401
            if base_revision[1] == -1:
 
402
                self.base_rev_id = base_branch.last_revision()
 
403
            elif base_revision[1] is None:
 
404
                self.base_rev_id = None
374
405
            else:
375
 
                assert path.startswith('.' + os.sep), "path is %s" % path
376
 
            path = path[2:]
377
 
        adjust_ids.append((path, id))
378
 
    if len(adjust_ids) > 0:
379
 
        this_branch.set_inventory(regen_inventory(this_branch, 
380
 
                                                  this_tree.basedir,
381
 
                                                  adjust_ids))
382
 
    conflicts = conflict_handler.conflicts
383
 
    conflict_handler.finalize()
384
 
    return conflicts
385
 
 
386
 
 
387
 
def regen_inventory(this_branch, root, new_entries):
388
 
    old_entries = this_branch.read_working_inventory()
389
 
    new_inventory = {}
390
 
    by_path = {}
391
 
    new_entries_map = {} 
392
 
    for path, file_id in new_entries:
393
 
        if path is None:
394
 
            continue
395
 
        new_entries_map[file_id] = path
396
 
 
397
 
    def id2path(file_id):
398
 
        path = new_entries_map.get(file_id)
399
 
        if path is not None:
400
 
            return path
401
 
        entry = old_entries[file_id]
402
 
        if entry.parent_id is None:
403
 
            return entry.name
404
 
        return os.path.join(id2path(entry.parent_id), entry.name)
 
406
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
 
407
            fetch(from_branch=base_branch, to_branch=self.this_branch)
 
408
            self.base_is_ancestor = is_ancestor(self.this_basis, 
 
409
                                                self.base_rev_id,
 
410
                                                self.this_branch)
 
411
 
 
412
    def do_merge(self):
 
413
        def get_inventory(tree):
 
414
            return tree.inventory
 
415
 
 
416
        inv_changes = merge_flex(self.this_tree, self.base_tree, 
 
417
                                 self.other_tree,
 
418
                                 generate_changeset, get_inventory,
 
419
                                 self.conflict_handler,
 
420
                                 merge_factory=self.merge_factory, 
 
421
                                 interesting_ids=self.interesting_ids)
 
422
 
 
423
        adjust_ids = []
 
424
        for id, path in inv_changes.iteritems():
 
425
            if path is not None:
 
426
                if path == '.':
 
427
                    path = ''
 
428
                else:
 
429
                    assert path.startswith('.' + os.sep), "path is %s" % path
 
430
                path = path[2:]
 
431
            adjust_ids.append((path, id))
 
432
        if len(adjust_ids) > 0:
 
433
            self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
 
434
        conflicts = self.conflict_handler.conflicts
 
435
        self.conflict_handler.finalize()
 
436
        return conflicts
 
437
 
 
438
    def regen_inventory(self, new_entries):
 
439
        old_entries = self.this_branch.read_working_inventory()
 
440
        new_inventory = {}
 
441
        by_path = {}
 
442
        new_entries_map = {} 
 
443
        for path, file_id in new_entries:
 
444
            if path is None:
 
445
                continue
 
446
            new_entries_map[file_id] = path
 
447
 
 
448
        def id2path(file_id):
 
449
            path = new_entries_map.get(file_id)
 
450
            if path is not None:
 
451
                return path
 
452
            entry = old_entries[file_id]
 
453
            if entry.parent_id is None:
 
454
                return entry.name
 
455
            return os.path.join(id2path(entry.parent_id), entry.name)
 
456
            
 
457
        for file_id in old_entries:
 
458
            entry = old_entries[file_id]
 
459
            path = id2path(file_id)
 
460
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
 
461
                                      entry.kind)
 
462
            by_path[path] = file_id
405
463
        
406
 
    for file_id in old_entries:
407
 
        entry = old_entries[file_id]
408
 
        path = id2path(file_id)
409
 
        new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
410
 
        by_path[path] = file_id
411
 
    
412
 
    deletions = 0
413
 
    insertions = 0
414
 
    new_path_list = []
415
 
    for path, file_id in new_entries:
416
 
        if path is None:
417
 
            del new_inventory[file_id]
418
 
            deletions += 1
419
 
        else:
420
 
            new_path_list.append((path, file_id))
421
 
            if file_id not in old_entries:
422
 
                insertions += 1
423
 
    # Ensure no file is added before its parent
424
 
    new_path_list.sort()
425
 
    for path, file_id in new_path_list:
426
 
        if path == '':
427
 
            parent = None
428
 
        else:
429
 
            parent = by_path[os.path.dirname(path)]
430
 
        kind = bzrlib.osutils.file_kind(os.path.join(root, path))
431
 
        new_inventory[file_id] = (path, file_id, parent, kind)
432
 
        by_path[path] = file_id 
 
464
        deletions = 0
 
465
        insertions = 0
 
466
        new_path_list = []
 
467
        for path, file_id in new_entries:
 
468
            if path is None:
 
469
                del new_inventory[file_id]
 
470
                deletions += 1
 
471
            else:
 
472
                new_path_list.append((path, file_id))
 
473
                if file_id not in old_entries:
 
474
                    insertions += 1
 
475
        # Ensure no file is added before its parent
 
476
        new_path_list.sort()
 
477
        for path, file_id in new_path_list:
 
478
            if path == '':
 
479
                parent = None
 
480
            else:
 
481
                parent = by_path[os.path.dirname(path)]
 
482
            abspath = os.path.join(self.this_tree.basedir, path)
 
483
            kind = bzrlib.osutils.file_kind(abspath)
 
484
            new_inventory[file_id] = (path, file_id, parent, kind)
 
485
            by_path[path] = file_id 
433
486
 
434
 
    # Get a list in insertion order
435
 
    new_inventory_list = new_inventory.values()
436
 
    mutter ("""Inventory regeneration:
437
 
old length: %i insertions: %i deletions: %i new_length: %i"""\
438
 
        % (len(old_entries), insertions, deletions, len(new_inventory_list)))
439
 
    assert len(new_inventory_list) == len(old_entries) + insertions - deletions
440
 
    new_inventory_list.sort()
441
 
    return new_inventory_list
 
487
        # Get a list in insertion order
 
488
        new_inventory_list = new_inventory.values()
 
489
        mutter ("""Inventory regeneration:
 
490
    old length: %i insertions: %i deletions: %i new_length: %i"""\
 
491
            % (len(old_entries), insertions, deletions, 
 
492
               len(new_inventory_list)))
 
493
        assert len(new_inventory_list) == len(old_entries) + insertions\
 
494
            - deletions
 
495
        new_inventory_list.sort()
 
496
        return new_inventory_list
442
497
 
443
498
merge_types = {     "merge3": (ApplyMerge3, "Native diff3-style merge"), 
444
499
                     "diff3": (Diff3Merge,  "Merge using external diff3")