~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Ian Clatworthy
  • Date: 2010-02-19 03:02:07 UTC
  • mto: (4797.23.1 integration-2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: ian.clatworthy@canonical.com-20100219030207-zpbzx021zavx4sqt
What's New in 2.1 - a summary of changes since 2.0

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.cleanup import OperationWithCleanups
 
40
from bzrlib.symbol_versioning import (
 
41
    deprecated_in,
 
42
    deprecated_method,
 
43
    )
63
44
# TODO: Report back as changes are merged in
64
45
 
65
46
 
66
47
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)
 
48
    from_tree.lock_tree_write()
 
49
    operation = OperationWithCleanups(merge_inner)
 
50
    operation.add_cleanup(from_tree.unlock)
 
51
    operation.run_simple(from_tree.branch, to_tree, from_tree,
 
52
        ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
 
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
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
103
    
 
104
    :ivar affected_files: The configured file paths to merge.
 
105
 
 
106
    :cvar name_prefix: The prefix to use when looking up configuration
 
107
        details. <name_prefix>_merge_files describes the files targeted by the
 
108
        hook for example.
 
109
        
 
110
    :cvar default_files: The default file paths to merge when no configuration
 
111
        is present.
 
112
    """
 
113
 
 
114
    name_prefix = None
 
115
    default_files = None
 
116
 
 
117
    def __init__(self, merger):
 
118
        super(ConfigurableFileMerger, self).__init__(merger)
 
119
        self.affected_files = None
 
120
        self.default_files = self.__class__.default_files or []
 
121
        self.name_prefix = self.__class__.name_prefix
 
122
        if self.name_prefix is None:
 
123
            raise ValueError("name_prefix must be set.")
 
124
 
 
125
    def filename_matches_config(self, params):
 
126
        """Check whether the file should call the merge hook.
 
127
 
 
128
        <name_prefix>_merge_files configuration variable is a list of files
 
129
        that should use the hook.
 
130
        """
 
131
        affected_files = self.affected_files
 
132
        if affected_files is None:
 
133
            config = self.merger.this_branch.get_config()
 
134
            # Until bzr provides a better policy for caching the config, we
 
135
            # just add the part we're interested in to the params to avoid
 
136
            # reading the config files repeatedly (bazaar.conf, location.conf,
 
137
            # branch.conf).
 
138
            config_key = self.name_prefix + '_merge_files'
 
139
            affected_files = config.get_user_option_as_list(config_key)
 
140
            if affected_files is None:
 
141
                # If nothing was specified in the config, use the default.
 
142
                affected_files = self.default_files
 
143
            self.affected_files = affected_files
 
144
        if affected_files:
 
145
            filename = self.merger.this_tree.id2path(params.file_id)
 
146
            if filename in affected_files:
 
147
                return True
 
148
        return False
 
149
 
 
150
    def merge_contents(self, params):
 
151
        """Merge the contents of a single file."""
 
152
        # First, check whether this custom merge logic should be used.  We
 
153
        # expect most files should not be merged by this handler.
 
154
        if (
 
155
            # OTHER is a straight winner, rely on default merge.
 
156
            params.winner == 'other' or
 
157
            # THIS and OTHER aren't both files.
 
158
            not params.is_file_merge() or
 
159
            # The filename isn't listed in the 'NAME_merge_files' config
 
160
            # option.
 
161
            not self.filename_matches_config(params)):
 
162
            return 'not_applicable', None
 
163
        return self.merge_text(params)
 
164
 
 
165
    def merge_text(self, params):
 
166
        """Merge the byte contents of a single file.
 
167
 
 
168
        This is called after checking that the merge should be performed in
 
169
        merge_contents, and it should behave as per
 
170
        ``bzrlib.merge.AbstractPerFileMerger.merge_contents``.
 
171
        """
 
172
        raise NotImplementedError(self.merge_text)
 
173
 
 
174
 
 
175
class MergeHookParams(object):
 
176
    """Object holding parameters passed to merge_file_content hooks.
 
177
 
 
178
    There are some fields hooks can access:
 
179
 
 
180
    :ivar file_id: the file ID of the file being merged
 
181
    :ivar trans_id: the transform ID for the merge of this file
 
182
    :ivar this_kind: kind of file_id in 'this' tree
 
183
    :ivar other_kind: kind of file_id in 'other' tree
 
184
    :ivar winner: one of 'this', 'other', 'conflict'
 
185
    """
 
186
 
 
187
    def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
 
188
            winner):
 
189
        self._merger = merger
 
190
        self.file_id = file_id
 
191
        self.trans_id = trans_id
 
192
        self.this_kind = this_kind
 
193
        self.other_kind = other_kind
 
194
        self.winner = winner
 
195
 
 
196
    def is_file_merge(self):
 
197
        """True if this_kind and other_kind are both 'file'."""
 
198
        return self.this_kind == 'file' and self.other_kind == 'file'
 
199
 
 
200
    @decorators.cachedproperty
 
201
    def base_lines(self):
 
202
        """The lines of the 'base' version of the file."""
 
203
        return self._merger.get_lines(self._merger.base_tree, self.file_id)
 
204
 
 
205
    @decorators.cachedproperty
 
206
    def this_lines(self):
 
207
        """The lines of the 'this' version of the file."""
 
208
        return self._merger.get_lines(self._merger.this_tree, self.file_id)
 
209
 
 
210
    @decorators.cachedproperty
 
211
    def other_lines(self):
 
212
        """The lines of the 'other' version of the file."""
 
213
        return self._merger.get_lines(self._merger.other_tree, self.file_id)
69
214
 
70
215
 
71
216
class Merger(object):
 
217
 
 
218
    hooks = MergeHooks()
 
219
 
72
220
    def __init__(self, this_branch, other_tree=None, base_tree=None,
73
221
                 this_tree=None, pb=None, change_reporter=None,
74
222
                 recurse='down', revision_graph=None):
90
238
        self.show_base = False
91
239
        self.reprocess = False
92
240
        if pb is None:
93
 
            pb = DummyProgress()
 
241
            pb = progress.DummyProgress()
94
242
        self._pb = pb
95
243
        self.pp = None
96
244
        self.recurse = recurse
102
250
        self._is_criss_cross = None
103
251
        self._lca_trees = None
104
252
 
 
253
    def cache_trees_with_revision_ids(self, trees):
 
254
        """Cache any tree in trees if it has a revision_id."""
 
255
        for maybe_tree in trees:
 
256
            if maybe_tree is None:
 
257
                continue
 
258
            try:
 
259
                rev_id = maybe_tree.get_revision_id()
 
260
            except AttributeError:
 
261
                continue
 
262
            self._cached_trees[rev_id] = maybe_tree
 
263
 
105
264
    @property
106
265
    def revision_graph(self):
107
266
        if self._revision_graph is None:
169
328
                base_revision_id, tree.branch.last_revision())):
170
329
                base_revision_id = None
171
330
            else:
172
 
                warning('Performing cherrypick')
 
331
                trace.warning('Performing cherrypick')
173
332
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
174
333
                                         base_revision_id, revision_graph=
175
334
                                         revision_graph)
227
386
        if revno is None:
228
387
            tree = workingtree.WorkingTree.open_containing(location)[0]
229
388
            return tree.branch, tree
230
 
        branch = Branch.open_containing(location, possible_transports)[0]
 
389
        branch = _mod_branch.Branch.open_containing(
 
390
            location, possible_transports)[0]
231
391
        if revno == -1:
232
392
            revision_id = branch.last_revision()
233
393
        else:
234
394
            revision_id = branch.get_rev_id(revno)
235
 
        revision_id = ensure_null(revision_id)
 
395
        revision_id = _mod_revision.ensure_null(revision_id)
236
396
        return branch, self.revision_tree(revision_id, branch)
237
397
 
 
398
    @deprecated_method(deprecated_in((2, 1, 0)))
238
399
    def ensure_revision_trees(self):
239
400
        if self.this_revision_tree is None:
240
401
            self.this_basis_tree = self.revision_tree(self.this_basis)
244
405
        if self.other_rev_id is None:
245
406
            other_basis_tree = self.revision_tree(self.other_basis)
246
407
            if other_basis_tree.has_changes(self.other_tree):
247
 
                raise WorkingTreeNotRevision(self.this_tree)
 
408
                raise errors.WorkingTreeNotRevision(self.this_tree)
248
409
            other_rev_id = self.other_basis
249
410
            self.other_tree = other_basis_tree
250
411
 
 
412
    @deprecated_method(deprecated_in((2, 1, 0)))
251
413
    def file_revisions(self, file_id):
252
414
        self.ensure_revision_trees()
253
415
        def get_id(tree, file_id):
256
418
        if self.this_rev_id is None:
257
419
            if self.this_basis_tree.get_file_sha1(file_id) != \
258
420
                self.this_tree.get_file_sha1(file_id):
259
 
                raise WorkingTreeNotRevision(self.this_tree)
 
421
                raise errors.WorkingTreeNotRevision(self.this_tree)
260
422
 
261
423
        trees = (self.this_basis_tree, self.other_tree)
262
424
        return [get_id(tree, file_id) for tree in trees]
263
425
 
 
426
    @deprecated_method(deprecated_in((2, 1, 0)))
264
427
    def check_basis(self, check_clean, require_commits=True):
265
428
        if self.this_basis is None and require_commits is True:
266
 
            raise BzrCommandError("This branch has no commits."
267
 
                                  " (perhaps you would prefer 'bzr pull')")
 
429
            raise errors.BzrCommandError(
 
430
                "This branch has no commits."
 
431
                " (perhaps you would prefer 'bzr pull')")
268
432
        if check_clean:
269
433
            self.compare_basis()
270
434
            if self.this_basis != self.this_rev_id:
271
435
                raise errors.UncommittedChanges(self.this_tree)
272
436
 
 
437
    @deprecated_method(deprecated_in((2, 1, 0)))
273
438
    def compare_basis(self):
274
439
        try:
275
440
            basis_tree = self.revision_tree(self.this_tree.last_revision())
282
447
        self.interesting_files = file_list
283
448
 
284
449
    def set_pending(self):
285
 
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
 
450
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
 
451
            or self.other_rev_id is None):
286
452
            return
287
453
        self._add_parent()
288
454
 
289
455
    def _add_parent(self):
290
456
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
291
457
        new_parent_trees = []
 
458
        operation = OperationWithCleanups(self.this_tree.set_parent_trees)
292
459
        for revision_id in new_parents:
293
460
            try:
294
461
                tree = self.revision_tree(revision_id)
296
463
                tree = None
297
464
            else:
298
465
                tree.lock_read()
 
466
                operation.add_cleanup(tree.unlock)
299
467
            new_parent_trees.append((revision_id, tree))
300
 
        try:
301
 
            self.this_tree.set_parent_trees(new_parent_trees,
302
 
                                            allow_leftmost_as_ghost=True)
303
 
        finally:
304
 
            for _revision_id, tree in new_parent_trees:
305
 
                if tree is not None:
306
 
                    tree.unlock()
 
468
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
307
469
 
308
470
    def set_other(self, other_revision, possible_transports=None):
309
471
        """Set the revision and tree to merge from.
318
480
            self.other_rev_id = _mod_revision.ensure_null(
319
481
                self.other_branch.last_revision())
320
482
            if _mod_revision.is_null(self.other_rev_id):
321
 
                raise NoCommits(self.other_branch)
 
483
                raise errors.NoCommits(self.other_branch)
322
484
            self.other_basis = self.other_rev_id
323
485
        elif other_revision[1] is not None:
324
486
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
327
489
            self.other_rev_id = None
328
490
            self.other_basis = self.other_branch.last_revision()
329
491
            if self.other_basis is None:
330
 
                raise NoCommits(self.other_branch)
 
492
                raise errors.NoCommits(self.other_branch)
331
493
        if self.other_rev_id is not None:
332
494
            self._cached_trees[self.other_rev_id] = self.other_tree
333
495
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
360
522
            target.fetch(source, revision_id)
361
523
 
362
524
    def find_base(self):
363
 
        revisions = [ensure_null(self.this_basis),
364
 
                     ensure_null(self.other_basis)]
365
 
        if NULL_REVISION in revisions:
366
 
            self.base_rev_id = NULL_REVISION
 
525
        revisions = [_mod_revision.ensure_null(self.this_basis),
 
526
                     _mod_revision.ensure_null(self.other_basis)]
 
527
        if _mod_revision.NULL_REVISION in revisions:
 
528
            self.base_rev_id = _mod_revision.NULL_REVISION
367
529
            self.base_tree = self.revision_tree(self.base_rev_id)
368
530
            self._is_criss_cross = False
369
531
        else:
370
532
            lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
371
533
            self._is_criss_cross = False
372
534
            if len(lcas) == 0:
373
 
                self.base_rev_id = NULL_REVISION
 
535
                self.base_rev_id = _mod_revision.NULL_REVISION
374
536
            elif len(lcas) == 1:
375
537
                self.base_rev_id = list(lcas)[0]
376
538
            else: # len(lcas) > 1
385
547
                    self.base_rev_id = self.revision_graph.find_unique_lca(
386
548
                                            *lcas)
387
549
                self._is_criss_cross = True
388
 
            if self.base_rev_id == NULL_REVISION:
389
 
                raise UnrelatedBranches()
 
550
            if self.base_rev_id == _mod_revision.NULL_REVISION:
 
551
                raise errors.UnrelatedBranches()
390
552
            if self._is_criss_cross:
391
 
                warning('Warning: criss-cross merge encountered.  See bzr'
392
 
                        ' help criss-cross.')
393
 
                mutter('Criss-cross lcas: %r' % lcas)
 
553
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
 
554
                              ' help criss-cross.')
 
