~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Robert Collins
  • Date: 2005-10-30 00:00:09 UTC
  • mfrom: (1185.16.134)
  • Revision ID: robertc@robertcollins.net-20051030000009-9db99a338a0dfdac
MergeĀ fromĀ Martin.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
 
18
18
import os
 
19
import tempfile
19
20
import shutil
20
21
import errno
21
22
 
26
27
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
27
28
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
28
29
from bzrlib.branch import Branch
29
 
from bzrlib.errors import (BzrCommandError,
30
 
                           UnrelatedBranches,
31
 
                           NoCommonAncestor,
32
 
                           NoCommits,
33
 
                           WorkingTreeNotRevision,
34
 
                           NotBranchError,
35
 
                           NotVersionedError,
36
 
                           BzrError)
 
30
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
 
31
from bzrlib.errors import NoCommits, WorkingTreeNotRevision, NotBranchError
37
32
from bzrlib.delta import compare_trees
38
33
from bzrlib.trace import mutter, warning, note
39
34
from bzrlib.fetch import greedy_fetch, fetch
40
35
from bzrlib.revision import is_ancestor, NULL_REVISION
41
 
from bzrlib.osutils import rename, pathjoin
 
36
from bzrlib.osutils import rename
42
37
from bzrlib.revision import common_ancestor, MultipleRevisionSources
43
38
from bzrlib.errors import NoSuchRevision
44
39
 
114
109
                    if file_id is not None:
115
110
                        new_path = self.this_tree.relpath(new_name)
116
111
                        rename(new_name, name)
117
 
                        self.this_tree.rename_one(relpath, new_path)
 
112
                        self.this_tree.branch.rename_one(relpath, new_path)
 
113
                        assert self.this_tree.id2path(file_id) == relpath
 
114
                        self.this_tree._inventory = self.this_tree.branch.inventory
118
115
                        assert self.this_tree.id2path(file_id) == new_path
119
116
        except OSError, e:
120
117
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
148
145
        Handle weave conflicts by producing a .THIS, and .OTHER.  The
149
146
        main file will be a version with diff3-style conflicts.
150
147
        """
151
 
        self.add_suffix(filename, ".THIS", fix_inventory=False)
 
148
        self.add_suffix(filename, ".THIS")
152
149
        out_file.commit()
153
150
        self.dump(weave.get_iter(other_i), filename+".OTHER")
154
151
        self.conflict("Text conflict encountered in %s" % filename)
171
168
        return "skip"
172
169
 
173
170
    def rem_contents_conflict(self, filename, this_contents, base_contents):
174
 
        base_contents(filename+".BASE", self)
175
 
        this_contents(filename+".THIS", self)
 
171
        base_contents(filename+".BASE", self, False)
 
172
        this_contents(filename+".THIS", self, False)
 
173
        return ReplaceContents(this_contents, None)
 
174
 
 
175
    def rem_contents_conflict(self, filename, this_contents, base_contents):
 
176
        base_contents(filename+".BASE", self, False)
 
177
        this_contents(filename+".THIS", self, False)
176
178
        self.conflict("Other branch deleted locally modified file %s" %
177
179
                      filename)
178
180
        return ReplaceContents(this_contents, None)
196
198
            abspath = self.create_all_missing(entry.parent_id, tree)
197
199
        else:
198
200
            abspath = self.abs_this_path(entry.parent_id)
199
 
        entry_path = pathjoin(abspath, entry.name)
 
201
        entry_path = os.path.join(abspath, entry.name)
200
202
        if not os.path.isdir(entry_path):
201
203
            self.create(file_id, entry_path, tree)
202
204
        return entry_path
203
205
 
204
 
    def create(self, file_id, path, tree):
 
206
    def create(self, file_id, path, tree, reverse=False):
205
207
        """Uses tree data to create a filesystem object for the file_id"""
206
208
        from changeset import get_contents
207
 
        get_contents(tree, file_id)(path, self)
 
209
        get_contents(tree, file_id)(path, self, reverse)
208
210
 
209
211
    def missing_for_merge(self, file_id, other_path):
210
212
        """The file_id doesn't exist in THIS, but does in OTHER and BASE"""
211
213
        self.conflict("Other branch modified locally deleted file %s" %
212
214
                      other_path)
213
215
        parent_dir = self.add_missing_parents(file_id, self.other_tree)
214
 
        stem = pathjoin(parent_dir, os.path.basename(other_path))
 
216
        stem = os.path.join(parent_dir, os.path.basename(other_path))
215
217
        self.create(file_id, stem+".OTHER", self.other_tree)
216
218
        self.create(file_id, stem+".BASE", self.base_tree)
217
219
 
220
222
        self.conflict("Three-way conflict merging %s" % filename)
221
223
 
222
224
    def finalize(self):
