~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Gordon Tyler
  • Date: 2010-02-02 06:30:43 UTC
  • mto: (5037.3.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5046.
  • Revision ID: gordon@doxxx.net-20100202063043-3ygr1114d25m3f7m
Added cmdline.split function, which replaces commands.shlex_split_unicode.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
 
import errno
19
 
from itertools import chain
20
 
import os
21
 
import warnings
22
 
 
23
18
from bzrlib import (
 
19
    branch as _mod_branch,
 
20
    conflicts as _mod_conflicts,
24
21
    debug,
 
22
    decorators,
25
23
    errors,
26
24
    graph as _mod_graph,
 
25
    hooks,
 
26
    merge3,
27
27
    osutils,
28
28
    patiencediff,
29
 
    registry,
 
29
    progress,
30
30
    revision as _mod_revision,
 
31
    textfile,
 
32
    trace,
 
33
    transform,
31
34
    tree as _mod_tree,
32
35
    tsort,
33
 
    )
34
 
from bzrlib.branch import Branch
35
 
from bzrlib.conflicts import ConflictList, Conflict
36
 
from bzrlib.errors import (BzrCommandError,
37
 
                           BzrError,
38
 
                           NoCommonAncestor,
39
 
                           NoCommits,
40
 
                           NoSuchRevision,
41
 
                           NoSuchFile,
42
 
                           NotBranchError,
43
 
                           NotVersionedError,
44
 
                           UnrelatedBranches,
45
 
                           UnsupportedOperation,
46
 
                           WorkingTreeNotRevision,
47
 
                           BinaryFile,
48
 
                           )
49
 
from bzrlib.graph import Graph
50
 
from bzrlib.merge3 import Merge3
51
 
from bzrlib.osutils import rename, pathjoin
52
 
from progress import DummyProgress, ProgressPhase
53
 
from bzrlib.revision import (NULL_REVISION, ensure_null)
54
 
from bzrlib.textfile import check_text_lines
55
 
from bzrlib.trace import mutter, warning, note, is_quiet
56
 
from bzrlib.transform import (TransformPreview, TreeTransform,
57
 
                              resolve_conflicts, cook_conflicts,
58
 
                              conflict_pass, FinalPaths, create_from_tree,
59
 
                              unique_add, ROOT_PARENT)
60
 
from bzrlib.versionedfile import PlanWeaveMerge
61
 
from bzrlib import ui
62
 
 
 
36
    ui,
 
37
    versionedfile
 
38
    )
 
39
from bzrlib.symbol_versioning import (
 
40
    deprecated_in,
 
41
    deprecated_method,
 
42
    )
63
43
# TODO: Report back as changes are merged in
64
44
 
65
45
 
66
46
def transform_tree(from_tree, to_tree, interesting_ids=None):
67
 
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
68
 
                interesting_ids=interesting_ids, this_tree=from_tree)
 
47
    from_tree.lock_tree_write()
 
48
    try:
 
49
        merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
 
50
                    interesting_ids=interesting_ids, this_tree=from_tree)
 
51
    finally:
 
52
        from_tree.unlock()
 
53
 
 
54
 
 
55
class MergeHooks(hooks.Hooks):
 
56
 
 
57
    def __init__(self):
 
58
        hooks.Hooks.__init__(self)
 
59
        self.create_hook(hooks.HookPoint('merge_file_content',
 
60
            "Called with a bzrlib.merge.Merger object to create a per file "
 
61
            "merge object when starting a merge. "
 
62
            "Should return either None or a subclass of "
 
63
            "``bzrlib.merge.AbstractPerFileMerger``. "
 
64
            "Such objects will then be called per file "
 
65
            "that needs to be merged (including when one "
 
66
            "side has deleted the file and the other has changed it). "
 
67
            "See the AbstractPerFileMerger API docs for details on how it is "
 
68
            "used by merge.",
 
69
            (2, 1), None))
 
70
 
 
71
 
 
72
class AbstractPerFileMerger(object):
 
73
    """PerFileMerger objects are used by plugins extending merge for bzrlib.
 
74
 
 
75
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
76
    
 
77
    :ivar merger: The Merge3Merger performing the merge.
 
78
    """
 
79
 
 
80
    def __init__(self, merger):
 
81
        """Create a PerFileMerger for use with merger."""
 
82
        self.merger = merger
 
83
 
 
84
    def merge_contents(self, merge_params):
 
85
        """Attempt to merge the contents of a single file.
 
86
        
 
87
        :param merge_params: A bzrlib.merge.MergeHookParams
 
88
        :return : A tuple of (status, chunks), where status is one of
 
89
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
 
90
            is 'success' or 'conflicted', then chunks should be an iterable of
 
91
            strings for the new file contents.
 
92
        """
 
93
        return ('not applicable', None)
 
94
 
 
95
 
 
96
class ConfigurableFileMerger(AbstractPerFileMerger):
 
97
    """Merge individual files when configured via a .conf file.
 
98
 
 
99
    This is a base class for concrete custom file merging logic. Concrete
 
100
    classes should implement ``merge_text``.
 
101
 
 
102
    :ivar affected_files: The configured file paths to merge.
 
103
    :cvar name_prefix: The prefix to use when looking up configuration
 
104
        details.
 
105
    :cvar default_files: The default file paths to merge when no configuration
 
106
        is present.
 
107
    """
 
108
 
 
109
    name_prefix = None
 
110
    default_files = None
 
111
 
 
112
    def __init__(self, merger):
 
113
        super(ConfigurableFileMerger, self).__init__(merger)
 
114
        self.affected_files = None
 
115
        self.default_files = self.__class__.default_files or []
 
116
        self.name_prefix = self.__class__.name_prefix
 
117
        if self.name_prefix is None:
 
118
            raise ValueError("name_prefix must be set.")
 
119
 
 
120
    def filename_matches_config(self, params):
 
121
        affected_files = self.affected_files
 
122
        if affected_files is None:
 
123
            config = self.merger.this_tree.branch.get_config()
 
124
            # Until bzr provides a better policy for caching the config, we
 
125
            # just add the part we're interested in to the params to avoid
 
126
            # reading the config files repeatedly (bazaar.conf, location.conf,
 
127
            # branch.conf).
 
128
            config_key = self.name_prefix + '_merge_files'
 
129
            affected_files = config.get_user_option_as_list(config_key)
 
130
            if affected_files is None:
 
131
                # If nothing was specified in the config, use the default.
 
132
                affected_files = self.default_files
 
133
            self.affected_files = affected_files
 
134
        if affected_files:
 
