~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: John Arbash Meinel
  • Date: 2009-02-25 21:13:22 UTC
  • mto: This revision was merged to the branch mainline in revision 4051.
  • Revision ID: john@arbash-meinel.com-20090225211322-qc94czk3s1g7nliq
Some direct tests for _group_keys_for_io

Show diffs side-by-side

added added

removed removed

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