555
                trace.mutter('Criss-cross lcas: %r' % lcas)
394
556
                interesting_revision_ids = [self.base_rev_id]
395
557
                interesting_revision_ids.extend(lcas)
396
558
                interesting_trees = dict((t.get_revision_id(), t)
406
568
                self.base_tree = self.revision_tree(self.base_rev_id)
407
569
        self.base_is_ancestor = True
408
570
        self.base_is_other_ancestor = True
409
 
        mutter('Base revid: %r' % self.base_rev_id)
 
571
        trace.mutter('Base revid: %r' % self.base_rev_id)
410
572
 
411
573
    def set_base(self, base_revision):
412
574
        """Set the base revision to use for the merge.
413
575
 
414
576
        :param base_revision: A 2-list containing a path and revision number.
415
577
        """
416
 
        mutter("doing merge() with no base_revision specified")
 
578
        trace.mutter("doing merge() with no base_revision specified")
417
579
        if base_revision == [None, None]:
418
580
            self.find_base()
419
581
        else:
432
594
                  'other_tree': self.other_tree,
433
595
                  'interesting_ids': self.interesting_ids,
434
596
                  'interesting_files': self.interesting_files,
435
 
                  'pp': self.pp,
 
597
                  'pp': self.pp, 'this_branch': self.this_branch,
436
598
                  'do_merge': False}