135
            filename = self.merger.this_tree.id2path(params.file_id)
 
136
            if filename in affected_files:
 
137
                return True
 
138
        return False
 
139
 
 
140
    def merge_contents(self, params):
 
141
        """Merge the contents of a single file."""
 
142
        # First, check whether this custom merge logic should be used.  We
 
143
        # expect most files should not be merged by this handler.
 
144
        if (
 
145
            # OTHER is a straight winner, rely on default merge.
 
146
            params.winner == 'other' or
 
147
            # THIS and OTHER aren't both files.
 
148
            not params.is_file_merge() or
 
149
            # The filename isn't listed in the 'NAME_merge_files' config
 
150
            # option.
 
151
            not self.filename_matches_config(params)):
 
152
            return 'not_applicable', None
 
153
        return self.merge_text(self, params)
 
154
 
 
155
    def merge_text(self, params):
 
156
        """Merge the byte contents of a single file.
 
157
 
 
158
        This is called after checking that the merge should be performed in
 
159
        merge_contents, and it should behave as per
 
160
        ``bzrlib.merge.AbstractPerFileMerger.merge_contents``.
 
161
        """
 
162
        raise NotImplementedError(self.merge_text)
 
163
 
 
164
 
 
165
class MergeHookParams(object):
 
166
    """Object holding parameters passed to merge_file_content hooks.
 
167
 
 
168
    There are some fields hooks can access:
 
169
 
 
170
    :ivar file_id: the file ID of the file being merged
 
171
    :ivar trans_id: the transform ID for the merge of this file
 
172
    :ivar this_kind: kind of file_id in 'this' tree
 
173
    :ivar other_kind: kind of file_id in 'other' tree
 
174
    :ivar winner: one of 'this', 'other', 'conflict'
 
175
    """
 
176
 
 
177
    def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
 
178
            winner):
 
179
        self._merger = merger
 
180
        self.file_id = file_id
 
181
        self.trans_id = trans_id
 
182
        self.this_kind = this_kind
 
183
        self.other_kind = other_kind
 
184
        self.winner = winner
 
185
 
 
186
    def is_file_merge(self):
 
187
        """True if this_kind and other_kind are both 'file'."""
 
188
        return self.this_kind == 'file' and self.other_kind == 'file'
 
189
 
 
190
    @decorators.cachedproperty
 
191
    def base_lines(self):
 
192
        """The lines of the 'base' version of the file."""
 
193
        return self._merger.get_lines(self._merger.base_tree, self.file_id)
 
194
 
 
195
    @decorators.cachedproperty
 
196
    def this_lines(self):
 
197
        """The lines of the 'this' version of the file."""
 
198
        return self._merger.get_lines(self._merger.this_tree, self.file_id)
 
199
 
 
200
    @decorators.cachedproperty
 
201
    def other_lines(self):
 
202
        """The lines of the 'other' version of the file."""
 
203
        return self._merger.get_lines(self._merger.other_tree, self.file_id)
69
204
 
70
205
 
71
206
class Merger(object):
 
207
 
 
208
    hooks = MergeHooks()
 
209
 
72
210
    def __init__(self, this_branch, other_tree=None, base_tree=None,
73
211
                 this_tree=None, pb=None, change_reporter=None,
74
212
                 recurse='down', revision_graph=None):
90
228
        self.show_base = False
91
229
        self.reprocess = False
92
230
        if pb is None:
93
 
            pb = DummyProgress()
 
231
            pb = progress.DummyProgress()
94
232
        self._pb = pb
95
233
        self.pp = None
96
234
        self.recurse = recurse
102
240
        self._is_criss_cross = None
103
241
        self._lca_trees = None
104
242
 
 
243
    def cache_trees_with_revision_ids(self, trees):
 
244
        """Cache any tree in trees if it has a revision_id."""
 
245
        for maybe_tree in trees:
 
246
            if maybe_tree is None:
 
247
                continue
 
248
            try:
 
249
                rev_id = maybe_tree.get_revision_id()
 
250
            except AttributeError:
 
251
                continue
 
252
            self._cached_trees[rev_id] = maybe_tree
 
253
 
105
254
    @property
106
255
    def revision_graph(self):
107
256
        if self._revision_graph is None:
169
318
                base_revision_id, tree.branch.last_revision())):
170
319
                base_revision_id = None
171
320
            else:
172
 
                warning('Performing cherrypick')
 
321
                trace.warning('Performing cherrypick')
173
322
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
174
323
                                         base_revision_id, revision_graph=
175
324
                                         revision_graph)
227
376
        if revno is None:
228
377
            tree = workingtree.WorkingTree.open_containing(location)[0]
229
378
            return tree.branch, tree
230
 
        branch = Branch.open_containing(location, possible_transports)[0]
 
379
        branch = _mod_branch.Branch.open_containing(
 
380
            location, possible_transports)[0]
231
381
        if revno == -1:
232
382
            revision_id = branch.last_revision()
233
383
        else:
234
384
            revision_id = branch.get_rev_id(revno)
235
 
        revision_id = ensure_null(revision_id)
 
385
        revision_id = _mod_revision.ensure_null(revision_id)
236
386
        return branch, self.revision_tree(revision_id, branch)
237
387
 
 
388
    @deprecated_method(deprecated_in((2, 1, 0)))
238
389
    def ensure_revision_trees(self):
239
390
        if self.this_revision_tree is None:
240
391
            self.this_basis_tree = self.revision_tree(self.this_basis)
243
394
 
244
395
        if self.other_rev_id is None:
245
396
            other_basis_tree = self.revision_tree(self.other_basis)
246
 
            changes = other_basis_tree.changes_from(self.other_tree)
247
 
            if changes.has_changed():
248
 
                raise WorkingTreeNotRevision(self.this_tree)
 
397
            if other_basis_tree.has_changes(self.other_tree):
 
398
                raise errors.WorkingTreeNotRevision(self.this_tree)
249
399
            other_rev_id = self.other_basis
250
400
            self.other_tree = other_basis_tree
251
401
 
 
402
    @deprecated_method(deprecated_in((2, 1, 0)))
252
403
    def file_revisions(self, file_id):
253
404
        self.ensure_revision_trees()
254
405
        def get_id(tree, file_id):
257
408
        if self.this_rev_id is None:
258
409
            if self.this_basis_tree.get_file_sha1(file_id) != \
259
410
                self.this_tree.get_file_sha1(file_id):
260
 
                raise WorkingTreeNotRevision(self.this_tree)
 
411
                raise errors.WorkingTreeNotRevision(self.this_tree)
261
412
 