223
 
        if self.conflicts == 0:
224
 
            if not self.ignore_zero:
225
 
                note("All changes applied successfully.")
226
 
        else:
227
 
            note("%d conflicts encountered." % self.conflicts)
 
225
        if not self.ignore_zero:
 
226
            note("%d conflicts encountered.\n" % self.conflicts)
228
227
            
229
228
def get_tree(treespec, local_branch=None):
230
229
    location, revno = treespec
266
265
    lower-level code (e.g. constructing a changeset).
267
266
    """
268
267
    # RBC 20051019 is this not just 'export' ?
269
 
    # AB Well, export doesn't take care of inventory...
 
268
    # Well, export doesn't take care of inventory...
270
269
    this_branch = Branch.open_containing(to_dir)[0]
271
270
    transform_tree(this_branch.working_tree(), this_branch.basis_tree())
272
271
 
273
 
 
274
 
def transform_tree(from_tree, to_tree, interesting_ids=None):
275
 
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
276
 
                interesting_ids=interesting_ids)
277
 
 
 
272
def transform_tree(from_tree, to_tree):
 
273
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True)
278
274
 
279
275
def merge(other_revision, base_revision,
280
276
          check_clean=True, ignore_zero=False,
307
303
    clients might prefer to call merge_inner(), which has less magic behavior.
308
304
    """
309
305
    if this_dir is None:
310
 
        this_dir = u'.'
 
306
        this_dir = '.'
311
307
    this_branch = Branch.open_containing(this_dir)[0]
312
308
    if show_base and not merge_type is ApplyMerge3:
313
309
        raise BzrCommandError("Show-base is not supported for this merge"
321
317
    merger.check_basis(check_clean)
322
318
    merger.set_other(other_revision)
323
319
    merger.set_base(base_revision)
324
 
    if merger.base_rev_id == merger.other_rev_id:
325
 
        note('Nothing to do.')
326
 
        return 0
327
320
    merger.backup_files = backup_files
328
321
    merger.merge_type = merge_type 
329
322
    merger.set_interesting_files(file_list)
338
331
    return conflicts
339
332
 
340
333
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
341
 
                backup_files=False, 
342
 
                merge_type=ApplyMerge3, 
343
 
                interesting_ids=None, 
344
 
                show_base=False, 
345
 
                reprocess=False, 
346
 
                other_rev_id=None,
347
 
                interesting_files=None):
 
334
                backup_files=False, merge_type=ApplyMerge3, 
 
335
                interesting_ids=None, show_base=False, reprocess=False):
348
336
    """Primary interface for merging. 
349
337
 
350
338
        typical use is probably 
352
340
                     branch.get_revision_tree(base_revision))'
353
341
        """
354
342
    merger = Merger(this_branch, other_tree, base_tree)
355
 
    merger.backup_files = backup_files
356
 
    merger.merge_type = merge_type
 
343
    merger.backup_files = False
 
344
    merger.merge_type = ApplyMerge3
357
345
    merger.interesting_ids = interesting_ids
358
 
    if interesting_files:
359
 
        assert not interesting_ids, ('Only supply interesting_ids'
360
 
                                     ' or interesting_files')
361
 
        merger._set_interesting_files(interesting_files)
362
346
    merger.show_base = show_base 
363
347
    merger.reprocess = reprocess
364
348
    merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree, 
365
349
                                                   other_tree,
366
350
                                                   ignore_zero=ignore_zero)
367
 
    merger.other_rev_id = other_rev_id
368
 
    merger.other_basis = other_rev_id
369
351
    return merger.do_merge()
370
352
 
371
353
 
377
359
        self.this_rev_id = None
378
360
        self.this_tree = this_branch.working_tree()
379
361
        self.this_revision_tree = None
380
 
        self.this_basis_tree = None
381
362
        self.other_tree = other_tree
382
363
        self.base_tree = base_tree
383
364
        self.ignore_zero = False
393
374
 
394
375
    def ensure_revision_trees(self):
395
376
        if self.this_revision_tree is None:
396
 
            self.this_basis_tree = self.this_branch.revision_tree(
397
 
                self.this_basis)
398
 
            if self.this_basis == self.this_rev_id:
399
 
                self.this_revision_tree = self.this_basis_tree
400
 
 
 
377
            if self.this_rev_id is None:
 
378
                self.compare_basis()
 
379
            if self.this_rev_id is None:
 
380
                raise WorkingTreeNotRevision(self.this_tree)
 
381
            self.this_revision_tree = self.this_branch.revision_tree(
 
382
                self.this_rev_id)
401
383
 
402
384
        if self.other_rev_id is None:
403
385
            other_basis_tree = self.revision_tree(self.other_basis)
414
396
            revision_id = tree.inventory[file_id].revision
415
397
            assert revision_id is not None