437
599
        if self.merge_type.requires_base:
438
600
            kwargs['base_tree'] = self.base_tree
439
601
        if self.merge_type.supports_reprocess:
440
602
            kwargs['reprocess'] = self.reprocess
441
603
        elif self.reprocess:
442
 
            raise BzrError("Conflict reduction is not supported for merge"
443
 
                                  " type %s." % self.merge_type)
 
604
            raise errors.BzrError(
 
605
                "Conflict reduction is not supported for merge"
 
606
                " type %s." % self.merge_type)
444
607
        if self.merge_type.supports_show_base:
445
608
            kwargs['show_base'] = self.show_base
446
609
        elif self.show_base:
447
 
            raise BzrError("Showing base is not supported for this"
448
 
                           " merge type. %s" % self.merge_type)
 
610
            raise errors.BzrError("Showing base is not supported for this"
 
611
                                  " merge type. %s" % self.merge_type)
449
612
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
450
613
            and not self.base_is_other_ancestor):
451
614
            raise errors.CannotReverseCherrypick()
459
622
                               change_reporter=self.change_reporter,
460
623
                               **kwargs)
461
624
 
462
 
    def _do_merge_to(self, merge):
 
625
    def _do_merge_to(self):
 
626
        merge = self.make_merger()
463
627
        if self.other_branch is not None:
464
628
            self.other_branch.update_references(self.this_branch)
465
629
        merge.do_merge()
479
643
                    sub_tree.branch.repository.revision_tree(base_revision)
480
644
                sub_merge.base_rev_id = base_revision
481
645
                sub_merge.do_merge()
 
646
        return merge
482
647
 
483
648
    def do_merge(self):
 
649
        operation = OperationWithCleanups(self._do_merge_to)
484
650
        self.this_tree.lock_tree_write()