262
413
        trees = (self.this_basis_tree, self.other_tree)
263
414
        return [get_id(tree, file_id) for tree in trees]
264
415
 
 
416
    @deprecated_method(deprecated_in((2, 1, 0)))
265
417
    def check_basis(self, check_clean, require_commits=True):
266
418
        if self.this_basis is None and require_commits is True:
267
 
            raise BzrCommandError("This branch has no commits."
268
 
                                  " (perhaps you would prefer 'bzr pull')")
 
419
            raise errors.BzrCommandError(
 
420
                "This branch has no commits."
 
421
                " (perhaps you would prefer 'bzr pull')")
269
422
        if check_clean:
270
423
            self.compare_basis()
271
424
            if self.this_basis != self.this_rev_id:
272
425
                raise errors.UncommittedChanges(self.this_tree)
273
426
 
 
427
    @deprecated_method(deprecated_in((2, 1, 0)))
274
428
    def compare_basis(self):
275
429
        try:
276
430
            basis_tree = self.revision_tree(self.this_tree.last_revision())
277
431
        except errors.NoSuchRevision:
278
432
            basis_tree = self.this_tree.basis_tree()
279
 
        changes = self.this_tree.changes_from(basis_tree)
280
 
        if not changes.has_changed():
 
433
        if not self.this_tree.has_changes(basis_tree):
281
434
            self.this_rev_id = self.this_basis
282
435
 
283
436
    def set_interesting_files(self, file_list):
284
437
        self.interesting_files = file_list
285
438
 
286
439
    def set_pending(self):
287
 
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
 
440
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
 
441
            or self.other_rev_id is None):
288
442
            return
289
443
        self._add_parent()
290
444
 
320
474
            self.other_rev_id = _mod_revision.ensure_null(
321
475
                self.other_branch.last_revision())
322
476
            if _mod_revision.is_null(self.other_rev_id):
323
 
                raise NoCommits(self.other_branch)
 
477
                raise errors.NoCommits(self.other_branch)
324
478
            self.other_basis = self.other_rev_id
325
479
        elif other_revision[1] is not None:
326
480
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
329
483
            self.other_rev_id = None
330
484
            self.other_basis = self.other_branch.last_revision()
331
485
            if self.other_basis is None:
332
 
                raise NoCommits(self.other_branch)
 
486
                raise errors.NoCommits(self.other_branch)
333
487
        if self.other_rev_id is not None:
334
488
            self._cached_trees[self.other_rev_id] = self.other_tree
335
489
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
362
516
            target.fetch(source, revision_id)
363
517
 
364
518
    def find_base(self):
365
 
        revisions = [ensure_null(self.this_basis),
366
 
                     ensure_null(self.other_basis)]
367
 
        if NULL_REVISION in revisions:
368
 
            self.base_rev_id = NULL_REVISION
 
519
        revisions = [_mod_revision.ensure_null(self.this_basis),
 
520
                     _mod_revision.ensure_null(self.other_basis)]
 
521
        if _mod_revision.NULL_REVISION in revisions:
 
522
            self.base_rev_id = _mod_revision.NULL_REVISION
369
523
            self.base_tree = self.revision_tree(self.base_rev_id)
370
524
            self._is_criss_cross = False
371
525
        else:
372
526
            lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
373
527
            self._is_criss_cross = False
374
528
            if len(lcas) == 0:
375
 
                self.base_rev_id = NULL_REVISION
 
529
                self.base_rev_id = _mod_revision.NULL_REVISION
376
530
            elif len(lcas) == 1:
377
531
                self.base_rev_id = list(lcas)[0]
378
532
            else: # len(lcas) > 1
387
541
                    self.base_rev_id = self.revision_graph.find_unique_lca(
388
542
                                            *lcas)
389
543
                self._is_criss_cross = True
390
 
            if self.base_rev_id == NULL_REVISION:
391
 
                raise UnrelatedBranches()
 
544
            if self.base_rev_id == _mod_revision.NULL_REVISION:
 
545
                raise errors.UnrelatedBranches()
392
546
            if self._is_criss_cross:
393
 
                warning('Warning: criss-cross merge encountered.  See bzr'
394
 
                        ' help criss-cross.')
395
 
                mutter('Criss-cross lcas: %r' % lcas)
 
547
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
 
548
                              ' help criss-cross.')
 
549
                trace.mutter('Criss-cross lcas: %r' % lcas)
396
550
                interesting_revision_ids = [self.base_rev_id]
397
551
                interesting_revision_ids.extend(lcas)
398
552
                interesting_trees = dict((t.get_revision_id(), t)
408
562
                self.base_tree = self.revision_tree(self.base_rev_id)
409
563
        self.base_is_ancestor = True
410
564
        self.base_is_other_ancestor = True
411
 
        mutter('Base revid: %r' % self.base_rev_id)
 
565
        trace.mutter('Base revid: %r' % self.base_rev_id)
412
566
 
413
567
    def set_base(self, base_revision):
414
568
        """Set the base revision to use for the merge.
415
569
 
416
570
        :param base_revision: A 2-list containing a path and revision number.
417
571
        """
418
 
        mutter("doing merge() with no base_revision specified")
 
572
        trace.mutter("doing merge() with no base_revision specified")
419
573
        if base_revision == [None, None]:
420
574
            self.find_base()
421
575
        else:
434
588
                  'other_tree': self.other_tree,
435
589
                  'interesting_ids': self.interesting_ids,
436
590
                  'interesting_files': self.interesting_files,
437
 
                  'pp': self.pp,
 
591
                  'pp': self.pp, 'this_branch': self.this_branch,
438
592
                  'do_merge': False}
439
593
        if self.merge_type.requires_base:
440
594
            kwargs['base_tree'] = self.base_tree
441
595
        if self.merge_type.supports_reprocess:
442
596
            kwargs['reprocess'] = self.reprocess
443
597
        elif self.reprocess:
444
 
            raise BzrError("Conflict reduction is not supported for merge"
445
 
                                  " type %s." % self.merge_type)
 
598
            raise errors.BzrError(
 
599
                "Conflict reduction is not supported for merge"
 
600
                " type %s." % self.merge_type)
446
601
        if self.merge_type.supports_show_base:
447
602
            kwargs['show_base'] = self.show_base
448
603
        elif self.show_base:
449
 
            raise BzrError("Showing base is not supported for this"
450
 
                           " merge type. %s" % self.merge_type)
 
604
            raise errors.BzrError("Showing base is not supported for this"
 
605
                                  " merge type. %s" % self.merge_type)
451
606
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
452
607
            and not self.base_is_other_ancestor):