416
398
            return revision_id
417
 
        if self.this_rev_id is None:
418
 
            if self.this_basis_tree.get_file_sha1(file_id) != \
419
 
                self.this_tree.get_file_sha1(file_id):
420
 
                raise WorkingTreeNotRevision(self.this_tree)
421
 
 
422
 
        trees = (self.this_basis_tree, self.other_tree)
 
399
        trees = (self.this_revision_tree, self.other_tree)
423
400
        return [get_id(tree, file_id) for tree in trees]
424
401
            
425
402
 
426
403
    def merge_factory(self, file_id, base, other):
427
404
        if self.merge_type.history_based:
428
 
            if self.show_base is True:
429
 
                raise BzrError("Cannot show base for hisory-based merges")
430
 
            if self.reprocess is True:
431
 
                raise BzrError("Cannot reprocess history-based merges")
432
 
                
433
405
            t_revid, o_revid = self.file_revisions(file_id)
434
 
            weave = self.this_basis_tree.get_weave(file_id)
 
406
            weave = self.this_revision_tree.get_weave(file_id)
435
407
            contents_change = self.merge_type(weave, t_revid, o_revid)
436
408
        else:
437
409
            if self.show_base is True or self.reprocess is True:
459
431
            self.this_rev_id = self.this_basis
460
432
 
461
433
    def set_interesting_files(self, file_list):
462
 
        try:
463
 
            self._set_interesting_files(file_list)
464
 
        except NotVersionedError, e:
465
 
            raise BzrCommandError("%s is not a source file in any"
466
 
                                      " tree." % e.path)
467
 
 
468
 
    def _set_interesting_files(self, file_list):
469
 
        """Set the list of interesting ids from a list of files."""
470
434
        if file_list is None:
471
435
            self.interesting_ids = None
472
436
            return
481
445
                    interesting_ids.add(file_id)
482
446
                    found_id = True
483
447
            if not found_id:
484
 
                raise NotVersionedError(path=fname)
 
448
                raise BzrCommandError("%s is not a source file in any"
 
449
                                      " tree." % fname)
485
450
        self.interesting_ids = interesting_ids
486
451
 
487
452
    def set_pending(self):
491
456
            return
492
457
        if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
493
458
            return
494
 
        self.this_branch.working_tree().add_pending_merge(self.other_rev_id)
 
459
        self.this_branch.add_pending_merge(self.other_rev_id)
495
460
 
496
461
    def set_other(self, other_revision):
497
462
        other_branch, self.other_tree = get_tree(other_revision, 
540
505
    def do_merge(self):
541
506
        def get_inventory(tree):
542
507
            return tree.inventory
543
 
        
 
508
 
544
509
        inv_changes = merge_flex(self.this_tree, self.base_tree, 
545
510
                                 self.other_tree,
546
511
                                 generate_changeset, get_inventory,
551
516
        adjust_ids = []
552
517
        for id, path in inv_changes.iteritems():
553
518
            if path is not None:
554
 
                if path == u'.':
555
 
                    path = u''
 
519
                if path == '.':
 
520
                    path = ''
556
521
                else:
557
 
                    assert path.startswith('.' + '/') or path.startswith('.' + '\\'), "path is %s" % path
 
522
                    assert path.startswith('.' + os.sep), "path is %s" % path
558
523
                path = path[2:]
559
524
            adjust_ids.append((path, id))
560
525
        if len(adjust_ids) > 0:
561
 
            self.this_branch.working_tree().set_inventory(self.regen_inventory(adjust_ids))
 
526
            self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
562
527
        conflicts = self.conflict_handler.conflicts
563
528
        self.conflict_handler.finalize()
564
529
        return conflicts
565
530
 
566
531
    def regen_inventory(self, new_entries):
567
 
        old_entries = self.this_branch.working_tree().read_working_inventory()
 
532
        old_entries = self.this_branch.read_working_inventory()
568
533
        new_inventory = {}
569
534
        by_path = {}
570
535
        new_entries_map = {} 
580
545
            entry = old_entries[file_id]
581
546
            if entry.parent_id is None:
582
547
                return entry.name
583
 
            return pathjoin(id2path(entry.parent_id), entry.name)
 
548
            return os.path.join(id2path(entry.parent_id), entry.name)
584
549
            
585
550
        for file_id in old_entries:
586
551
            entry = old_entries[file_id]
607
572
                parent = None
608
573
            else:
609
574
                parent = by_path[os.path.dirname(path)]
610
 
            abspath = pathjoin(self.this_tree.basedir, path)
 
575
            abspath = os.path.join(self.this_tree.basedir, path)
611
576
            kind = bzrlib.osutils.file_kind(abspath)
612
577
            new_inventory[file_id] = (path, file_id, parent, kind)
613
578
            by_path[path] = file_id