485
 
        try:
486
 
            if self.base_tree is not None:
487
 
                self.base_tree.lock_read()
488
 
            try:
489
 
                if self.other_tree is not None:
490
 
                    self.other_tree.lock_read()
491
 
                try:
492
 
                    merge = self.make_merger()
493
 
                    self._do_merge_to(merge)
494
 
                finally:
495
 
                    if self.other_tree is not None:
496
 
                        self.other_tree.unlock()
497
 
            finally:
498
 
                if self.base_tree is not None:
499
 
                    self.base_tree.unlock()
500
 
        finally:
501
 
            self.this_tree.unlock()
 
651
        operation.add_cleanup(self.this_tree.unlock)
 
652
        if self.base_tree is not None:
 
653
            self.base_tree.lock_read()
 
654
            operation.add_cleanup(self.base_tree.unlock)
 
655
        if self.other_tree is not None:
 
656
            self.other_tree.lock_read()
 
657
            operation.add_cleanup(self.other_tree.unlock)
 
658
        merge = operation.run_simple()
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
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
 
597
757
    def do_merge(self):
 
758
        operation = OperationWithCleanups(self._do_merge)
 
759
        operation.add_cleanup(self.pb.clear)
598
760
        self.this_tree.lock_tree_write()
 
761
        operation.add_cleanup(self.this_tree.unlock)
599
762
        self.base_tree.lock_read()
 
763
        operation.add_cleanup(self.base_tree.unlock)
600
764
        self.other_tree.lock_read()
601
 
        self.tt = TreeTransform(self.this_tree, self.pb)
 
765
        operation.add_cleanup(self.other_tree.unlock)
 
766
        operation.run()
 
767
 
 
768
    def _do_merge(self, operation):
 
769
        self.tt = transform.TreeTransform(self.this_tree, self.pb)
 
770
        operation.add_cleanup(self.tt.finalize)
 
771
        self.pp.next_phase()
 
772
        self._compute_transform()
 
773
        self.pp.next_phase()
 
774
        results = self.tt.apply(no_conflicts=True)
 
775
        self.write_modified(results)
602
776
        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)
608
 
            try:
609
 
                self.this_tree.add_conflicts(self.cooked_conflicts)
610
 
            except UnsupportedOperation:
611
 
                pass
612
 
        finally:
613
 
            self.tt.finalize()
614
 
            self.other_tree.unlock()
615
 
            self.base_tree.unlock()
616
 
            self.this_tree.unlock()
617
 
            self.pb.clear()
 
777
            self.this_tree.add_conflicts(self.cooked_conflicts)
 
778
        except errors.UnsupportedOperation:
 
779
            pass
618
780
 
619
781
    def make_preview_transform(self):
 
782
        operation = OperationWithCleanups(self._make_preview_transform)
 
783
        operation.add_cleanup(self.pb.clear)
620
784
        self.base_tree.lock_read()
 
785
        operation.add_cleanup(self.base_tree.unlock)
621
786
        self.other_tree.lock_read()
622
 
        self.tt = TransformPreview(self.this_tree)
623
 
        try:
624
 
            self.pp.next_phase()
625
 
            self._compute_transform()
626
 
            self.pp.next_phase()
627
 
        finally:
628
 
            self.other_tree.unlock()
629
 
            self.base_tree.unlock()
630
 
            self.pb.clear()
 
787
        operation.add_cleanup(self.other_tree.unlock)
 
788
        return operation.run_simple()
 
789
 
 
790
    def _make_preview_transform(self):
 
791
        self.tt = transform.TransformPreview(self.this_tree)
 
792
        self.pp.next_phase()
 
793
        self._compute_transform()
 
794
        self.pp.next_phase()
631
795
        return self.tt
632
796
 
633
797
    def _compute_transform(self):
639
803
            resolver = self._lca_multi_way
640
804
        child_pb = ui.ui_factory.nested_progress_bar()
641
805
        try:
 
806
            factories = Merger.hooks['merge_file_content']
 
807
            hooks = [factory(self) for factory in factories] + [self]
 
808
            self.active_hooks = [hook for hook in hooks if hook is not None]
642
809
            for num, (file_id, changed, parents3, names3,
643
810
                      executable3) in enumerate(entries):
644
811
                child_pb.update('Preparing file merge', num, len(entries))
645
812
                self._merge_names(file_id, parents3, names3, resolver=resolver)
646
813
                if changed:
647
 
                    file_status = self.merge_contents(file_id)
 
814
                    file_status = self._do_merge_contents(file_id)
648
815
                else:
649
816
                    file_status = 'unmodified'
650
817
                self._merge_executable(file_id,
655
822
        self.pp.next_phase()
656
823
        child_pb = ui.ui_factory.nested_progress_bar()
657
824
        try:
658
 
            fs_conflicts = resolve_conflicts(self.tt, child_pb,
659
 
                lambda t, c: conflict_pass(t, c, self.other_tree))
 
825
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
 
826
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
660
827
        finally:
661
828
            child_pb.finished()
662
829
        if self.change_reporter is not None:
665
832
                self.tt.iter_changes(), self.change_reporter)
666
833
        self.cook_conflicts(fs_conflicts)
667
834
        for conflict in self.cooked_conflicts:
668
 
            warning(conflict)
 
835
            trace.warning(conflict)
669
836
 
670
837
    def _entries3(self):