453
608
            raise errors.CannotReverseCherrypick()
462
617
                               **kwargs)
463
618
 
464
619
    def _do_merge_to(self, merge):
 
620
        if self.other_branch is not None:
 
621
            self.other_branch.update_references(self.this_branch)
465
622
        merge.do_merge()
466
623
        if self.recurse == 'down':
467
624
            for relpath, file_id in self.this_tree.iter_references():
500
657
        finally:
501
658
            self.this_tree.unlock()
502
659
        if len(merge.cooked_conflicts) == 0:
503
 
            if not self.ignore_zero and not is_quiet():
504
 
                note("All changes applied successfully.")
 
660
            if not self.ignore_zero and not trace.is_quiet():
 
661
                trace.note("All changes applied successfully.")
505
662
        else:
506
 
            note("%d conflicts encountered." % len(merge.cooked_conflicts))
 
663
            trace.note("%d conflicts encountered."
 
664
                       % len(merge.cooked_conflicts))
507
665
 
508
666
        return len(merge.cooked_conflicts)
509
667
 
538
696
 
539
697
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
540
698
                 interesting_ids=None, reprocess=False, show_base=False,
541
 
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
699
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
542
700
                 interesting_files=None, do_merge=True,
543
 
                 cherrypick=False, lca_trees=None):
 
701
                 cherrypick=False, lca_trees=None, this_branch=None):
544
702
        """Initialize the merger object and perform the merge.
545
703
 
546
704
        :param working_tree: The working tree to apply the merge to
547
705
        :param this_tree: The local tree in the merge operation
548
706
        :param base_tree: The common tree in the merge operation
549
 
        :param other_tree: The other other tree to merge changes from
 
707
        :param other_tree: The other tree to merge changes from
 
708
        :param this_branch: The branch associated with this_tree
550
709
        :param interesting_ids: The file_ids of files that should be
551
710
            participate in the merge.  May not be combined with
552
711
            interesting_files.
575
734
        self.this_tree = working_tree
576
735
        self.base_tree = base_tree
577
736
        self.other_tree = other_tree
 
737
        self.this_branch = this_branch
578
738
        self._raw_conflicts = []
579
739
        self.cooked_conflicts = []
580
740
        self.reprocess = reprocess
590
750
        self.change_reporter = change_reporter
591
751
        self.cherrypick = cherrypick
592
752
        if self.pp is None:
593
 
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
 
753
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
594
754
        if do_merge:
595
755
            self.do_merge()
596
756
 
598
758
        self.this_tree.lock_tree_write()
599
759
        self.base_tree.lock_read()
600
760
        self.other_tree.lock_read()
601
 
        self.tt = TreeTransform(self.this_tree, self.pb)
602
761
        try:
603
 
            self.pp.next_phase()
604
 
            self._compute_transform()
605
 
            self.pp.next_phase()
606
 
            results = self.tt.apply(no_conflicts=True)
607
 
            self.write_modified(results)
 
762
            self.tt = transform.TreeTransform(self.this_tree, self.pb)
608
763
            try:
609
 
                self.this_tree.add_conflicts(self.cooked_conflicts)
610
 
            except UnsupportedOperation:
611
 
                pass
 
764
                self.pp.next_phase()
 
765
                self._compute_transform()
 
766
                self.pp.next_phase()
 
767
                results = self.tt.apply(no_conflicts=True)
 
768
                self.write_modified(results)
 
769
                try:
 
770
                    self.this_tree.add_conflicts(self.cooked_conflicts)
 
771
                except errors.UnsupportedOperation:
 
772
                    pass
 
773
            finally:
 
774
                self.tt.finalize()
612
775
        finally:
613
 
            self.tt.finalize()
614
776
            self.other_tree.unlock()
615
777
            self.base_tree.unlock()
616
778
            self.this_tree.unlock()
619
781
    def make_preview_transform(self):
620
782
        self.base_tree.lock_read()
621
783
        self.other_tree.lock_read()
622
 
        self.tt = TransformPreview(self.this_tree)
 
784
        self.tt = transform.TransformPreview(self.this_tree)
623
785
        try:
624
786
            self.pp.next_phase()
625
787
            self._compute_transform()
639
801
            resolver = self._lca_multi_way
640
802
        child_pb = ui.ui_factory.nested_progress_bar()
641
803
        try:
 
804
            factories = Merger.hooks['merge_file_content']
 
805
            hooks = [factory(self) for factory in factories] + [self]
 
806
            self.active_hooks = [hook for hook in hooks if hook is not None]
642
807
            for num, (file_id, changed, parents3, names3,
643
808
                      executable3) in enumerate(entries):
644
809
                child_pb.update('Preparing file merge', num, len(entries))
645
810
                self._merge_names(file_id, parents3, names3, resolver=resolver)
646
811
                if changed:
647
 
                    file_status = self.merge_contents(file_id)
 
812
                    file_status = self._do_merge_contents(file_id)
648
813
                else:
649
814
                    file_status = 'unmodified'
650
815
                self._merge_executable(file_id,
655
820
        self.pp.next_phase()
656
821
        child_pb = ui.ui_factory.nested_progress_bar()
657
822
        try:
658
 
            fs_conflicts = resolve_conflicts(self.tt, child_pb,
659
 
                lambda t, c: conflict_pass(t, c, self.other_tree))
 
823
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
 
824
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
660
825
        finally:
661
826
            child_pb.finished()
662
827
        if self.change_reporter is not None:
665
830
                self.tt.iter_changes(), self.change_reporter)
666
831
        self.cook_conflicts(fs_conflicts)
667
832
        for conflict in self.cooked_conflicts:
668
 
            warning(conflict)
 
833
            trace.warning(conflict)
669
834
 
670
835
    def _entries3(self):
671
836
        """Gather data about files modified between three trees.
873
1038
    def fix_root(self):
874
1039
        try:
875
1040
            self.tt.final_kind(self.tt.root)
876
 
        except NoSuchFile:
 
1041
        except errors.NoSuchFile:
877
1042
            self.tt.cancel_deletion(self.tt.root)
878
1043
        if self.tt.final_file_id(self.tt.root) is None:
879
1044
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
886
1051
            return
887
1052
        try:
888
1053
            self.tt.final_kind(other_root)
889
 
        except NoSuchFile:
 
1054
        except errors.NoSuchFile:
890
1055
            return
891
 
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
 
1056
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
892
1057
            # the other tree's root is a non-root in the current tree
893
1058
            return
894
1059
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
936
1101
    @staticmethod
937
1102
    def executable(tree, file_id):
938
1103
        """Determine the executability of a file-id (used as a key method)."""
939
 
        if file_id not in tree:
 
1104
        if not tree.has_id(file_id):
940
1105
            return None
941
1106
        if tree.kind(file_id) != "file":
942
1107
            return False
945
1110
    @staticmethod
946
1111
    def kind(tree, file_id):
947
1112
        """Determine the kind of a file-id (used as a key method)."""
948
 
        if file_id not in tree:
 
1113
        if not tree.has_id(file_id):
949
1114
            return None
950
1115
        return tree.kind(file_id)
951
1116
 
1034
1199
 
1035
1200
    def merge_names(self, file_id):
1036
1201
        def get_entry(tree):
1037
 
            if file_id in tree.inventory:
 
1202
            if tree.has_id(file_id):
1038
1203
                return tree.inventory[file_id]
1039
1204
            else:
1040
1205
                return None
1089
1254
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1090
1255
                                parent_trans_id, trans_id)
1091
1256
 
1092
 
    def merge_contents(self, file_id):
 
1257
    def _do_merge_contents(self, file_id):
1093
1258
        """Performs a merge on file_id contents."""
1094
1259
        def contents_pair(tree):
1095
1260
            if file_id not in tree:
1103
1268
                contents = None
1104
1269
            return kind, contents
1105
1270
 
1106
 
        def contents_conflict():
1107
 
            trans_id = self.tt.trans_id_file_id(file_id)
1108
 
            name = self.tt.final_name(trans_id)
1109
 
            parent_id = self.tt.final_parent(trans_id)
1110
 
            if file_id in self.this_tree.inventory:
1111
 
                self.tt.unversion_file(trans_id)
1112
 
                if file_id in self.this_tree:
1113
 
                    self.tt.delete_contents(trans_id)
1114
 
            file_group = self._dump_conflicts(name, parent_id, file_id,
1115
 
                                              set_version=True)
1116
 
            self._raw_conflicts.append(('contents conflict', file_group))
1117
 
 
1118
1271
        # See SPOT run.  run, SPOT, run.
1119
1272
        # So we're not QUITE repeating ourselves; we do tricky things with
1120
1273
        # file kind...
1136
1289
        if winner == 'this':
1137
1290
            # No interesting changes introduced by OTHER
1138
1291
            return "unmodified"
 
1292
        # We have a hypothetical conflict, but if we have files, then we
 
1293
        # can try to merge the content
1139
1294
        trans_id = self.tt.trans_id_file_id(file_id)
1140
 
        if winner == 'other':
 
1295
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
 
1296
            other_pair[0], winner)
 
1297
        hooks = self.active_hooks
 
1298
        hook_status = 'not_applicable'
 
1299
        for hook in hooks:
 
1300
            hook_status, lines = hook.merge_contents(params)
 
1301
            if hook_status != 'not_applicable':
 
1302
                # Don't try any more hooks, this one applies.
 
1303
                break
 
1304
        result = "modified"
 
1305
        if hook_status == 'not_applicable':
 
1306
            # This is a contents conflict, because none of the available
 
1307
            # functions could merge it.
 
1308
            result = None
 
1309
            name = self.tt.final_name(trans_id)
 
1310
            parent_id = self.tt.final_parent(trans_id)
 
1311
            if self.this_tree.has_id(file_id):
 
1312
                self.tt.unversion_file(trans_id)
 
1313
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1314
                                              set_version=True)
 
1315
            self._raw_conflicts.append(('contents conflict', file_group))
 
1316
        elif hook_status == 'success':
 
1317
            self.tt.create_file(lines, trans_id)
 
1318
        elif hook_status == 'conflicted':
 
1319
            # XXX: perhaps the hook should be able to provide
 
1320
            # the BASE/THIS/OTHER files?
 
1321
            self.tt.create_file(lines, trans_id)
 
1322
            self._raw_conflicts.append(('text conflict', trans_id))
 
1323
            name = self.tt.final_name(trans_id)
 
1324
            parent_id = self.tt.final_parent(trans_id)
 
1325
            self._dump_conflicts(name, parent_id, file_id)
 
1326
        elif hook_status == 'delete':
 
1327
            self.tt.unversion_file(trans_id)
 
1328
            result = "deleted"
 
1329
        elif hook_status == 'done':
 
1330
            # The hook function did whatever it needs to do directly, no
 
1331
            # further action needed here.
 
1332
            pass
 
1333
        else:
 
1334
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
 
1335
        if not self.this_tree.has_id(file_id) and result == "modified":
 
1336
            self.tt.version_file(file_id, trans_id)
 
1337
        # The merge has been performed, so the old contents should not be
 
1338
        # retained.
 
1339
        try:
 
1340
            self.tt.delete_contents(trans_id)
 
1341
        except errors.NoSuchFile:
 
1342
            pass
 
1343
        return result
 
1344
 
 
1345
    def _default_other_winner_merge(self, merge_hook_params):
 
1346
        """Replace this contents with other."""
 
1347
        file_id = merge_hook_params.file_id
 
1348
        trans_id = merge_hook_params.trans_id
 
1349
        file_in_this = self.this_tree.has_id(file_id)
 
1350
        if self.other_tree.has_id(file_id):
 
1351
            # OTHER changed the file
 
1352
            wt = self.this_tree
 
1353
            if wt.supports_content_filtering():
 
1354
                # We get the path from the working tree if it exists.
 
1355
                # That fails though when OTHER is adding a file, so
 
1356
                # we fall back to the other tree to find the path if
 
1357
                # it doesn't exist locally.
 
1358
                try:
 
1359
                    filter_tree_path = wt.id2path(file_id)
 
1360
                except errors.NoSuchId:
 
1361
                    filter_tree_path = self.other_tree.id2path(file_id)
 
1362
            else:
 
1363
                # Skip the id2path lookup for older formats
 
1364
                filter_tree_path = None
 
1365
            transform.create_from_tree(self.tt, trans_id,
 
1366
                             self.other_tree, file_id,
 
1367
                             filter_tree_path=filter_tree_path)
 
1368
            return 'done', None
 
1369
        elif file_in_this:
 
1370
            # OTHER deleted the file
 
1371
            return 'delete', None
 
1372
        else:
 
1373
            raise AssertionError(
 
1374
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
 
1375
                % (file_id,))
 
1376
 
 
1377
    def merge_contents(self, merge_hook_params):
 
1378
        """Fallback merge logic after user installed hooks."""
 
1379
        # This function is used in merge hooks as the fallback instance.
 
1380
        # Perhaps making this function and the functions it calls be a 
 