671
838
        """Gather data about files modified between three trees.
873
1040
    def fix_root(self):
874
1041
        try:
875
1042
            self.tt.final_kind(self.tt.root)
876
 
        except NoSuchFile:
 
1043
        except errors.NoSuchFile:
877
1044
            self.tt.cancel_deletion(self.tt.root)
878
1045
        if self.tt.final_file_id(self.tt.root) is None:
879
1046
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
886
1053
            return
887
1054
        try:
888
1055
            self.tt.final_kind(other_root)
889
 
        except NoSuchFile:
 
1056
        except errors.NoSuchFile:
890
1057
            return
891
 
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
 
1058
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
892
1059
            # the other tree's root is a non-root in the current tree
893
1060
            return
894
1061
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
936
1103
    @staticmethod
937
1104
    def executable(tree, file_id):
938
1105
        """Determine the executability of a file-id (used as a key method)."""
939
 
        if file_id not in tree:
 
1106
        if not tree.has_id(file_id):
940
1107
            return None
941
1108
        if tree.kind(file_id) != "file":
942
1109
            return False
945
1112
    @staticmethod
946
1113
    def kind(tree, file_id):
947
1114
        """Determine the kind of a file-id (used as a key method)."""
948
 
        if file_id not in tree:
 
1115
        if not tree.has_id(file_id):
949
1116
            return None
950
1117
        return tree.kind(file_id)
951
1118
 
1034
1201
 
1035
1202
    def merge_names(self, file_id):
1036
1203
        def get_entry(tree):
1037
 
            if file_id in tree.inventory:
 
1204
            if tree.has_id(file_id):
1038
1205
                return tree.inventory[file_id]
1039
1206
            else:
1040
1207
                return None
1089
1256
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1090
1257
                                parent_trans_id, trans_id)
1091
1258
 
1092
 
    def merge_contents(self, file_id):
 
1259
    def _do_merge_contents(self, file_id):
1093
1260
        """Performs a merge on file_id contents."""
1094
1261
        def contents_pair(tree):
1095
1262
            if file_id not in tree:
1103
1270
                contents = None
1104
1271
            return kind, contents
1105
1272
 
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
1273
        # See SPOT run.  run, SPOT, run.
1119
1274
        # So we're not QUITE repeating ourselves; we do tricky things with
1120
1275
        # file kind...
1136
1291
        if winner == 'this':
1137
1292
            # No interesting changes introduced by OTHER
1138
1293
            return "unmodified"
 
1294
        # We have a hypothetical conflict, but if we have files, then we
 
1295
        # can try to merge the content
1139
1296
        trans_id = self.tt.trans_id_file_id(file_id)
1140
 
        if winner == 'other':
 
1297
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
 
1298
            other_pair[0], winner)
 
1299
        hooks = self.active_hooks
 
1300
        hook_status = 'not_applicable'
 
1301
        for hook in hooks:
 
1302
            hook_status, lines = hook.merge_contents(params)
 
1303
            if hook_status != 'not_applicable':
 
1304
                # Don't try any more hooks, this one applies.
 
1305
                break
 
1306
        result = "modified"
 
1307
        if hook_status == 'not_applicable':
 
1308
            # This is a contents conflict, because none of the available
 
1309
            # functions could merge it.
 
1310
            result = None
 
1311
            name = self.tt.final_name(trans_id)
 
1312
            parent_id = self.tt.final_parent(trans_id)
 
1313
            if self.this_tree.has_id(file_id):
 
1314
                self.tt.unversion_file(trans_id)
 
1315
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1316
                                              set_version=True)
 
1317
            self._raw_conflicts.append(('contents conflict', file_group))
 
1318
        elif hook_status == 'success':
 
1319
            self.tt.create_file(lines, trans_id)
 
1320
        elif hook_status == 'conflicted':
 
1321
            # XXX: perhaps the hook should be able to provide
 
1322
            # the BASE/THIS/OTHER files?
 
1323
            self.tt.create_file(lines, trans_id)
 
1324
            self._raw_conflicts.append(('text conflict', trans_id))
 
1325
            name = self.tt.final_name(trans_id)
 
1326
            parent_id = self.tt.final_parent(trans_id)
 
1327
            self._dump_conflicts(name, parent_id, file_id)
 
1328
        elif hook_status == 'delete':
 
1329
            self.tt.unversion_file(trans_id)
 
1330
            result = "deleted"
 
1331
        elif hook_status == 'done':
 
1332
            # The hook function did whatever it needs to do directly, no
 
1333
            # further action needed here.
 
1334
            pass
 
1335
        else:
 
1336
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
 
1337
        if not self.this_tree.has_id(file_id) and result == "modified":
 
1338
            self.tt.version_file(file_id, trans_id)
 
1339
        # The merge has been performed, so the old contents should not be
 
1340
        # retained.
 
1341
        try:
 
1342
            self.tt.delete_contents(trans_id)
 
1343
        except errors.NoSuchFile:
 
1344
            pass
 
1345
        return result
 
1346
 
 
1347
    def _default_other_winner_merge(self, merge_hook_params):
 
1348
        """Replace this contents with other."""
 
1349
        file_id = merge_hook_params.file_id
 
1350
        trans_id = merge_hook_params.trans_id
 
1351
        file_in_this = self.this_tree.has_id(file_id)
 
1352
        if self.other_tree.has_id(file_id):
 
1353
            # OTHER changed the file
 
1354
            wt = self.this_tree
 
1355
            if wt.supports_content_filtering():
 
1356
                # We get the path from the working tree if it exists.
 
1357
                # That fails though when OTHER is adding a file, so
 
1358
                # we fall back to the other tree to find the path if
 
1359
                # it doesn't exist locally.
 
1360
                try:
 
1361
                    filter_tree_path = wt.id2path(file_id)
 
1362
                except errors.NoSuchId:
 
1363
                    filter_tree_path = self.other_tree.id2path(file_id)
 
1364
            else:
 
1365
                # Skip the id2path lookup for older formats
 
1366
                filter_tree_path = None
 
1367
            transform.create_from_tree(self.tt, trans_id,
 
1368
                             self.other_tree, file_id,
 
1369
                             filter_tree_path=filter_tree_path)
 
1370
            return 'done', None
 
1371
        elif file_in_this:
 
1372
            # OTHER deleted the file
 
1373
            return 'delete', None
 
1374
        else:
 
1375
            raise AssertionError(
 
1376
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
 
1377
                % (file_id,))
 
1378
 
 
1379
    def merge_contents(self, merge_hook_params):
 
1380
        """Fallback merge logic after user installed hooks."""
 
1381
        # This function is used in merge hooks as the fallback instance.
 
1382
        # Perhaps making this function and the functions it calls be a 
 
1383
        # a separate class would be better.
 
1384
        if merge_hook_params.winner == 'other':
1141
1385
            # 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"
 
1386
            return self._default_other_winner_merge(merge_hook_params)
 
1387
        elif merge_hook_params.is_file_merge():
 
1388
            # THIS and OTHER are both files, so text merge.  Either
 
1389
            # BASE is a file, or both converted to files, so at least we
 
1390
            # have agreement that output should be a file.
 
1391
            try:
 
1392
                self.text_merge(merge_hook_params.file_id,
 
1393
                    merge_hook_params.trans_id)
 
1394
            except errors.BinaryFile:
 
1395
                return 'not_applicable', None
 
1396
            return 'done', None
1157
1397
        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()
 
1398
            return 'not_applicable', None
1178
1399
 
1179
1400
    def get_lines(self, tree, file_id):
1180
1401
        """Return the lines in a file, or an empty list."""
1181
 
        if file_id in tree:
 
1402
        if tree.has_id(file_id):
1182
1403
            return tree.get_file(file_id).readlines()
1183
1404
        else:
1184
1405
            return []
1187
1408
        """Perform a three-way text merge on a file_id"""
1188
1409
        # it's possible that we got here with base as a different type.
1189
1410
        # if so, we just want two-way text conflicts.
1190
 
        if file_id in self.base_tree and \
 
1411
        if self.base_tree.has_id(file_id) and \
1191
1412
            self.base_tree.kind(file_id) == "file":
1192
1413
            base_lines = self.get_lines(self.base_tree, file_id)
1193
1414
        else:
1194
1415
            base_lines = []
1195
1416
        other_lines = self.get_lines(self.other_tree, file_id)
1196
1417
        this_lines = self.get_lines(self.this_tree, file_id)
1197
 
        m3 = Merge3(base_lines, this_lines, other_lines,
1198
 
                    is_cherrypick=self.cherrypick)
 
1418
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
 
1419
                           is_cherrypick=self.cherrypick)
1199
1420
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1200
1421
        if self.show_base is True:
1201
1422
            base_marker = '|' * 7
1239
1460
                ('THIS', self.this_tree, this_lines)]
1240
1461
        if not no_base:
1241
1462
            data.append(('BASE', self.base_tree, base_lines))
 
1463
 
 
1464
        # We need to use the actual path in the working tree of the file here,
 
1465
        # ignoring the conflict suffixes
 
1466
        wt = self.this_tree
 
1467
        if wt.supports_content_filtering():
 
1468
            try:
 
1469
                filter_tree_path = wt.id2path(file_id)
 
1470
            except errors.NoSuchId:
 
1471
                # file has been deleted
 
1472
                filter_tree_path = None
 
1473
        else:
 
1474
            # Skip the id2path lookup for older formats
 
1475
            filter_tree_path = None
 
1476
 
1242
1477
        versioned = False
1243
1478
        file_group = []
1244
1479
        for suffix, tree, lines in data:
1245
 
            if file_id in tree:
 
1480
            if tree.has_id(file_id):
1246
1481
                trans_id = self._conflict_file(name, parent_id, tree, file_id,
1247
 
                                               suffix, lines)
 
1482
                                               suffix, lines, filter_tree_path)
1248
1483
                file_group.append(trans_id)
1249
1484
                if set_version and not versioned:
1250
1485
                    self.tt.version_file(file_id, trans_id)
1252
1487
        return file_group
1253
1488
 
1254
1489
    def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1255
 
                       lines=None):
 
1490
                       lines=None, filter_tree_path=None):
1256
1491
        """Emit a single conflict file."""
1257
1492
        name = name + '.' + suffix
1258
1493
        trans_id = self.tt.create_path(name, parent_id)
1259
 
        create_from_tree(self.tt, trans_id, tree, file_id, lines)
 
1494
        transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
 
1495
            filter_tree_path)
1260
1496
        return trans_id
1261
1497
 
1262
1498
    def merge_executable(self, file_id, file_status):
1286
1522
        try:
1287
1523
            if self.tt.final_kind(trans_id) != "file":
1288
1524
                return
1289
 
        except NoSuchFile:
 
1525
        except errors.NoSuchFile:
1290
1526
            return
1291
1527
        if winner == "this":
1292
1528
            executability = this_executable
1293
1529
        else:
1294
 
            if file_id in self.other_tree:
 
1530
            if self.other_tree.has_id(file_id):
1295
1531
                executability = other_executable
1296
 
            elif file_id in self.this_tree:
 
1532
            elif self.this_tree.has_id(file_id):
1297
1533
                executability = this_executable
1298
 
            elif file_id in self.base_tree:
 
1534
            elif self.base_tree_has_id(file_id):
1299
1535
                executability = base_executable
1300
1536
        if executability is not None:
1301
1537
            trans_id = self.tt.trans_id_file_id(file_id)
1303
1539
 
1304
1540
    def cook_conflicts(self, fs_conflicts):
1305
1541
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1306
 
        from conflicts import Conflict
1307
1542
        name_conflicts = {}
1308
 
        self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1309
 
        fp = FinalPaths(self.tt)
 
1543
        self.cooked_conflicts.extend(transform.cook_conflicts(
 
1544
                fs_conflicts, self.tt))
 
1545
        fp = transform.FinalPaths(self.tt)
1310
1546
        for conflict in self._raw_conflicts:
1311
1547
            conflict_type = conflict[0]
1312
1548
            if conflict_type in ('name conflict', 'parent conflict'):
1314
1550
                conflict_args = conflict[2:]
1315
1551
                if trans_id not in name_conflicts:
1316
1552
                    name_conflicts[trans_id] = {}
1317
 
                unique_add(name_conflicts[trans_id], conflict_type,
1318
 
                           conflict_args)
 
1553
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1554
                                     conflict_args)
1319
1555
            if conflict_type == 'contents conflict':
1320
1556
                for trans_id in conflict[1]:
1321
1557
                    file_id = self.tt.final_file_id(trans_id)
1326
1562
                    if path.endswith(suffix):
1327
1563
                        path = path[:-len(suffix)]
1328
1564
                        break
1329
 
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
1565
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1566
                                                    path=path, file_id=file_id)
1330
1567
                self.cooked_conflicts.append(c)
1331
1568
            if conflict_type == 'text conflict':
1332
1569
                trans_id = conflict[1]
1333
1570
                path = fp.get_path(trans_id)
1334
1571
                file_id = self.tt.final_file_id(trans_id)
1335
 
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
1572
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1573
                                                    path=path, file_id=file_id)
1336
1574
                self.cooked_conflicts.append(c)
1337
1575
 
1338
1576
        for trans_id, conflicts in name_conflicts.iteritems():
1353
1591
            if this_parent is not None and this_name is not None:
1354
1592
                this_parent_path = \
1355
1593
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
1356
 
                this_path = pathjoin(this_parent_path, this_name)
 
1594
                this_path = osutils.pathjoin(this_parent_path, this_name)
1357
1595
            else:
1358
1596
                this_path = "<deleted>"
1359
1597
            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)
 
1598
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1599
                                                conflict_path=other_path,
 
1600
                                                file_id=file_id)
1362
1601
            self.cooked_conflicts.append(c)
1363
 
        self.cooked_conflicts.sort(key=Conflict.sort_key)
 
1602
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1364
1603
 
1365
1604
 
1366
1605
class WeaveMerger(Merge3Merger):
1370
1609
    supports_reverse_cherrypick = False
1371
1610
    history_based = True
1372
1611
 
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,
 
1612
    def _generate_merge_plan(self, file_id, base):
 
1613
        return self.this_tree.plan_file_merge(file_id, self.other_tree,
1383
1614
                                              base=base)
 
1615
 
 
1616
    def _merged_lines(self, file_id):
 
1617
        """Generate the merged lines.
 