1381
        # a separate class would be better.
 
1382
        if merge_hook_params.winner == 'other':
1141
1383
            # OTHER is a straight winner, so replace this contents with other
1142
 
            file_in_this = file_id in self.this_tree
1143
 
            if file_in_this:
1144
 
                # Remove any existing contents
1145
 
                self.tt.delete_contents(trans_id)
1146
 
            if file_id in self.other_tree:
1147
 
                # OTHER changed the file
1148
 
                create_from_tree(self.tt, trans_id,
1149
 
                                 self.other_tree, file_id)
1150
 
                if not file_in_this:
1151
 
                    self.tt.version_file(file_id, trans_id)
1152
 
                return "modified"
1153
 
            elif file_in_this:
1154
 
                # OTHER deleted the file
1155
 
                self.tt.unversion_file(trans_id)
1156
 
                return "deleted"
 
1384
            return self._default_other_winner_merge(merge_hook_params)
 
1385
        elif merge_hook_params.is_file_merge():
 
1386
            # THIS and OTHER are both files, so text merge.  Either
 
1387
            # BASE is a file, or both converted to files, so at least we
 
1388
            # have agreement that output should be a file.
 
1389
            try:
 
1390
                self.text_merge(merge_hook_params.file_id,
 
1391
                    merge_hook_params.trans_id)
 
1392
            except errors.BinaryFile:
 
1393
                return 'not_applicable', None
 
1394
            return 'done', None
1157
1395
        else:
1158
 
            # We have a hypothetical conflict, but if we have files, then we
1159
 
            # can try to merge the content
1160
 
            if this_pair[0] == 'file' and other_pair[0] == 'file':
1161
 
                # THIS and OTHER are both files, so text merge.  Either
1162
 
                # BASE is a file, or both converted to files, so at least we
1163
 
                # have agreement that output should be a file.
1164
 
                try:
1165
 
                    self.text_merge(file_id, trans_id)
1166
 
                except BinaryFile:
1167
 
                    return contents_conflict()
1168
 
                if file_id not in self.this_tree:
1169
 
                    self.tt.version_file(file_id, trans_id)
1170
 
                try:
1171
 
                    self.tt.tree_kind(trans_id)
1172
 
                    self.tt.delete_contents(trans_id)
1173
 
                except NoSuchFile:
1174
 
                    pass
1175
 
                return "modified"
1176
 
            else:
1177
 
                return contents_conflict()
 
1396
            return 'not_applicable', None
1178
1397
 
1179
1398
    def get_lines(self, tree, file_id):
1180
1399
        """Return the lines in a file, or an empty list."""
1181
 
        if file_id in tree:
 
1400
        if tree.has_id(file_id):
1182
1401
            return tree.get_file(file_id).readlines()
1183
1402
        else:
1184
1403
            return []
1187
1406
        """Perform a three-way text merge on a file_id"""
1188
1407
        # it's possible that we got here with base as a different type.
1189
1408
        # if so, we just want two-way text conflicts.
1190
 
        if file_id in self.base_tree and \
 
1409
        if self.base_tree.has_id(file_id) and \
1191
1410
            self.base_tree.kind(file_id) == "file":
1192
1411
            base_lines = self.get_lines(self.base_tree, file_id)
1193
1412
        else:
1194
1413
            base_lines = []
1195
1414
        other_lines = self.get_lines(self.other_tree, file_id)
1196
1415
        this_lines = self.get_lines(self.this_tree, file_id)
1197
 
        m3 = Merge3(base_lines, this_lines, other_lines,
1198
 
                    is_cherrypick=self.cherrypick)
 
1416
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
 
1417
                           is_cherrypick=self.cherrypick)
1199
1418
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1200
1419
        if self.show_base is True:
1201
1420
            base_marker = '|' * 7
1239
1458
                ('THIS', self.this_tree, this_lines)]
1240
1459
        if not no_base:
1241
1460
            data.append(('BASE', self.base_tree, base_lines))
 
1461
 
 
1462
        # We need to use the actual path in the working tree of the file here,
 
1463
        # ignoring the conflict suffixes
 
1464
        wt = self.this_tree
 
1465
        if wt.supports_content_filtering():
 
1466
            try:
 
1467
                filter_tree_path = wt.id2path(file_id)
 
1468
            except errors.NoSuchId:
 
1469
                # file has been deleted
 
1470
                filter_tree_path = None
 
1471
        else:
 
1472
            # Skip the id2path lookup for older formats
 
1473
            filter_tree_path = None
 
1474
 
1242
1475
        versioned = False
1243
1476
        file_group = []
1244
1477
        for suffix, tree, lines in data:
1245
 
            if file_id in tree:
 
1478
            if tree.has_id(file_id):
1246
1479
                trans_id = self._conflict_file(name, parent_id, tree, file_id,
1247
 
                                               suffix, lines)
 
1480
                                               suffix, lines, filter_tree_path)
1248
1481
                file_group.append(trans_id)
1249
1482
                if set_version and not versioned:
1250
1483
                    self.tt.version_file(file_id, trans_id)
1252
1485
        return file_group
1253
1486
 
1254
1487
    def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1255
 
                       lines=None):
 
1488
                       lines=None, filter_tree_path=None):
1256
1489
        """Emit a single conflict file."""
1257
1490
        name = name + '.' + suffix
1258
1491
        trans_id = self.tt.create_path(name, parent_id)
1259
 
        create_from_tree(self.tt, trans_id, tree, file_id, lines)
 
1492
        transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
 
1493
            filter_tree_path)
1260
1494
        return trans_id
1261
1495
 
1262
1496
    def merge_executable(self, file_id, file_status):
1286
1520
        try:
1287
1521
            if self.tt.final_kind(trans_id) != "file":
1288
1522
                return
1289
 
        except NoSuchFile:
 
1523
        except errors.NoSuchFile:
1290
1524
            return
1291
1525
        if winner == "this":
1292
1526
            executability = this_executable
1293
1527
        else:
1294
 
            if file_id in self.other_tree:
 
1528
            if self.other_tree.has_id(file_id):
1295
1529
                executability = other_executable
1296
 
            elif file_id in self.this_tree:
 
1530
            elif self.this_tree.has_id(file_id):
1297
1531
                executability = this_executable
1298
 
            elif file_id in self.base_tree:
 
1532
            elif self.base_tree_has_id(file_id):
1299
1533
                executability = base_executable
1300
1534
        if executability is not None:
1301
1535
            trans_id = self.tt.trans_id_file_id(file_id)
1303
1537
 
1304
1538
    def cook_conflicts(self, fs_conflicts):
1305
1539
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1306
 
        from conflicts import Conflict
1307
1540
        name_conflicts = {}
1308
 
        self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1309
 
        fp = FinalPaths(self.tt)
 
1541
        self.cooked_conflicts.extend(transform.cook_conflicts(
 
1542
                fs_conflicts, self.tt))
 
1543
        fp = transform.FinalPaths(self.tt)
1310
1544
        for conflict in self._raw_conflicts:
1311
1545
            conflict_type = conflict[0]
1312
1546
            if conflict_type in ('name conflict', 'parent conflict'):
1314
1548
                conflict_args = conflict[2:]
1315
1549
                if trans_id not in name_conflicts:
1316
1550
                    name_conflicts[trans_id] = {}
1317
 
                unique_add(name_conflicts[trans_id], conflict_type,
1318
 
                           conflict_args)
 
1551
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1552
                                     conflict_args)
1319
1553
            if conflict_type == 'contents conflict':
1320
1554
                for trans_id in conflict[1]:
1321
1555
                    file_id = self.tt.final_file_id(trans_id)
1326
1560
                    if path.endswith(suffix):
1327
1561
                        path = path[:-len(suffix)]
1328
1562
                        break
1329
 
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
1563
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1564
                                                    path=path, file_id=file_id)
1330
1565
                self.cooked_conflicts.append(c)
1331
1566
            if conflict_type == 'text conflict':
1332
1567
                trans_id = conflict[1]
1333
1568
                path = fp.get_path(trans_id)
1334
1569
                file_id = self.tt.final_file_id(trans_id)
1335
 
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
1570
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1571
                                                    path=path, file_id=file_id)
1336
1572
                self.cooked_conflicts.append(c)
1337
1573
 
1338
1574
        for trans_id, conflicts in name_conflicts.iteritems():
1353
1589
            if this_parent is not None and this_name is not None:
1354
1590
                this_parent_path = \
1355
1591
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
1356
 
                this_path = pathjoin(this_parent_path, this_name)
 
1592
                this_path = osutils.pathjoin(this_parent_path, this_name)
1357
1593
            else:
1358
1594
                this_path = "<deleted>"
1359
1595
            file_id = self.tt.final_file_id(trans_id)
1360
 
            c = Conflict.factory('path conflict', path=this_path,
1361
 
                                 conflict_path=other_path, file_id=file_id)
 
1596
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1597
                                                conflict_path=other_path,
 
1598
                                                file_id=file_id)
1362
1599
            self.cooked_conflicts.append(c)
1363
 
        self.cooked_conflicts.sort(key=Conflict.sort_key)
 
1600
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1364
1601
 
1365
1602
 
1366
1603
class WeaveMerger(Merge3Merger):
1370
1607
    supports_reverse_cherrypick = False
1371
1608
    history_based = True
1372
1609
 
1373
 
    def _merged_lines(self, file_id):
1374
 
        """Generate the merged lines.
1375
 
        There is no distinction between lines that are meant to contain <<<<<<<
1376
 
        and conflicts.
1377
 
        """
1378
 
        if self.cherrypick:
1379
 
            base = self.base_tree
1380
 
        else:
1381
 
            base = None
1382
 
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
 
1610
    def _generate_merge_plan(self, file_id, base):
 
1611
        return self.this_tree.plan_file_merge(file_id, self.other_tree,
1383
1612
                                              base=base)
 
1613
 
 
1614
    def _merged_lines(self, file_id):
 
1615
        """Generate the merged lines.
 
1616
        There is no distinction between lines that are meant to contain <<<<<<<
 
1617
        and conflicts.
 
1618
        """
 
1619
        if self.cherrypick:
 
1620
            base = self.base_tree
 
1621
        else:
 
1622
            base = None
 
1623
        plan = self._generate_merge_plan(file_id, base)
1384
1624
        if 'merge' in debug.debug_flags:
1385
1625
            plan = list(plan)
1386
1626
            trans_id = self.tt.trans_id_file_id(file_id)
1387
1627
            name = self.tt.final_name(trans_id) + '.plan'
1388
 
            contents = ('%10s|%s' % l for l in plan)
 
1628
            contents = ('%11s|%s' % l for l in plan)
1389
1629
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1390
 
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1391
 
            '>>>>>>> MERGE-SOURCE\n')
1392
 
        return textmerge.merge_lines(self.reprocess)
 
1630
        textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
1631
                                                 '>>>>>>> MERGE-SOURCE\n')
 
1632
        lines, conflicts = textmerge.merge_lines(self.reprocess)
 
1633
        if conflicts:
 
1634
            base_lines = textmerge.base_from_plan()
 
1635
        else:
 
1636
            base_lines = None
 
1637
        return lines, base_lines
1393
1638
 
1394
1639
    def text_merge(self, file_id, trans_id):
1395
1640
        """Perform a (weave) text merge for a given file and file-id.
1396
1641
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1397
1642
        and a conflict will be noted.
1398
1643
        """
1399
 
        lines, conflicts = self._merged_lines(file_id)
 
1644
        lines, base_lines = self._merged_lines(file_id)
1400
1645
        lines = list(lines)
1401
1646
        # Note we're checking whether the OUTPUT is binary in this case,
1402
1647
        # because we don't want to get into weave merge guts.
1403
 
        check_text_lines(lines)
 
1648
        textfile.check_text_lines(lines)
1404
1649
        self.tt.create_file(lines, trans_id)
1405
 
        if conflicts:
 
1650
        if base_lines is not None:
 
1651
            # Conflict
1406
1652
            self._raw_conflicts.append(('text conflict', trans_id))
1407
1653
            name = self.tt.final_name(trans_id)
1408
1654
            parent_id = self.tt.final_parent(trans_id)
1409
1655
            file_group = self._dump_conflicts(name, parent_id, file_id,
1410
 
                                              no_base=True)
 
1656
                                              no_base=False,
 
1657
                                              base_lines=base_lines)
1411
1658
            file_group.append(trans_id)
1412
1659
 
1413
1660
 
1414
1661
class LCAMerger(WeaveMerger):
1415
1662
 
1416
 
    def _merged_lines(self, file_id):
1417
 
        """Generate the merged lines.
1418
 
        There is no distinction between lines that are meant to contain <<<<<<<
1419
 
        and conflicts.
1420
 
        """
1421
 
        if self.cherrypick:
1422
 
            base = self.base_tree
1423
 
        else:
1424
 
            base = None
1425
 
        plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
 
1663
    def _generate_merge_plan(self, file_id, base):
 