1618
        There is no distinction between lines that are meant to contain <<<<<<<
 
1619
        and conflicts.
 
1620
        """
 
1621
        if self.cherrypick:
 
1622
            base = self.base_tree
 
1623
        else:
 
1624
            base = None
 
1625
        plan = self._generate_merge_plan(file_id, base)
1384
1626
        if 'merge' in debug.debug_flags:
1385
1627
            plan = list(plan)
1386
1628
            trans_id = self.tt.trans_id_file_id(file_id)
1387
1629
            name = self.tt.final_name(trans_id) + '.plan'
1388
 
            contents = ('%10s|%s' % l for l in plan)
 
1630
            contents = ('%11s|%s' % l for l in plan)
1389
1631
            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)
 
1632
        textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
1633
                                                 '>>>>>>> MERGE-SOURCE\n')
 
1634
        lines, conflicts = textmerge.merge_lines(self.reprocess)
 
1635
        if conflicts:
 
1636
            base_lines = textmerge.base_from_plan()
 
1637
        else:
 
1638
            base_lines = None
 
1639
        return lines, base_lines
1393
1640
 
1394
1641
    def text_merge(self, file_id, trans_id):
1395
1642
        """Perform a (weave) text merge for a given file and file-id.
1396
1643
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1397
1644
        and a conflict will be noted.