1664
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1426
1665
                                                  base=base)
1427
 
        if 'merge' in debug.debug_flags:
1428
 
            plan = list(plan)
1429
 
            trans_id = self.tt.trans_id_file_id(file_id)
1430
 
            name = self.tt.final_name(trans_id) + '.plan'
1431
 
            contents = ('%10s|%s' % l for l in plan)
1432
 
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1433
 
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1434
 
            '>>>>>>> MERGE-SOURCE\n')
1435
 
        return textmerge.merge_lines(self.reprocess)
1436
 
 
1437
1666
 
1438
1667
class Diff3Merger(Merge3Merger):
1439
1668
    """Three-way merger using external diff3 for text merging"""
1440
1669
 
1441
1670
    def dump_file(self, temp_dir, name, tree, file_id):
1442
 
        out_path = pathjoin(temp_dir, name)
 
1671
        out_path = osutils.pathjoin(temp_dir, name)
1443
1672
        out_file = open(out_path, "wb")
1444
1673
        try:
1445
1674
            in_file = tree.get_file(file_id)
1457
1686
        import bzrlib.patch
1458
1687
        temp_dir = osutils.mkdtemp(prefix="bzr-")
1459
1688
        try:
1460
 
            new_file = pathjoin(temp_dir, "new")
 
1689
            new_file = osutils.pathjoin(temp_dir, "new")
1461
1690
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1462
1691
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1463
1692
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1464
1693
            status = bzrlib.patch.diff3(new_file, this, base, other)
1465
1694
            if status not in (0, 1):
1466
 
                raise BzrError("Unhandled diff3 exit code")
 
1695
                raise errors.BzrError("Unhandled diff3 exit code")
1467
1696
            f = open(new_file, 'rb')
1468
1697
            try:
1469
1698
                self.tt.create_file(f, trans_id)
1487
1716
                other_rev_id=None,
1488
1717
                interesting_files=None,
1489
1718
                this_tree=None,
1490
 
                pb=DummyProgress(),
 
1719
                pb=progress.DummyProgress(),
1491
1720
                change_reporter=None):
1492
1721
    """Primary interface for merging.
1493
1722
 
1496
1725
                     branch.get_revision_tree(base_revision))'
1497
1726
        """
1498
1727
    if this_tree is None:
1499
 
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1500
 
            "parameter as of bzrlib version 0.8.")
 
1728
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1729
                              "parameter as of bzrlib version 0.8.")
1501
1730
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1502
1731
                    pb=pb, change_reporter=change_reporter)
1503
1732
    merger.backup_files = backup_files
1516
1745
    get_revision_id = getattr(base_tree, 'get_revision_id', None)
1517
1746
    if get_revision_id is None:
1518
1747
        get_revision_id = base_tree.last_revision
 
1748
    merger.cache_trees_with_revision_ids([other_tree, base_tree, this_tree])
1519
1749
    merger.set_base_revision(get_revision_id(), this_branch)
1520
1750
    return merger.do_merge()
1521
1751
 
1720
1950
        super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
1721
1951
        self.a_key = self._key_prefix + (self.a_rev,)
1722
1952
        self.b_key = self._key_prefix + (self.b_rev,)
1723
 
        self.graph = Graph(self.vf)
 
1953
        self.graph = _mod_graph.Graph(self.vf)
1724
1954
        heads = self.graph.heads((self.a_key, self.b_key))
1725
1955
        if len(heads) == 1:
1726
1956
            # one side dominates, so we can just return its values, yay for
1731
1961
                other = b_rev
1732
1962
            else:
1733
1963
                other = a_rev
1734
 
            mutter('found dominating revision for %s\n%s > %s', self.vf,
1735
 
                   self._head_key[-1], other)
 
1964
            trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
 
1965
                         self._head_key[-1], other)
1736
1966
            self._weave = None
1737
1967
        else:
1738
1968
            self._head_key = None
1752
1982
        while True:
1753
1983
            next_lcas = self.graph.find_lca(*cur_ancestors)
1754
1984
            # Map a plain NULL_REVISION to a simple no-ancestors
1755
 
            if next_lcas == set([NULL_REVISION]):
 
1985
            if next_lcas == set([_mod_revision.NULL_REVISION]):
1756
1986
                next_lcas = ()
1757
1987
            # Order the lca's based on when they were merged into the tip
1758
1988
            # While the actual merge portion of weave merge uses a set() of
1770
2000
            elif len(next_lcas) > 2:
1771
2001
                # More than 2 lca's, fall back to grabbing all nodes between
1772
2002
                # this and the unique lca.
1773
 
                mutter('More than 2 LCAs, falling back to all nodes for:'
1774
 
                       ' %s, %s\n=> %s', self.a_key, self.b_key, cur_ancestors)
 
2003
                trace.mutter('More than 2 LCAs, falling back to all nodes for:'
 
2004
                             ' %s, %s\n=> %s',
 
2005
                             self.a_key, self.b_key, cur_ancestors)
1775
2006
                cur_lcas = next_lcas
1776
2007
                while len(cur_lcas) > 1:
1777
2008
                    cur_lcas = self.graph.find_lca(*cur_lcas)
1780
2011
                    unique_lca = None
1781
2012
                else:
1782
2013
                    unique_lca = list(cur_lcas)[0]
1783
 
                    if unique_lca == NULL_REVISION:
 
2014
                    if unique_lca == _mod_revision.NULL_REVISION:
1784
2015
                        # find_lca will return a plain 'NULL_REVISION' rather
1785
2016
                        # than a key tuple when there is no common ancestor, we
1786
2017
                        # prefer to just use None, because it doesn't confuse
1809
2040
            # We remove NULL_REVISION because it isn't a proper tuple key, and
1810
2041
            # thus confuses things like _get_interesting_texts, and our logic
1811
2042
            # to add the texts into the memory weave.
1812
 
            if NULL_REVISION in parent_map:
1813
 
                parent_map.pop(NULL_REVISION)
 
2043
            if _mod_revision.NULL_REVISION in parent_map:
 
2044
                parent_map.pop(_mod_revision.NULL_REVISION)
1814
2045
        else:
1815
2046
            interesting = set()
1816
2047
            for tip in tip_keys:
1968
2199
        lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
1969
2200
        self.lcas = set()
1970
2201
        for lca in lcas:
1971
 
            if lca == NULL_REVISION:
 
2202
            if lca == _mod_revision.NULL_REVISION:
1972
2203
                self.lcas.add(lca)
1973
2204
            else:
1974
2205
                self.lcas.add(lca[-1])