1398
1645
        """
1399
 
        lines, conflicts = self._merged_lines(file_id)
 
1646
        lines, base_lines = self._merged_lines(file_id)
1400
1647
        lines = list(lines)
1401
1648
        # Note we're checking whether the OUTPUT is binary in this case,
1402
1649
        # because we don't want to get into weave merge guts.
1403
 
        check_text_lines(lines)
 
1650
        textfile.check_text_lines(lines)
1404
1651
        self.tt.create_file(lines, trans_id)
1405
 
        if conflicts:
 
1652
        if base_lines is not None:
 
1653
            # Conflict
1406
1654
            self._raw_conflicts.append(('text conflict', trans_id))
1407
1655
            name = self.tt.final_name(trans_id)
1408
1656
            parent_id = self.tt.final_parent(trans_id)
1409
1657
            file_group = self._dump_conflicts(name, parent_id, file_id,
1410
 
                                              no_base=True)
 
1658
                                              no_base=False,
 
1659
                                              base_lines=base_lines)
1411
1660
            file_group.append(trans_id)
1412
1661
 
1413
1662
 
1414
1663
class LCAMerger(WeaveMerger):
1415
1664
 
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,
 
1665
    def _generate_merge_plan(self, file_id, base):
 
1666
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1426
1667
                                                  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
1668
 
1438
1669
class Diff3Merger(Merge3Merger):
1439
1670
    """Three-way merger using external diff3 for text merging"""
1440
1671
 
1441
1672
    def dump_file(self, temp_dir, name, tree, file_id):
1442
 
        out_path = pathjoin(temp_dir, name)
 
1673
        out_path = osutils.pathjoin(temp_dir, name)
1443
1674
        out_file = open(out_path, "wb")
1444
1675
        try:
1445
1676
            in_file = tree.get_file(file_id)
1457
1688
        import bzrlib.patch
1458
1689
        temp_dir = osutils.mkdtemp(prefix="bzr-")
1459
1690
        try:
1460
 
            new_file = pathjoin(temp_dir, "new")
 
1691
            new_file = osutils.pathjoin(temp_dir, "new")
1461
1692
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1462
1693
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1463
1694
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1464
1695
            status = bzrlib.patch.diff3(new_file, this, base, other)
1465
1696
            if status not in (0, 1):
1466
 
                raise BzrError("Unhandled diff3 exit code")
 
1697
                raise errors.BzrError("Unhandled diff3 exit code")
1467
1698
            f = open(new_file, 'rb')
1468
1699
            try:
1469
1700
                self.tt.create_file(f, trans_id)
1487
1718
                other_rev_id=None,
1488
1719
                interesting_files=None,
1489
1720
                this_tree=None,
1490
 
                pb=DummyProgress(),
 
1721
                pb=progress.DummyProgress(),
1491
1722
                change_reporter=None):
1492
1723
    """Primary interface for merging.
1493
1724
 
1496
1727
                     branch.get_revision_tree(base_revision))'
1497
1728
        """
1498
1729
    if this_tree is None:
1499
 
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1500
 
            "parameter as of bzrlib version 0.8.")
 
1730
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1731
                              "parameter as of bzrlib version 0.8.")
1501
1732
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1502
1733
                    pb=pb, change_reporter=change_reporter)
1503
1734
    merger.backup_files = backup_files
1516
1747
    get_revision_id = getattr(base_tree, 'get_revision_id', None)
1517
1748
    if get_revision_id is None:
1518
1749
        get_revision_id = base_tree.last_revision
 
1750
    merger.cache_trees_with_revision_ids([other_tree, base_tree, this_tree])
1519
1751
    merger.set_base_revision(get_revision_id(), this_branch)
1520
1752
    return merger.do_merge()
1521
1753
 
1720
1952
        super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
1721
1953
        self.a_key = self._key_prefix + (self.a_rev,)
1722
1954
        self.b_key = self._key_prefix + (self.b_rev,)
1723
 
        self.graph = Graph(self.vf)
 
1955
        self.graph = _mod_graph.Graph(self.vf)
1724
1956
        heads = self.graph.heads((self.a_key, self.b_key))
1725
1957
        if len(heads) == 1:
1726
1958
            # one side dominates, so we can just return its values, yay for
1731
1963
                other = b_rev
1732
1964
            else:
1733
1965
                other = a_rev
1734
 
            mutter('found dominating revision for %s\n%s > %s', self.vf,
1735
 
                   self._head_key[-1], other)
 
1966
            trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
 
1967
                         self._head_key[-1], other)
1736
1968
            self._weave = None
1737
1969
        else:
1738
1970
            self._head_key = None
1752
1984
        while True:
1753
1985
            next_lcas = self.graph.find_lca(*cur_ancestors)
1754
1986
            # Map a plain NULL_REVISION to a simple no-ancestors
1755
 
            if next_lcas == set([NULL_REVISION]):
 
1987
            if next_lcas == set([_mod_revision.NULL_REVISION]):
1756
1988
                next_lcas = ()
1757
1989
            # Order the lca's based on when they were merged into the tip
1758
1990
            # While the actual merge portion of weave merge uses a set() of
1770
2002
            elif len(next_lcas) > 2:
1771
2003
                # More than 2 lca's, fall back to grabbing all nodes between
1772
2004
                # 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)
 
2005
                trace.mutter('More than 2 LCAs, falling back to all nodes for:'
 
2006
                             ' %s, %s\n=> %s',
 
2007
                             self.a_key, self.b_key, cur_ancestors)
1775
2008
                cur_lcas = next_lcas
1776
2009
                while len(cur_lcas) > 1:
1777
2010
                    cur_lcas = self.graph.find_lca(*cur_lcas)
1780
2013
                    unique_lca = None
1781
2014
                else:
1782
2015
                    unique_lca = list(cur_lcas)[0]
1783
 
                    if unique_lca == NULL_REVISION:
 
2016
                    if unique_lca == _mod_revision.NULL_REVISION:
1784
2017
                        # find_lca will return a plain 'NULL_REVISION' rather
1785
2018
                        # than a key tuple when there is no common ancestor, we
1786
2019
                        # prefer to just use None, because it doesn't confuse
1809
2042
            # We remove NULL_REVISION because it isn't a proper tuple key, and
1810
2043
            # thus confuses things like _get_interesting_texts, and our logic
1811
2044
            # to add the texts into the memory weave.
1812
 
            if NULL_REVISION in parent_map:
1813
 
                parent_map.pop(NULL_REVISION)
 
2045
            if _mod_revision.NULL_REVISION in parent_map:
 
2046
                parent_map.pop(_mod_revision.NULL_REVISION)
1814
2047
        else:
1815
2048
            interesting = set()
1816
2049
            for tip in tip_keys:
1968
2201
        lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
1969
2202
        self.lcas = set()
1970
2203
        for lca in lcas:
1971
 
            if lca == NULL_REVISION:
 
2204
            if lca == _mod_revision.NULL_REVISION:
1972
2205
                self.lcas.add(lca)
1973
2206
            else:
1974
2207
                self.lcas.add(lca[-1])