/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/merge.py

  • Committer: Jelmer Vernooij
  • Date: 2020-07-18 23:14:00 UTC
  • mfrom: (7490.40.62 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200718231400-jaes9qltn8oi8xss
Merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
 
17
import contextlib
 
18
 
 
19
from .lazy_import import lazy_import
 
20
lazy_import(globals(), """
 
21
import patiencediff
 
22
 
 
23
from breezy import (
 
24
    branch as _mod_branch,
 
25
    conflicts as _mod_conflicts,
 
26
    debug,
 
27
    graph as _mod_graph,
 
28
    merge3,
 
29
    osutils,
 
30
    revision as _mod_revision,
 
31
    textfile,
 
32
    trace,
 
33
    tree as _mod_tree,
 
34
    tsort,
 
35
    ui,
 
36
    workingtree,
 
37
    )
 
38
from breezy.bzr import (
 
39
    generate_ids,
 
40
    versionedfile,
 
41
    )
 
42
from breezy.i18n import gettext
 
43
""")
 
44
from . import (
 
45
    decorators,
 
46
    errors,
 
47
    hooks,
 
48
    registry,
 
49
    transform,
 
50
    )
 
51
# TODO: Report back as changes are merged in
 
52
 
 
53
 
 
54
def transform_tree(from_tree, to_tree, interesting_files=None):
 
55
    with from_tree.lock_tree_write():
 
56
        merge_inner(from_tree.branch, to_tree, from_tree,
 
57
                    ignore_zero=True, this_tree=from_tree,
 
58
                    interesting_files=interesting_files)
 
59
 
 
60
 
 
61
class MergeHooks(hooks.Hooks):
 
62
 
 
63
    def __init__(self):
 
64
        hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
 
65
        self.add_hook('merge_file_content',
 
66
                      "Called with a breezy.merge.Merger object to create a per file "
 
67
                      "merge object when starting a merge. "
 
68
                      "Should return either None or a subclass of "
 
69
                      "``breezy.merge.AbstractPerFileMerger``. "
 
70
                      "Such objects will then be called per file "
 
71
                      "that needs to be merged (including when one "
 
72
                      "side has deleted the file and the other has changed it). "
 
73
                      "See the AbstractPerFileMerger API docs for details on how it is "
 
74
                      "used by merge.",
 
75
                      (2, 1))
 
76
        self.add_hook('pre_merge',
 
77
                      'Called before a merge. '
 
78
                      'Receives a Merger object as the single argument.',
 
79
                      (2, 5))
 
80
        self.add_hook('post_merge',
 
81
                      'Called after a merge. '
 
82
                      'Receives a Merger object as the single argument. '
 
83
                      'The return value is ignored.',
 
84
                      (2, 5))
 
85
 
 
86
 
 
87
class AbstractPerFileMerger(object):
 
88
    """PerFileMerger objects are used by plugins extending merge for breezy.
 
89
 
 
90
    See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
 
91
 
 
92
    :ivar merger: The Merge3Merger performing the merge.
 
93
    """
 
94
 
 
95
    def __init__(self, merger):
 
96
        """Create a PerFileMerger for use with merger."""
 
97
        self.merger = merger
 
98
 
 
99
    def merge_contents(self, merge_params):
 
100
        """Attempt to merge the contents of a single file.
 
101
 
 
102
        :param merge_params: A breezy.merge.MergeFileHookParams
 
103
        :return: A tuple of (status, chunks), where status is one of
 
104
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
 
105
            is 'success' or 'conflicted', then chunks should be an iterable of
 
106
            strings for the new file contents.
 
107
        """
 
108
        return ('not applicable', None)
 
109
 
 
110
 
 
111
class PerFileMerger(AbstractPerFileMerger):
 
112
    """Merge individual files when self.file_matches returns True.
 
113
 
 
114
    This class is intended to be subclassed.  The file_matches and
 
115
    merge_matching methods should be overridden with concrete implementations.
 
116
    """
 
117
 
 
118
    def file_matches(self, params):
 
119
        """Return True if merge_matching should be called on this file.
 
120
 
 
121
        Only called with merges of plain files with no clear winner.
 
122
 
 
123
        Subclasses must override this.
 
124
        """
 
125
        raise NotImplementedError(self.file_matches)
 
126
 
 
127
    def merge_contents(self, params):
 
128
        """Merge the contents of a single file."""
 
129
        # Check whether this custom merge logic should be used.
 
130
        if (
 
131
            # OTHER is a straight winner, rely on default merge.
 
132
            params.winner == 'other' or
 
133
            # THIS and OTHER aren't both files.
 
134
            not params.is_file_merge() or
 
135
            # The filename doesn't match
 
136
                not self.file_matches(params)):
 
137
            return 'not_applicable', None
 
138
        return self.merge_matching(params)
 
139
 
 
140
    def merge_matching(self, params):
 
141
        """Merge the contents of a single file that has matched the criteria
 
142
        in PerFileMerger.merge_contents (is a conflict, is a file,
 
143
        self.file_matches is True).
 
144
 
 
145
        Subclasses must override this.
 
146
        """
 
147
        raise NotImplementedError(self.merge_matching)
 
148
 
 
149
 
 
150
class ConfigurableFileMerger(PerFileMerger):
 
151
    """Merge individual files when configured via a .conf file.
 
152
 
 
153
    This is a base class for concrete custom file merging logic. Concrete
 
154
    classes should implement ``merge_text``.
 
155
 
 
156
    See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
 
157
 
 
158
    :ivar affected_files: The configured file paths to merge.
 
159
 
 
160
    :cvar name_prefix: The prefix to use when looking up configuration
 
161
        details. <name_prefix>_merge_files describes the files targeted by the
 
162
        hook for example.
 
163
 
 
164
    :cvar default_files: The default file paths to merge when no configuration
 
165
        is present.
 
166
    """
 
167
 
 
168
    name_prefix = None
 
169
    default_files = None
 
170
 
 
171
    def __init__(self, merger):
 
172
        super(ConfigurableFileMerger, self).__init__(merger)
 
173
        self.affected_files = None
 
174
        self.default_files = self.__class__.default_files or []
 
175
        self.name_prefix = self.__class__.name_prefix
 
176
        if self.name_prefix is None:
 
177
            raise ValueError("name_prefix must be set.")
 
178
 
 
179
    def file_matches(self, params):
 
180
        """Check whether the file should call the merge hook.
 
181
 
 
182
        <name_prefix>_merge_files configuration variable is a list of files
 
183
        that should use the hook.
 
184
        """
 
185
        affected_files = self.affected_files
 
186
        if affected_files is None:
 
187
            config = self.merger.this_branch.get_config()
 
188
            # Until bzr provides a better policy for caching the config, we
 
189
            # just add the part we're interested in to the params to avoid
 
190
            # reading the config files repeatedly (breezy.conf, location.conf,
 
191
            # branch.conf).
 
192
            config_key = self.name_prefix + '_merge_files'
 
193
            affected_files = config.get_user_option_as_list(config_key)
 
194
            if affected_files is None:
 
195
                # If nothing was specified in the config, use the default.
 
196
                affected_files = self.default_files
 
197
            self.affected_files = affected_files
 
198
        if affected_files:
 
199
            filepath = params.this_path
 
200
            if filepath in affected_files:
 
201
                return True
 
202
        return False
 
203
 
 
204
    def merge_matching(self, params):
 
205
        return self.merge_text(params)
 
206
 
 
207
    def merge_text(self, params):
 
208
        """Merge the byte contents of a single file.
 
209
 
 
210
        This is called after checking that the merge should be performed in
 
211
        merge_contents, and it should behave as per
 
212
        ``breezy.merge.AbstractPerFileMerger.merge_contents``.
 
213
        """
 
214
        raise NotImplementedError(self.merge_text)
 
215
 
 
216
 
 
217
class MergeFileHookParams(object):
 
218
    """Object holding parameters passed to merge_file_content hooks.
 
219
 
 
220
    There are some fields hooks can access:
 
221
 
 
222
    :ivar base_path: Path in base tree
 
223
    :ivar other_path: Path in other tree
 
224
    :ivar this_path: Path in this tree
 
225
    :ivar trans_id: the transform ID for the merge of this file
 
226
    :ivar this_kind: kind of file in 'this' tree
 
227
    :ivar other_kind: kind of file in 'other' tree
 
228
    :ivar winner: one of 'this', 'other', 'conflict'
 
229
    """
 
230
 
 
231
    def __init__(self, merger, paths, trans_id, this_kind, other_kind,
 
232
                 winner):
 
233
        self._merger = merger
 
234
        self.paths = paths
 
235
        self.base_path, self.other_path, self.this_path = paths
 
236
        self.trans_id = trans_id
 
237
        self.this_kind = this_kind
 
238
        self.other_kind = other_kind
 
239
        self.winner = winner
 
240
 
 
241
    def is_file_merge(self):
 
242
        """True if this_kind and other_kind are both 'file'."""
 
243
        return self.this_kind == 'file' and self.other_kind == 'file'
 
244
 
 
245
    @decorators.cachedproperty
 
246
    def base_lines(self):
 
247
        """The lines of the 'base' version of the file."""
 
248
        return self._merger.get_lines(self._merger.base_tree, self.base_path)
 
249
 
 
250
    @decorators.cachedproperty
 
251
    def this_lines(self):
 
252
        """The lines of the 'this' version of the file."""
 
253
        return self._merger.get_lines(self._merger.this_tree, self.this_path)
 
254
 
 
255
    @decorators.cachedproperty
 
256
    def other_lines(self):
 
257
        """The lines of the 'other' version of the file."""
 
258
        return self._merger.get_lines(self._merger.other_tree, self.other_path)
 
259
 
 
260
 
 
261
class Merger(object):
 
262
 
 
263
    hooks = MergeHooks()
 
264
 
 
265
    def __init__(self, this_branch, other_tree=None, base_tree=None,
 
266
                 this_tree=None, change_reporter=None,
 
267
                 recurse='down', revision_graph=None):
 
268
        object.__init__(self)
 
269
        self.this_branch = this_branch
 
270
        self.this_basis = _mod_revision.ensure_null(
 
271
            this_branch.last_revision())
 
272
        self.this_rev_id = None
 
273
        self.this_tree = this_tree
 
274
        self.this_revision_tree = None
 
275
        self.this_basis_tree = None
 
276
        self.other_tree = other_tree
 
277
        self.other_branch = None
 
278
        self.base_tree = base_tree
 
279
        self.ignore_zero = False
 
280
        self.backup_files = False
 
281
        self.interesting_files = None
 
282
        self.show_base = False
 
283
        self.reprocess = False
 
284
        self.pp = None
 
285
        self.recurse = recurse
 
286
        self.change_reporter = change_reporter
 
287
        self._cached_trees = {}
 
288
        self._revision_graph = revision_graph
 
289
        self._base_is_ancestor = None
 
290
        self._base_is_other_ancestor = None
 
291
        self._is_criss_cross = None
 
292
        self._lca_trees = None
 
293
 
 
294
    def cache_trees_with_revision_ids(self, trees):
 
295
        """Cache any tree in trees if it has a revision_id."""
 
296
        for maybe_tree in trees:
 
297
            if maybe_tree is None:
 
298
                continue
 
299
            try:
 
300
                rev_id = maybe_tree.get_revision_id()
 
301
            except AttributeError:
 
302
                continue
 
303
            self._cached_trees[rev_id] = maybe_tree
 
304
 
 
305
    @property
 
306
    def revision_graph(self):
 
307
        if self._revision_graph is None:
 
308
            self._revision_graph = self.this_branch.repository.get_graph()
 
309
        return self._revision_graph
 
310
 
 
311
    def _set_base_is_ancestor(self, value):
 
312
        self._base_is_ancestor = value
 
313
 
 
314
    def _get_base_is_ancestor(self):
 
315
        if self._base_is_ancestor is None:
 
316
            self._base_is_ancestor = self.revision_graph.is_ancestor(
 
317
                self.base_rev_id, self.this_basis)
 
318
        return self._base_is_ancestor
 
319
 
 
320
    base_is_ancestor = property(_get_base_is_ancestor, _set_base_is_ancestor)
 
321
 
 
322
    def _set_base_is_other_ancestor(self, value):
 
323
        self._base_is_other_ancestor = value
 
324
 
 
325
    def _get_base_is_other_ancestor(self):
 
326
        if self._base_is_other_ancestor is None:
 
327
            if self.other_basis is None:
 
328
                return True
 
329
            self._base_is_other_ancestor = self.revision_graph.is_ancestor(
 
330
                self.base_rev_id, self.other_basis)
 
331
        return self._base_is_other_ancestor
 
332
 
 
333
    base_is_other_ancestor = property(_get_base_is_other_ancestor,
 
334
                                      _set_base_is_other_ancestor)
 
335
 
 
336
    @staticmethod
 
337
    def from_uncommitted(tree, other_tree, base_tree=None):
 
338
        """Return a Merger for uncommitted changes in other_tree.
 
339
 
 
340
        :param tree: The tree to merge into
 
341
        :param other_tree: The tree to get uncommitted changes from
 
342
        :param base_tree: The basis to use for the merge.  If unspecified,
 
343
            other_tree.basis_tree() will be used.
 
344
        """
 
345
        if base_tree is None:
 
346
            base_tree = other_tree.basis_tree()
 
347
        merger = Merger(tree.branch, other_tree, base_tree, tree)
 
348
        merger.base_rev_id = merger.base_tree.get_revision_id()
 
349
        merger.other_rev_id = None
 
350
        merger.other_basis = merger.base_rev_id
 
351
        return merger
 
352
 
 
353
    @classmethod
 
354
    def from_mergeable(klass, tree, mergeable):
 
355
        """Return a Merger for a bundle or merge directive.
 
356
 
 
357
        :param tree: The tree to merge changes into
 
358
        :param mergeable: A merge directive or bundle
 
359
        """
 
360
        mergeable.install_revisions(tree.branch.repository)
 
361
        base_revision_id, other_revision_id, verified =\
 
362
            mergeable.get_merge_request(tree.branch.repository)
 
363
        revision_graph = tree.branch.repository.get_graph()
 
364
        if base_revision_id is not None:
 
365
            if (base_revision_id != _mod_revision.NULL_REVISION and
 
366
                revision_graph.is_ancestor(
 
367
                    base_revision_id, tree.branch.last_revision())):
 
368
                base_revision_id = None
 
369
            else:
 
370
                trace.warning('Performing cherrypick')
 
371
        merger = klass.from_revision_ids(tree, other_revision_id,
 
372
                                         base_revision_id, revision_graph=revision_graph)
 
373
        return merger, verified
 
374
 
 
375
    @staticmethod
 
376
    def from_revision_ids(tree, other, base=None, other_branch=None,
 
377
                          base_branch=None, revision_graph=None,
 
378
                          tree_branch=None):
 
379
        """Return a Merger for revision-ids.
 
380
 
 
381
        :param tree: The tree to merge changes into
 
382
        :param other: The revision-id to use as OTHER
 
383
        :param base: The revision-id to use as BASE.  If not specified, will
 
384
            be auto-selected.
 
385
        :param other_branch: A branch containing the other revision-id.  If
 
386
            not supplied, tree.branch is used.
 
387
        :param base_branch: A branch containing the base revision-id.  If
 
388
            not supplied, other_branch or tree.branch will be used.
 
389
        :param revision_graph: If you have a revision_graph precomputed, pass
 
390
            it in, otherwise it will be created for you.
 
391
        :param tree_branch: The branch associated with tree.  If not supplied,
 
392
            tree.branch will be used.
 
393
        """
 
394
        if tree_branch is None:
 
395
            tree_branch = tree.branch
 
396
        merger = Merger(tree_branch, this_tree=tree,
 
397
                        revision_graph=revision_graph)
 
398
        if other_branch is None:
 
399
            other_branch = tree.branch
 
400
        merger.set_other_revision(other, other_branch)
 
401
        if base is None:
 
402
            merger.find_base()
 
403
        else:
 
404
            if base_branch is None:
 
405
                base_branch = other_branch
 
406
            merger.set_base_revision(base, base_branch)
 
407
        return merger
 
408
 
 
409
    def revision_tree(self, revision_id, branch=None):
 
410
        if revision_id not in self._cached_trees:
 
411
            if branch is None:
 
412
                branch = self.this_branch
 
413
            try:
 
414
                tree = self.this_tree.revision_tree(revision_id)
 
415
            except errors.NoSuchRevisionInTree:
 
416
                tree = branch.repository.revision_tree(revision_id)
 
417
            self._cached_trees[revision_id] = tree
 
418
        return self._cached_trees[revision_id]
 
419
 
 
420
    def _get_tree(self, treespec, possible_transports=None):
 
421
        location, revno = treespec
 
422
        if revno is None:
 
423
            tree = workingtree.WorkingTree.open_containing(location)[0]
 
424
            return tree.branch, tree
 
425
        branch = _mod_branch.Branch.open_containing(
 
426
            location, possible_transports)[0]
 
427
        if revno == -1:
 
428
            revision_id = branch.last_revision()
 
429
        else:
 
430
            revision_id = branch.get_rev_id(revno)
 
431
        revision_id = _mod_revision.ensure_null(revision_id)
 
432
        return branch, self.revision_tree(revision_id, branch)
 
433
 
 
434
    def set_interesting_files(self, file_list):
 
435
        self.interesting_files = file_list
 
436
 
 
437
    def set_pending(self):
 
438
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
 
439
                or self.other_rev_id is None):
 
440
            return
 
441
        self._add_parent()
 
442
 
 
443
    def _add_parent(self):
 
444
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
 
445
        new_parent_trees = []
 
446
        with contextlib.ExitStack() as stack:
 
447
            for revision_id in new_parents:
 
448
                try:
 
449
                    tree = self.revision_tree(revision_id)
 
450
                except errors.NoSuchRevision:
 
451
                    tree = None
 
452
                else:
 
453
                    stack.enter_context(tree.lock_read())
 
454
                new_parent_trees.append((revision_id, tree))
 
455
            self.this_tree.set_parent_trees(new_parent_trees, allow_leftmost_as_ghost=True)
 
456
 
 
457
    def set_other(self, other_revision, possible_transports=None):
 
458
        """Set the revision and tree to merge from.
 
459
 
 
460
        This sets the other_tree, other_rev_id, other_basis attributes.
 
461
 
 
462
        :param other_revision: The [path, revision] list to merge from.
 
463
        """
 
464
        self.other_branch, self.other_tree = self._get_tree(other_revision,
 
465
                                                            possible_transports)
 
466
        if other_revision[1] == -1:
 
467
            self.other_rev_id = _mod_revision.ensure_null(
 
468
                self.other_branch.last_revision())
 
469
            if _mod_revision.is_null(self.other_rev_id):
 
470
                raise errors.NoCommits(self.other_branch)
 
471
            self.other_basis = self.other_rev_id
 
472
        elif other_revision[1] is not None:
 
473
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
 
474
            self.other_basis = self.other_rev_id
 
475
        else:
 
476
            self.other_rev_id = None
 
477
            self.other_basis = self.other_branch.last_revision()
 
478
            if self.other_basis is None:
 
479
                raise errors.NoCommits(self.other_branch)
 
480
        if self.other_rev_id is not None:
 
481
            self._cached_trees[self.other_rev_id] = self.other_tree
 
482
        self._maybe_fetch(self.other_branch,
 
483
                          self.this_branch, self.other_basis)
 
484
 
 
485
    def set_other_revision(self, revision_id, other_branch):
 
486
        """Set 'other' based on a branch and revision id
 
487
 
 
488
        :param revision_id: The revision to use for a tree
 
489
        :param other_branch: The branch containing this tree
 
490
        """
 
491
        self.other_rev_id = revision_id
 
492
        self.other_branch = other_branch
 
493
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
 
494
        self.other_tree = self.revision_tree(revision_id)
 
495
        self.other_basis = revision_id
 
496
 
 
497
    def set_base_revision(self, revision_id, branch):
 
498
        """Set 'base' based on a branch and revision id
 
499
 
 
500
        :param revision_id: The revision to use for a tree
 
501
        :param branch: The branch containing this tree
 
502
        """
 
503
        self.base_rev_id = revision_id
 
504
        self.base_branch = branch
 
505
        self._maybe_fetch(branch, self.this_branch, revision_id)
 
506
        self.base_tree = self.revision_tree(revision_id)
 
507
 
 
508
    def _maybe_fetch(self, source, target, revision_id):
 
509
        if not source.repository.has_same_location(target.repository):
 
510
            target.fetch(source, revision_id)
 
511
 
 
512
    def find_base(self):
 
513
        revisions = [_mod_revision.ensure_null(self.this_basis),
 
514
                     _mod_revision.ensure_null(self.other_basis)]
 
515
        if _mod_revision.NULL_REVISION in revisions:
 
516
            self.base_rev_id = _mod_revision.NULL_REVISION
 
517
            self.base_tree = self.revision_tree(self.base_rev_id)
 
518
            self._is_criss_cross = False
 
519
        else:
 
520
            lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
 
521
            self._is_criss_cross = False
 
522
            if len(lcas) == 0:
 
523
                self.base_rev_id = _mod_revision.NULL_REVISION
 
524
            elif len(lcas) == 1:
 
525
                self.base_rev_id = list(lcas)[0]
 
526
            else:  # len(lcas) > 1
 
527
                self._is_criss_cross = True
 
528
                if len(lcas) > 2:
 
529
                    # find_unique_lca can only handle 2 nodes, so we have to
 
530
                    # start back at the beginning. It is a shame to traverse
 
531
                    # the graph again, but better than re-implementing
 
532
                    # find_unique_lca.
 
533
                    self.base_rev_id = self.revision_graph.find_unique_lca(
 
534
                        revisions[0], revisions[1])
 
535
                else:
 
536
                    self.base_rev_id = self.revision_graph.find_unique_lca(
 
537
                        *lcas)
 
538
                sorted_lca_keys = self.revision_graph.find_merge_order(
 
539
                    revisions[0], lcas)
 
540
                if self.base_rev_id == _mod_revision.NULL_REVISION:
 
541
                    self.base_rev_id = sorted_lca_keys[0]
 
542
 
 
543
            if self.base_rev_id == _mod_revision.NULL_REVISION:
 
544
                raise errors.UnrelatedBranches()
 
545
            if self._is_criss_cross:
 
546
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
 
547
                              ' help criss-cross.')
 
548
                trace.mutter('Criss-cross lcas: %r' % lcas)
 
549
                if self.base_rev_id in lcas:
 
550
                    trace.mutter('Unable to find unique lca. '
 
551
                                 'Fallback %r as best option.'
 
552
                                 % self.base_rev_id)
 
553
                interesting_revision_ids = set(lcas)
 
554
                interesting_revision_ids.add(self.base_rev_id)
 
555
                interesting_trees = dict((t.get_revision_id(), t)
 
556
                                         for t in self.this_branch.repository.revision_trees(
 
557
                    interesting_revision_ids))
 
558
                self._cached_trees.update(interesting_trees)
 
559
                if self.base_rev_id in lcas:
 
560
                    self.base_tree = interesting_trees[self.base_rev_id]
 
561
                else:
 
562
                    self.base_tree = interesting_trees.pop(self.base_rev_id)
 
563
                self._lca_trees = [interesting_trees[key]
 
564
                                   for key in sorted_lca_keys]
 
565
            else:
 
566
                self.base_tree = self.revision_tree(self.base_rev_id)
 
567
        self.base_is_ancestor = True
 
568
        self.base_is_other_ancestor = True
 
569
        trace.mutter('Base revid: %r' % self.base_rev_id)
 
570
 
 
571
    def set_base(self, base_revision):
 
572
        """Set the base revision to use for the merge.
 
573
 
 
574
        :param base_revision: A 2-list containing a path and revision number.
 
575
        """
 
576
        trace.mutter("doing merge() with no base_revision specified")
 
577
        if base_revision == [None, None]:
 
578
            self.find_base()
 
579
        else:
 
580
            base_branch, self.base_tree = self._get_tree(base_revision)
 
581
            if base_revision[1] == -1:
 
582
                self.base_rev_id = base_branch.last_revision()
 
583
            elif base_revision[1] is None:
 
584
                self.base_rev_id = _mod_revision.NULL_REVISION
 
585
            else:
 
586
                self.base_rev_id = _mod_revision.ensure_null(
 
587
                    base_branch.get_rev_id(base_revision[1]))
 
588
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
 
589
 
 
590
    def make_merger(self):
 
591
        kwargs = {'working_tree': self.this_tree, 'this_tree': self.this_tree,
 
592
                  'other_tree': self.other_tree,
 
593
                  'interesting_files': self.interesting_files,
 
594
                  'this_branch': self.this_branch,
 
595
                  'other_branch': self.other_branch,
 
596
                  'do_merge': False}
 
597
        if self.merge_type.requires_base:
 
598
            kwargs['base_tree'] = self.base_tree
 
599
        if self.merge_type.supports_reprocess:
 
600
            kwargs['reprocess'] = self.reprocess
 
601
        elif self.reprocess:
 
602
            raise errors.BzrError(
 
603
                "Conflict reduction is not supported for merge"
 
604
                " type %s." % self.merge_type)
 
605
        if self.merge_type.supports_show_base:
 
606
            kwargs['show_base'] = self.show_base
 
607
        elif self.show_base:
 
608
            raise errors.BzrError("Showing base is not supported for this"
 
609
                                  " merge type. %s" % self.merge_type)
 
610
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
 
611
                and not self.base_is_other_ancestor):
 
612
            raise errors.CannotReverseCherrypick()
 
613
        if self.merge_type.supports_cherrypick:
 
614
            kwargs['cherrypick'] = (not self.base_is_ancestor or
 
615
                                    not self.base_is_other_ancestor)
 
616
        if self._is_criss_cross and getattr(self.merge_type,
 
617
                                            'supports_lca_trees', False):
 
618
            kwargs['lca_trees'] = self._lca_trees
 
619
        return self.merge_type(change_reporter=self.change_reporter,
 
620
                               **kwargs)
 
621
 
 
622
    def _do_merge_to(self):
 
623
        merge = self.make_merger()
 
624
        if self.other_branch is not None:
 
625
            self.other_branch.update_references(self.this_branch)
 
626
        for hook in Merger.hooks['pre_merge']:
 
627
            hook(merge)
 
628
        merge.do_merge()
 
629
        for hook in Merger.hooks['post_merge']:
 
630
            hook(merge)
 
631
        if self.recurse == 'down':
 
632
            for relpath in self.this_tree.iter_references():
 
633
                sub_tree = self.this_tree.get_nested_tree(relpath)
 
634
                other_revision = self.other_tree.get_reference_revision(
 
635
                    relpath)
 
636
                if other_revision == sub_tree.last_revision():
 
637
                    continue
 
638
                sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
639
                sub_merge.merge_type = self.merge_type
 
640
                other_branch = self.other_tree.reference_parent(relpath)
 
641
                sub_merge.set_other_revision(other_revision, other_branch)
 
642
                base_tree_path = _mod_tree.find_previous_path(
 
643
                    self.this_tree, self.base_tree, relpath)
 
644
                base_revision = self.base_tree.get_reference_revision(
 
645
                    base_tree_path)
 
646
                sub_merge.base_tree = \
 
647
                    sub_tree.branch.repository.revision_tree(base_revision)
 
648
                sub_merge.base_rev_id = base_revision
 
649
                sub_merge.do_merge()
 
650
        return merge
 
651
 
 
652
    def do_merge(self):
 
653
        with contextlib.ExitStack() as stack:
 
654
            stack.enter_context(self.this_tree.lock_tree_write())
 
655
            if self.base_tree is not None:
 
656
                stack.enter_context(self.base_tree.lock_read())
 
657
            if self.other_tree is not None:
 
658
                stack.enter_context(self.other_tree.lock_read())
 
659
            merge = self._do_merge_to()
 
660
        if len(merge.cooked_conflicts) == 0:
 
661
            if not self.ignore_zero and not trace.is_quiet():
 
662
                trace.note(gettext("All changes applied successfully."))
 
663
        else:
 
664
            trace.note(gettext("%d conflicts encountered.")
 
665
                       % len(merge.cooked_conflicts))
 
666
 
 
667
        return len(merge.cooked_conflicts)
 
668
 
 
669
 
 
670
class _InventoryNoneEntry(object):
 
671
    """This represents an inventory entry which *isn't there*.
 
672
 
 
673
    It simplifies the merging logic if we always have an InventoryEntry, even
 
674
    if it isn't actually present
 
675
    """
 
676
    executable = None
 
677
    kind = None
 
678
    name = None
 
679
    parent_id = None
 
680
    revision = None
 
681
    symlink_target = None
 
682
    text_sha1 = None
 
683
 
 
684
    def is_unmodified(self, other):
 
685
        return other is self
 
686
 
 
687
 
 
688
_none_entry = _InventoryNoneEntry()
 
689
 
 
690
 
 
691
class Merge3Merger(object):
 
692
    """Three-way merger that uses the merge3 text merger"""
 
693
    requires_base = True
 
694
    supports_reprocess = True
 
695
    supports_show_base = True
 
696
    history_based = False
 
697
    supports_cherrypick = True
 
698
    supports_reverse_cherrypick = True
 
699
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
 
700
    supports_lca_trees = True
 
701
    requires_file_merge_plan = False
 
702
 
 
703
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
 
704
                 reprocess=False, show_base=False,
 
705
                 change_reporter=None, interesting_files=None, do_merge=True,
 
706
                 cherrypick=False, lca_trees=None, this_branch=None,
 
707
                 other_branch=None):
 
708
        """Initialize the merger object and perform the merge.
 
709
 
 
710
        :param working_tree: The working tree to apply the merge to
 
711
        :param this_tree: The local tree in the merge operation
 
712
        :param base_tree: The common tree in the merge operation
 
713
        :param other_tree: The other tree to merge changes from
 
714
        :param this_branch: The branch associated with this_tree.  Defaults to
 
715
            this_tree.branch if not supplied.
 
716
        :param other_branch: The branch associated with other_tree, if any.
 
717
        :param: reprocess If True, perform conflict-reduction processing.
 
718
        :param show_base: If True, show the base revision in text conflicts.
 
719
            (incompatible with reprocess)
 
720
        :param change_reporter: An object that should report changes made
 
721
        :param interesting_files: The tree-relative paths of files that should
 
722
            participate in the merge.  If these paths refer to directories,
 
723
            the contents of those directories will also be included.  If not
 
724
            specified, all files may participate in the
 
725
            merge.
 
726
        :param lca_trees: Can be set to a dictionary of {revision_id:rev_tree}
 
727
            if the ancestry was found to include a criss-cross merge.
 
728
            Otherwise should be None.
 
729
        """
 
730
        object.__init__(self)
 
731
        if this_branch is None:
 
732
            this_branch = this_tree.branch
 
733
        self.interesting_files = interesting_files
 
734
        self.working_tree = working_tree
 
735
        self.this_tree = this_tree
 
736
        self.base_tree = base_tree
 
737
        self.other_tree = other_tree
 
738
        self.this_branch = this_branch
 
739
        self.other_branch = other_branch
 
740
        self._raw_conflicts = []
 
741
        self.cooked_conflicts = []
 
742
        self.reprocess = reprocess
 
743
        self.show_base = show_base
 
744
        self._lca_trees = lca_trees
 
745
        # Uncommenting this will change the default algorithm to always use
 
746
        # _entries_lca. This can be useful for running the test suite and
 
747
        # making sure we haven't missed any corner cases.
 
748
        # if lca_trees is None:
 
749
        #     self._lca_trees = [self.base_tree]
 
750
        self.change_reporter = change_reporter
 
751
        self.cherrypick = cherrypick
 
752
        if do_merge:
 
753
            self.do_merge()
 
754
 
 
755
    def do_merge(self):
 
756
        with contextlib.ExitStack() as stack:
 
757
            stack.enter_context(self.working_tree.lock_tree_write())
 
758
            stack.enter_context(self.this_tree.lock_read())
 
759
            stack.enter_context(self.base_tree.lock_read())
 
760
            stack.enter_context(self.other_tree.lock_read())
 
761
            self.tt = self.working_tree.transform()
 
762
            stack.enter_context(self.tt)
 
763
            self._compute_transform()
 
764
            results = self.tt.apply(no_conflicts=True)
 
765
            self.write_modified(results)
 
766
            try:
 
767
                self.working_tree.add_conflicts(self.cooked_conflicts)
 
768
            except errors.UnsupportedOperation:
 
769
                pass
 
770
 
 
771
    def make_preview_transform(self):
 
772
        with self.base_tree.lock_read(), self.other_tree.lock_read():
 
773
            self.tt = self.working_tree.preview_transform()
 
774
            self._compute_transform()
 
775
            return self.tt
 
776
 
 
777
    def _compute_transform(self):
 
778
        if self._lca_trees is None:
 
779
            entries = list(self._entries3())
 
780
            resolver = self._three_way
 
781
        else:
 
782
            entries = list(self._entries_lca())
 
783
            resolver = self._lca_multi_way
 
784
        # Prepare merge hooks
 
785
        factories = Merger.hooks['merge_file_content']
 
786
        # One hook for each registered one plus our default merger
 
787
        hooks = [factory(self) for factory in factories] + [self]
 
788
        self.active_hooks = [hook for hook in hooks if hook is not None]
 
789
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
790
            for num, (file_id, changed, paths3, parents3, names3,
 
791
                      executable3) in enumerate(entries):
 
792
                trans_id = self.tt.trans_id_file_id(file_id)
 
793
                # Try merging each entry
 
794
                child_pb.update(gettext('Preparing file merge'),
 
795
                                num, len(entries))
 
796
                self._merge_names(trans_id, file_id, paths3, parents3,
 
797
                                  names3, resolver=resolver)
 
798
                if changed:
 
799
                    file_status = self._do_merge_contents(paths3, trans_id, file_id)
 
800
                else:
 
801
                    file_status = 'unmodified'
 
802
                self._merge_executable(paths3, trans_id, executable3,
 
803
                                       file_status, resolver=resolver)
 
804
        self.tt.fixup_new_roots()
 
805
        self._finish_computing_transform()
 
806
 
 
807
    def _finish_computing_transform(self):
 
808
        """Finalize the transform and report the changes.
 
809
 
 
810
        This is the second half of _compute_transform.
 
811
        """
 
812
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
813
            fs_conflicts = transform.resolve_conflicts(
 
814
                self.tt, child_pb,
 
815
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
 
816
        if self.change_reporter is not None:
 
817
            from breezy import delta
 
818
            delta.report_changes(
 
819
                self.tt.iter_changes(), self.change_reporter)
 
820
        self.cook_conflicts(fs_conflicts)
 
821
        for conflict in self.cooked_conflicts:
 
822
            trace.warning('%s', conflict.describe())
 
823
 
 
824
    def _entries3(self):
 
825
        """Gather data about files modified between three trees.
 
826
 
 
827
        Return a list of tuples of file_id, changed, parents3, names3,
 
828
        executable3.  changed is a boolean indicating whether the file contents
 
829
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
830
        other and this.  names3 is a tuple of names for base, other and this.
 
831
        executable3 is a tuple of execute-bit values for base, other and this.
 
832
        """
 
833
        iterator = self.other_tree.iter_changes(self.base_tree,
 
834
                                                specific_files=self.interesting_files,
 
835
                                                extra_trees=[self.this_tree])
 
836
        this_interesting_files = self.this_tree.find_related_paths_across_trees(
 
837
            self.interesting_files, trees=[self.other_tree])
 
838
        this_entries = dict(self.this_tree.iter_entries_by_dir(
 
839
                            specific_files=this_interesting_files))
 
840
        for change in iterator:
 
841
            if change.path[0] is not None:
 
842
                this_path = _mod_tree.find_previous_path(
 
843
                    self.base_tree, self.this_tree, change.path[0])
 
844
            else:
 
845
                this_path = _mod_tree.find_previous_path(
 
846
                    self.other_tree, self.this_tree, change.path[1])
 
847
            this_entry = this_entries.get(this_path)
 
848
            if this_entry is not None:
 
849
                this_name = this_entry.name
 
850
                this_parent = this_entry.parent_id
 
851
                this_executable = this_entry.executable
 
852
            else:
 
853
                this_name = None
 
854
                this_parent = None
 
855
                this_executable = None
 
856
            parents3 = change.parent_id + (this_parent,)
 
857
            names3 = change.name + (this_name,)
 
858
            paths3 = change.path + (this_path, )
 
859
            executable3 = change.executable + (this_executable,)
 
860
            yield (
 
861
                (change.file_id, change.changed_content, paths3,
 
862
                 parents3, names3, executable3))
 
863
 
 
864
    def _entries_lca(self):
 
865
        """Gather data about files modified between multiple trees.
 
866
 
 
867
        This compares OTHER versus all LCA trees, and for interesting entries,
 
868
        it then compares with THIS and BASE.
 
869
 
 
870
        For the multi-valued entries, the format will be (BASE, [lca1, lca2])
 
871
 
 
872
        :return: [(file_id, changed, paths, parents, names, executable)], where:
 
873
 
 
874
            * file_id: Simple file_id of the entry
 
875
            * changed: Boolean, True if the kind or contents changed else False
 
876
            * paths: ((base, [path, in, lcas]), path_other, path_this)
 
877
            * parents: ((base, [parent_id, in, lcas]), parent_id_other,
 
878
                        parent_id_this)
 
879
            * names:   ((base, [name, in, lcas]), name_in_other, name_in_this)
 
880
            * executable: ((base, [exec, in, lcas]), exec_in_other,
 
881
                        exec_in_this)
 
882
        """
 
883
        if self.interesting_files is not None:
 
884
            lookup_trees = [self.this_tree, self.base_tree]
 
885
            lookup_trees.extend(self._lca_trees)
 
886
            # I think we should include the lca trees as well
 
887
            interesting_files = self.other_tree.find_related_paths_across_trees(
 
888
                self.interesting_files, lookup_trees)
 
889
        else:
 
890
            interesting_files = None
 
891
        from .multiwalker import MultiWalker
 
892
        walker = MultiWalker(self.other_tree, self._lca_trees)
 
893
 
 
894
        for other_path, file_id, other_ie, lca_values in walker.iter_all():
 
895
            # Is this modified at all from any of the other trees?
 
896
            if other_ie is None:
 
897
                other_ie = _none_entry
 
898
                other_path = None
 
899
            if interesting_files is not None and other_path not in interesting_files:
 
900
                continue
 
901
 
 
902
            # If other_revision is found in any of the lcas, that means this
 
903
            # node is uninteresting. This is because when merging, if there are
 
904
            # multiple heads(), we have to create a new node. So if we didn't,
 
905
            # we know that the ancestry is linear, and that OTHER did not
 
906
            # modify anything
 
907
            # See doc/developers/lca_merge_resolution.txt for details
 
908
            # We can't use this shortcut when other_revision is None,
 
909
            # because it may be None because things are WorkingTrees, and
 
910
            # not because it is *actually* None.
 
911
            is_unmodified = False
 
912
            for lca_path, ie in lca_values:
 
913
                if ie is not None and other_ie.is_unmodified(ie):
 
914
                    is_unmodified = True
 
915
                    break
 
916
            if is_unmodified:
 
917
                continue
 
918
 
 
919
            lca_entries = []
 
920
            lca_paths = []
 
921
            for lca_path, lca_ie in lca_values:
 
922
                if lca_ie is None:
 
923
                    lca_entries.append(_none_entry)
 
924
                    lca_paths.append(None)
 
925
                else:
 
926
                    lca_entries.append(lca_ie)
 
927
                    lca_paths.append(lca_path)
 
928
 
 
929
            try:
 
930
                base_path = self.base_tree.id2path(file_id)
 
931
            except errors.NoSuchId:
 
932
                base_path = None
 
933
                base_ie = _none_entry
 
934
            else:
 
935
                base_ie = next(self.base_tree.iter_entries_by_dir(specific_files=[base_path]))[1]
 
936
 
 
937
            try:
 
938
                this_path = self.this_tree.id2path(file_id)
 
939
            except errors.NoSuchId:
 
940
                this_ie = _none_entry
 
941
                this_path = None
 
942
            else:
 
943
                this_ie = next(self.this_tree.iter_entries_by_dir(specific_files=[this_path]))[1]
 
944
 
 
945
            lca_kinds = []
 
946
            lca_parent_ids = []
 
947
            lca_names = []
 
948
            lca_executable = []
 
949
            for lca_ie in lca_entries:
 
950
                lca_kinds.append(lca_ie.kind)
 
951
                lca_parent_ids.append(lca_ie.parent_id)
 
952
                lca_names.append(lca_ie.name)
 
953
                lca_executable.append(lca_ie.executable)
 
954
 
 
955
            kind_winner = self._lca_multi_way(
 
956
                (base_ie.kind, lca_kinds),
 
957
                other_ie.kind, this_ie.kind)
 
958
            parent_id_winner = self._lca_multi_way(
 
959
                (base_ie.parent_id, lca_parent_ids),
 
960
                other_ie.parent_id, this_ie.parent_id)
 
961
            name_winner = self._lca_multi_way(
 
962
                (base_ie.name, lca_names),
 
963
                other_ie.name, this_ie.name)
 
964
 
 
965
            content_changed = True
 
966
            if kind_winner == 'this':
 
967
                # No kind change in OTHER, see if there are *any* changes
 
968
                if other_ie.kind == 'directory':
 
969
                    if parent_id_winner == 'this' and name_winner == 'this':
 
970
                        # No change for this directory in OTHER, skip
 
971
                        continue
 
972
                    content_changed = False
 
973
                elif other_ie.kind is None or other_ie.kind == 'file':
 
974
                    def get_sha1(tree, path):
 
975
                        if path is None:
 
976
                            return None
 
977
                        try:
 
978
                            return tree.get_file_sha1(path)
 
979
                        except errors.NoSuchFile:
 
980
                            return None
 
981
                    base_sha1 = get_sha1(self.base_tree, base_path)
 
982
                    lca_sha1s = [get_sha1(tree, lca_path)
 
983
                                 for tree, lca_path
 
984
                                 in zip(self._lca_trees, lca_paths)]
 
985
                    this_sha1 = get_sha1(self.this_tree, this_path)
 
986
                    other_sha1 = get_sha1(self.other_tree, other_path)
 
987
                    sha1_winner = self._lca_multi_way(
 
988
                        (base_sha1, lca_sha1s), other_sha1, this_sha1,
 
989
                        allow_overriding_lca=False)
 
990
                    exec_winner = self._lca_multi_way(
 
991
                        (base_ie.executable, lca_executable),
 
992
                        other_ie.executable, this_ie.executable)
 
993
                    if (parent_id_winner == 'this' and name_winner == 'this'
 
994
                            and sha1_winner == 'this' and exec_winner == 'this'):
 
995
                        # No kind, parent, name, exec, or content change for
 
996
                        # OTHER, so this node is not considered interesting
 
997
                        continue
 
998
                    if sha1_winner == 'this':
 
999
                        content_changed = False
 
1000
                elif other_ie.kind == 'symlink':
 
1001
                    def get_target(ie, tree, path):
 
1002
                        if ie.kind != 'symlink':
 
1003
                            return None
 
1004
                        return tree.get_symlink_target(path)
 
1005
                    base_target = get_target(base_ie, self.base_tree, base_path)
 
1006
                    lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
 
1007
                                   in zip(lca_entries, self._lca_trees, lca_paths)]
 
1008
                    this_target = get_target(
 
1009
                        this_ie, self.this_tree, this_path)
 
1010
                    other_target = get_target(
 
1011
                        other_ie, self.other_tree, other_path)
 
1012
                    target_winner = self._lca_multi_way(
 
1013
                        (base_target, lca_targets),
 
1014
                        other_target, this_target)
 
1015
                    if (parent_id_winner == 'this' and name_winner == 'this'
 
1016
                            and target_winner == 'this'):
 
1017
                        # No kind, parent, name, or symlink target change
 
1018
                        # not interesting
 
1019
                        continue
 
1020
                    if target_winner == 'this':
 
1021
                        content_changed = False
 
1022
                elif other_ie.kind == 'tree-reference':
 
1023
                    # The 'changed' information seems to be handled at a higher
 
1024
                    # level. At least, _entries3 returns False for content
 
1025
                    # changed, even when at a new revision_id.
 
1026
                    content_changed = False
 
1027
                    if (parent_id_winner == 'this' and name_winner == 'this'):
 
1028
                        # Nothing interesting
 
1029
                        continue
 
1030
                else:
 
1031
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1032
 
 
1033
            # If we have gotten this far, that means something has changed
 
1034
            yield (file_id, content_changed,
 
1035
                           ((base_path, lca_paths),
 
1036
                            other_path, this_path),
 
1037
                           ((base_ie.parent_id, lca_parent_ids),
 
1038
                            other_ie.parent_id, this_ie.parent_id),
 
1039
                           ((base_ie.name, lca_names),
 
1040
                            other_ie.name, this_ie.name),
 
1041
                           ((base_ie.executable, lca_executable),
 
1042
                            other_ie.executable, this_ie.executable)
 
1043
                           )
 
1044
 
 
1045
    def write_modified(self, results):
 
1046
        if not self.working_tree.supports_merge_modified():
 
1047
            return
 
1048
        modified_hashes = {}
 
1049
        for path in results.modified_paths:
 
1050
            wt_relpath = self.working_tree.relpath(path)
 
1051
            if not self.working_tree.is_versioned(wt_relpath):
 
1052
                continue
 
1053
            hash = self.working_tree.get_file_sha1(wt_relpath)
 
1054
            if hash is None:
 
1055
                continue
 
1056
            modified_hashes[wt_relpath] = hash
 
1057
        self.working_tree.set_merge_modified(modified_hashes)
 
1058
 
 
1059
    @staticmethod
 
1060
    def parent(entry):
 
1061
        """Determine the parent for a file_id (used as a key method)"""
 
1062
        if entry is None:
 
1063
            return None
 
1064
        return entry.parent_id
 
1065
 
 
1066
    @staticmethod
 
1067
    def name(entry):
 
1068
        """Determine the name for a file_id (used as a key method)"""
 
1069
        if entry is None:
 
1070
            return None
 
1071
        return entry.name
 
1072
 
 
1073
    @staticmethod
 
1074
    def contents_sha1(tree, path):
 
1075
        """Determine the sha1 of the file contents (used as a key method)."""
 
1076
        try:
 
1077
            return tree.get_file_sha1(path)
 
1078
        except errors.NoSuchFile:
 
1079
            return None
 
1080
 
 
1081
    @staticmethod
 
1082
    def executable(tree, path):
 
1083
        """Determine the executability of a file-id (used as a key method)."""
 
1084
        try:
 
1085
            if tree.kind(path) != "file":
 
1086
                return False
 
1087
        except errors.NoSuchFile:
 
1088
            return None
 
1089
        return tree.is_executable(path)
 
1090
 
 
1091
    @staticmethod
 
1092
    def kind(tree, path):
 
1093
        """Determine the kind of a file-id (used as a key method)."""
 
1094
        try:
 
1095
            return tree.kind(path)
 
1096
        except errors.NoSuchFile:
 
1097
            return None
 
1098
 
 
1099
    @staticmethod
 
1100
    def _three_way(base, other, this):
 
1101
        if base == other:
 
1102
            # if 'base == other', either they all agree, or only 'this' has
 
1103
            # changed.
 
1104
            return 'this'
 
1105
        elif this not in (base, other):
 
1106
            # 'this' is neither 'base' nor 'other', so both sides changed
 
1107
            return 'conflict'
 
1108
        elif this == other:
 
1109
            # "Ambiguous clean merge" -- both sides have made the same change.
 
1110
            return "this"
 
1111
        else:
 
1112
            # this == base: only other has changed.
 
1113
            return "other"
 
1114
 
 
1115
    @staticmethod
 
1116
    def _lca_multi_way(bases, other, this, allow_overriding_lca=True):
 
1117
        """Consider LCAs when determining whether a change has occurred.
 
1118
 
 
1119
        If LCAS are all identical, this is the same as a _three_way comparison.
 
1120
 
 
1121
        :param bases: value in (BASE, [LCAS])
 
1122
        :param other: value in OTHER
 
1123
        :param this: value in THIS
 
1124
        :param allow_overriding_lca: If there is more than one unique lca
 
1125
            value, allow OTHER to override THIS if it has a new value, and
 
1126
            THIS only has an lca value, or vice versa. This is appropriate for
 
1127
            truly scalar values, not as much for non-scalars.
 
1128
        :return: 'this', 'other', or 'conflict' depending on whether an entry
 
1129
            changed or not.
 
1130
        """
 
1131
        # See doc/developers/lca_tree_merging.txt for details about this
 
1132
        # algorithm.
 
1133
        if other == this:
 
1134
            # Either Ambiguously clean, or nothing was actually changed. We
 
1135
            # don't really care
 
1136
            return 'this'
 
1137
        base_val, lca_vals = bases
 
1138
        # Remove 'base_val' from the lca_vals, because it is not interesting
 
1139
        filtered_lca_vals = [lca_val for lca_val in lca_vals
 
1140
                             if lca_val != base_val]
 
1141
        if len(filtered_lca_vals) == 0:
 
1142
            return Merge3Merger._three_way(base_val, other, this)
 
1143
 
 
1144
        unique_lca_vals = set(filtered_lca_vals)
 
1145
        if len(unique_lca_vals) == 1:
 
1146
            return Merge3Merger._three_way(unique_lca_vals.pop(), other, this)
 
1147
 
 
1148
        if allow_overriding_lca:
 
1149
            if other in unique_lca_vals:
 
1150
                if this in unique_lca_vals:
 
1151
                    # Each side picked a different lca, conflict
 
1152
                    return 'conflict'
 
1153
                else:
 
1154
                    # This has a value which supersedes both lca values, and
 
1155
                    # other only has an lca value
 
1156
                    return 'this'
 
1157
            elif this in unique_lca_vals:
 
1158
                # OTHER has a value which supersedes both lca values, and this
 
1159
                # only has an lca value
 
1160
                return 'other'
 
1161
 
 
1162
        # At this point, the lcas disagree, and the tip disagree
 
1163
        return 'conflict'
 
1164
 
 
1165
    def _merge_names(self, trans_id, file_id, paths, parents, names, resolver):
 
1166
        """Perform a merge on file names and parents"""
 
1167
        base_name, other_name, this_name = names
 
1168
        base_parent, other_parent, this_parent = parents
 
1169
        unused_base_path, other_path, this_path = paths
 
1170
 
 
1171
        name_winner = resolver(*names)
 
1172
 
 
1173
        parent_id_winner = resolver(*parents)
 
1174
        if this_name is None:
 
1175
            if name_winner == "this":
 
1176
                name_winner = "other"
 
1177
            if parent_id_winner == "this":
 
1178
                parent_id_winner = "other"
 
1179
        if name_winner == "this" and parent_id_winner == "this":
 
1180
            return
 
1181
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
 
1182
            # Creating helpers (.OTHER or .THIS) here cause problems down the
 
1183
            # road if a ContentConflict needs to be created so we should not do
 
1184
            # that
 
1185
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
 
1186
                                        this_parent, this_name,
 
1187
                                        other_parent, other_name))
 
1188
        if other_path is None:
 
1189
            # it doesn't matter whether the result was 'other' or
 
1190
            # 'conflict'-- if it has no file id, we leave it alone.
 
1191
            return
 
1192
        parent_id = parents[self.winner_idx[parent_id_winner]]
 
1193
        name = names[self.winner_idx[name_winner]]
 
1194
        if parent_id is not None or name is not None:
 
1195
            # if we get here, name_winner and parent_winner are set to safe
 
1196
            # values.
 
1197
            if parent_id is None and name is not None:
 
1198
                # if parent_id is None and name is non-None, current file is
 
1199
                # the tree root.
 
1200
                if names[self.winner_idx[parent_id_winner]] != '':
 
1201
                    raise AssertionError(
 
1202
                        'File looks like a root, but named %s' %
 
1203
                        names[self.winner_idx[parent_id_winner]])
 
1204
                parent_trans_id = transform.ROOT_PARENT
 
1205
            else:
 
1206
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1207
            self.tt.adjust_path(name, parent_trans_id, trans_id)
 
1208
 
 
1209
    def _do_merge_contents(self, paths, trans_id, file_id):
 
1210
        """Performs a merge on file_id contents."""
 
1211
        def contents_pair(tree, path):
 
1212
            if path is None:
 
1213
                return (None, None)
 
1214
            try:
 
1215
                kind = tree.kind(path)
 
1216
            except errors.NoSuchFile:
 
1217
                return (None, None)
 
1218
            if kind == "file":
 
1219
                contents = tree.get_file_sha1(path)
 
1220
            elif kind == "symlink":
 
1221
                contents = tree.get_symlink_target(path)
 
1222
            else:
 
1223
                contents = None
 
1224
            return kind, contents
 
1225
 
 
1226
        base_path, other_path, this_path = paths
 
1227
        # See SPOT run.  run, SPOT, run.
 
1228
        # So we're not QUITE repeating ourselves; we do tricky things with
 
1229
        # file kind...
 
1230
        other_pair = contents_pair(self.other_tree, other_path)
 
1231
        this_pair = contents_pair(self.this_tree, this_path)
 
1232
        if self._lca_trees:
 
1233
            (base_path, lca_paths) = base_path
 
1234
            base_pair = contents_pair(self.base_tree, base_path)
 
1235
            lca_pairs = [contents_pair(tree, path)
 
1236
                         for tree, path in zip(self._lca_trees, lca_paths)]
 
1237
            winner = self._lca_multi_way((base_pair, lca_pairs), other_pair,
 
1238
                                         this_pair, allow_overriding_lca=False)
 
1239
        else:
 
1240
            base_pair = contents_pair(self.base_tree, base_path)
 
1241
            if base_pair == other_pair:
 
1242
                winner = 'this'
 
1243
            else:
 
1244
                # We delayed evaluating this_pair as long as we can to avoid
 
1245
                # unnecessary sha1 calculation
 
1246
                this_pair = contents_pair(self.this_tree, this_path)
 
1247
                winner = self._three_way(base_pair, other_pair, this_pair)
 
1248
        if winner == 'this':
 
1249
            # No interesting changes introduced by OTHER
 
1250
            return "unmodified"
 
1251
        # We have a hypothetical conflict, but if we have files, then we
 
1252
        # can try to merge the content
 
1253
        params = MergeFileHookParams(
 
1254
            self, (base_path, other_path, this_path), trans_id, this_pair[0],
 
1255
            other_pair[0], winner)
 
1256
        hooks = self.active_hooks
 
1257
        hook_status = 'not_applicable'
 
1258
        for hook in hooks:
 
1259
            hook_status, lines = hook.merge_contents(params)
 
1260
            if hook_status != 'not_applicable':
 
1261
                # Don't try any more hooks, this one applies.
 
1262
                break
 
1263
        # If the merge ends up replacing the content of the file, we get rid of
 
1264
        # it at the end of this method (this variable is used to track the
 
1265
        # exceptions to this rule).
 
1266
        keep_this = False
 
1267
        result = "modified"
 
1268
        if hook_status == 'not_applicable':
 
1269
            # No merge hook was able to resolve the situation. Two cases exist:
 
1270
            # a content conflict or a duplicate one.
 
1271
            result = None
 
1272
            name = self.tt.final_name(trans_id)
 
1273
            parent_id = self.tt.final_parent(trans_id)
 
1274
            inhibit_content_conflict = False
 
1275
            if params.this_kind is None:  # file_id is not in THIS
 
1276
                # Is the name used for a different file_id ?
 
1277
                if self.this_tree.is_versioned(other_path):
 
1278
                    # Two entries for the same path
 
1279
                    keep_this = True
 
1280
                    # versioning the merged file will trigger a duplicate
 
1281
                    # conflict
 
1282
                    self.tt.version_file(trans_id, file_id=file_id)
 
1283
                    transform.create_from_tree(
 
1284
                        self.tt, trans_id, self.other_tree,
 
1285
                        other_path,
 
1286
                        filter_tree_path=self._get_filter_tree_path(other_path))
 
1287
                    inhibit_content_conflict = True
 
1288
            elif params.other_kind is None:  # file_id is not in OTHER
 
1289
                # Is the name used for a different file_id ?
 
1290
                if self.other_tree.is_versioned(this_path):
 
1291
                    # Two entries for the same path again, but here, the other
 
1292
                    # entry will also be merged.  We simply inhibit the
 
1293
                    # 'content' conflict creation because we know OTHER will
 
1294
                    # create (or has already created depending on ordering) an
 
1295
                    # entry at the same path. This will trigger a 'duplicate'
 
1296
                    # conflict later.
 
1297
                    keep_this = True
 
1298
                    inhibit_content_conflict = True
 
1299
            if not inhibit_content_conflict:
 
1300
                if params.this_kind is not None:
 
1301
                    self.tt.unversion_file(trans_id)
 
1302
                # This is a contents conflict, because none of the available
 
1303
                # functions could merge it.
 
1304
                file_group = self._dump_conflicts(
 
1305
                    name, (base_path, other_path, this_path), parent_id,
 
1306
                    file_id, set_version=True)
 
1307
                self._raw_conflicts.append(('contents conflict', file_group))
 
1308
        elif hook_status == 'success':
 
1309
            self.tt.create_file(lines, trans_id)
 
1310
        elif hook_status == 'conflicted':
 
1311
            # XXX: perhaps the hook should be able to provide
 
1312
            # the BASE/THIS/OTHER files?
 
1313
            self.tt.create_file(lines, trans_id)
 
1314
            self._raw_conflicts.append(('text conflict', trans_id))
 
1315
            name = self.tt.final_name(trans_id)
 
1316
            parent_id = self.tt.final_parent(trans_id)
 
1317
            self._dump_conflicts(
 
1318
                name, (base_path, other_path, this_path), parent_id, file_id)
 
1319
        elif hook_status == 'delete':
 
1320
            self.tt.unversion_file(trans_id)
 
1321
            result = "deleted"
 
1322
        elif hook_status == 'done':
 
1323
            # The hook function did whatever it needs to do directly, no
 
1324
            # further action needed here.
 
1325
            pass
 
1326
        else:
 
1327
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
 
1328
        if not this_path and result == "modified":
 
1329
            self.tt.version_file(trans_id, file_id=file_id)
 
1330
        if not keep_this:
 
1331
            # The merge has been performed and produced a new content, so the
 
1332
            # old contents should not be retained.
 
1333
            self.tt.delete_contents(trans_id)
 
1334
        return result
 
1335
 
 
1336
    def _default_other_winner_merge(self, merge_hook_params):
 
1337
        """Replace this contents with other."""
 
1338
        trans_id = merge_hook_params.trans_id
 
1339
        if merge_hook_params.other_path is not None:
 
1340
            # OTHER changed the file
 
1341
            transform.create_from_tree(
 
1342
                self.tt, trans_id, self.other_tree,
 
1343
                merge_hook_params.other_path,
 
1344
                filter_tree_path=self._get_filter_tree_path(merge_hook_params.other_path))
 
1345
            return 'done', None
 
1346
        elif merge_hook_params.this_path is not None:
 
1347
            # OTHER deleted the file
 
1348
            return 'delete', None
 
1349
        else:
 
1350
            raise AssertionError(
 
1351
                'winner is OTHER, but file %r not in THIS or OTHER tree'
 
1352
                % (merge_hook_params.base_path,))
 
1353
 
 
1354
    def merge_contents(self, merge_hook_params):
 
1355
        """Fallback merge logic after user installed hooks."""
 
1356
        # This function is used in merge hooks as the fallback instance.
 
1357
        # Perhaps making this function and the functions it calls be a
 
1358
        # a separate class would be better.
 
1359
        if merge_hook_params.winner == 'other':
 
1360
            # OTHER is a straight winner, so replace this contents with other
 
1361
            return self._default_other_winner_merge(merge_hook_params)
 
1362
        elif merge_hook_params.is_file_merge():
 
1363
            # THIS and OTHER are both files, so text merge.  Either
 
1364
            # BASE is a file, or both converted to files, so at least we
 
1365
            # have agreement that output should be a file.
 
1366
            try:
 
1367
                self.text_merge(merge_hook_params.trans_id,
 
1368
                                merge_hook_params.paths)
 
1369
            except errors.BinaryFile:
 
1370
                return 'not_applicable', None
 
1371
            return 'done', None
 
1372
        else:
 
1373
            return 'not_applicable', None
 
1374
 
 
1375
    def get_lines(self, tree, path):
 
1376
        """Return the lines in a file, or an empty list."""
 
1377
        if path is None:
 
1378
            return []
 
1379
        try:
 
1380
            kind = tree.kind(path)
 
1381
        except errors.NoSuchFile:
 
1382
            return []
 
1383
        else:
 
1384
            if kind != 'file':
 
1385
                return []
 
1386
            return tree.get_file_lines(path)
 
1387
 
 
1388
    def text_merge(self, trans_id, paths):
 
1389
        """Perform a three-way text merge on a file"""
 
1390
        # it's possible that we got here with base as a different type.
 
1391
        # if so, we just want two-way text conflicts.
 
1392
        base_path, other_path, this_path = paths
 
1393
        base_lines = self.get_lines(self.base_tree, base_path)
 
1394
        other_lines = self.get_lines(self.other_tree, other_path)
 
1395
        this_lines = self.get_lines(self.this_tree, this_path)
 
1396
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
 
1397
                           is_cherrypick=self.cherrypick)
 
1398
        start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
 
1399
        if self.show_base is True:
 
1400
            base_marker = b'|' * 7
 
1401
        else:
 
1402
            base_marker = None
 
1403
 
 
1404
        def iter_merge3(retval):
 
1405
            retval["text_conflicts"] = False
 
1406
            for line in m3.merge_lines(name_a=b"TREE",
 
1407
                                       name_b=b"MERGE-SOURCE",
 
1408
                                       name_base=b"BASE-REVISION",
 
1409
                                       start_marker=start_marker,
 
1410
                                       base_marker=base_marker,
 
1411
                                       reprocess=self.reprocess):
 
1412
                if line.startswith(start_marker):
 
1413
                    retval["text_conflicts"] = True
 
1414
                    yield line.replace(start_marker, b'<' * 7)
 
1415
                else:
 
1416
                    yield line
 
1417
        retval = {}
 
1418
        merge3_iterator = iter_merge3(retval)
 
1419
        self.tt.create_file(merge3_iterator, trans_id)
 
1420
        if retval["text_conflicts"] is True:
 
1421
            self._raw_conflicts.append(('text conflict', trans_id))
 
1422
            name = self.tt.final_name(trans_id)
 
1423
            parent_id = self.tt.final_parent(trans_id)
 
1424
            file_id = self.tt.final_file_id(trans_id)
 
1425
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
 
1426
                                              this_lines, base_lines,
 
1427
                                              other_lines)
 
1428
            file_group.append(trans_id)
 
1429
 
 
1430
    def _get_filter_tree_path(self, path):
 
1431
        if self.this_tree.supports_content_filtering():
 
1432
            # We get the path from the working tree if it exists.
 
1433
            # That fails though when OTHER is adding a file, so
 
1434
            # we fall back to the other tree to find the path if
 
1435
            # it doesn't exist locally.
 
1436
            filter_path = _mod_tree.find_previous_path(
 
1437
                self.other_tree, self.working_tree, path)
 
1438
            if filter_path is None:
 
1439
                filter_path = path
 
1440
            return filter_path
 
1441
        # Skip the lookup for older formats
 
1442
        return None
 
1443
 
 
1444
    def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
 
1445
                        base_lines=None, other_lines=None, set_version=False,
 
1446
                        no_base=False):
 
1447
        """Emit conflict files.
 
1448
        If this_lines, base_lines, or other_lines are omitted, they will be
 
1449
        determined automatically.  If set_version is true, the .OTHER, .THIS
 
1450
        or .BASE (in that order) will be created as versioned files.
 
1451
        """
 
1452
        base_path, other_path, this_path = paths
 
1453
        data = [('OTHER', self.other_tree, other_path, other_lines),
 
1454
                ('THIS', self.this_tree, this_path, this_lines)]
 
1455
        if not no_base:
 
1456
            data.append(('BASE', self.base_tree, base_path, base_lines))
 
1457
 
 
1458
        # We need to use the actual path in the working tree of the file here,
 
1459
        if self.this_tree.supports_content_filtering():
 
1460
            filter_tree_path = this_path
 
1461
        else:
 
1462
            # Skip the id2path lookup for older formats
 
1463
            filter_tree_path = None
 
1464
 
 
1465
        versioned = False
 
1466
        file_group = []
 
1467
        for suffix, tree, path, lines in data:
 
1468
            if path is not None:
 
1469
                trans_id = self._conflict_file(
 
1470
                    name, parent_id, path, tree, suffix, lines,
 
1471
                    filter_tree_path)
 
1472
                file_group.append(trans_id)
 
1473
                if set_version and not versioned:
 
1474
                    self.tt.version_file(trans_id, file_id=file_id)
 
1475
                    versioned = True
 
1476
        return file_group
 
1477
 
 
1478
    def _conflict_file(self, name, parent_id, path, tree, suffix,
 
1479
                       lines=None, filter_tree_path=None):
 
1480
        """Emit a single conflict file."""
 
1481
        name = name + '.' + suffix
 
1482
        trans_id = self.tt.create_path(name, parent_id)
 
1483
        transform.create_from_tree(
 
1484
            self.tt, trans_id, tree, path,
 
1485
            chunks=lines,
 
1486
            filter_tree_path=filter_tree_path)
 
1487
        return trans_id
 
1488
 
 
1489
    def _merge_executable(self, paths, trans_id, executable, file_status,
 
1490
                          resolver):
 
1491
        """Perform a merge on the execute bit."""
 
1492
        base_executable, other_executable, this_executable = executable
 
1493
        base_path, other_path, this_path = paths
 
1494
        if file_status == "deleted":
 
1495
            return
 
1496
        winner = resolver(*executable)
 
1497
        if winner == "conflict":
 
1498
            # There must be a None in here, if we have a conflict, but we
 
1499
            # need executability since file status was not deleted.
 
1500
            if other_path is None:
 
1501
                winner = "this"
 
1502
            else:
 
1503
                winner = "other"
 
1504
        if winner == 'this' and file_status != "modified":
 
1505
            return
 
1506
        if self.tt.final_kind(trans_id) != "file":
 
1507
            return
 
1508
        if winner == "this":
 
1509
            executability = this_executable
 
1510
        else:
 
1511
            if other_path is not None:
 
1512
                executability = other_executable
 
1513
            elif this_path is not None:
 
1514
                executability = this_executable
 
1515
            elif base_path is not None:
 
1516
                executability = base_executable
 
1517
        if executability is not None:
 
1518
            self.tt.set_executability(executability, trans_id)
 
1519
 
 
1520
    def cook_conflicts(self, fs_conflicts):
 
1521
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1522
        content_conflict_file_ids = set()
 
1523
        cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
 
1524
        fp = transform.FinalPaths(self.tt)
 
1525
        for conflict in self._raw_conflicts:
 
1526
            conflict_type = conflict[0]
 
1527
            if conflict_type == 'path conflict':
 
1528
                (trans_id, file_id,
 
1529
                 this_parent, this_name,
 
1530
                 other_parent, other_name) = conflict[1:]
 
1531
                if this_parent is None or this_name is None:
 
1532
                    this_path = '<deleted>'
 
1533
                else:
 
1534
                    parent_path = fp.get_path(
 
1535
                        self.tt.trans_id_file_id(this_parent))
 
1536
                    this_path = osutils.pathjoin(parent_path, this_name)
 
1537
                if other_parent is None or other_name is None:
 
1538
                    other_path = '<deleted>'
 
1539
                else:
 
1540
                    if other_parent == self.other_tree.path2id(''):
 
1541
                        # The tree transform doesn't know about the other root,
 
1542
                        # so we special case here to avoid a NoFinalPath
 
1543
                        # exception
 
1544
                        parent_path = ''
 
1545
                    else:
 
1546
                        parent_path = fp.get_path(
 
1547
                            self.tt.trans_id_file_id(other_parent))
 
1548
                    other_path = osutils.pathjoin(parent_path, other_name)
 
1549
                c = _mod_conflicts.Conflict.factory(
 
1550
                    'path conflict', path=this_path,
 
1551
                    conflict_path=other_path,
 
1552
                    file_id=file_id)
 
1553
            elif conflict_type == 'contents conflict':
 
1554
                for trans_id in conflict[1]:
 
1555
                    file_id = self.tt.final_file_id(trans_id)
 
1556
                    if file_id is not None:
 
1557
                        # Ok we found the relevant file-id
 
1558
                        break
 
1559
                path = fp.get_path(trans_id)
 
1560
                for suffix in ('.BASE', '.THIS', '.OTHER'):
 
1561
                    if path.endswith(suffix):
 
1562
                        # Here is the raw path
 
1563
                        path = path[:-len(suffix)]
 
1564
                        break
 
1565
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1566
                                                    path=path, file_id=file_id)
 
1567
                content_conflict_file_ids.add(file_id)
 
1568
            elif conflict_type == 'text conflict':
 
1569
                trans_id = conflict[1]
 
1570
                path = fp.get_path(trans_id)
 
1571
                file_id = self.tt.final_file_id(trans_id)
 
1572
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1573
                                                    path=path, file_id=file_id)
 
1574
            else:
 
1575
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1576
            cooked_conflicts.append(c)
 
1577
 
 
1578
        self.cooked_conflicts = []
 
1579
        # We want to get rid of path conflicts when a corresponding contents
 
1580
        # conflict exists. This can occur when one branch deletes a file while
 
1581
        # the other renames *and* modifies it. In this case, the content
 
1582
        # conflict is enough.
 
1583
        for c in cooked_conflicts:
 
1584
            if (c.typestring == 'path conflict'
 
1585
                    and c.file_id in content_conflict_file_ids):
 
1586
                continue
 
1587
            self.cooked_conflicts.append(c)
 
1588
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
 
1589
 
 
1590
 
 
1591
class WeaveMerger(Merge3Merger):
 
1592
    """Three-way tree merger, text weave merger."""
 
1593
    supports_reprocess = True
 
1594
    supports_show_base = False
 
1595
    supports_reverse_cherrypick = False
 
1596
    history_based = True
 
1597
    requires_file_merge_plan = True
 
1598
 
 
1599
    def _generate_merge_plan(self, this_path, base):
 
1600
        return self.this_tree.plan_file_merge(this_path, self.other_tree,
 
1601
                                              base=base)
 
1602
 
 
1603
    def _merged_lines(self, this_path):
 
1604
        """Generate the merged lines.
 
1605
        There is no distinction between lines that are meant to contain <<<<<<<
 
1606
        and conflicts.
 
1607
        """
 
1608
        if self.cherrypick:
 
1609
            base = self.base_tree
 
1610
        else:
 
1611
            base = None
 
1612
        plan = self._generate_merge_plan(this_path, base)
 
1613
        if 'merge' in debug.debug_flags:
 
1614
            plan = list(plan)
 
1615
            trans_id = self.tt.trans_id_file_id(file_id)
 
1616
            name = self.tt.final_name(trans_id) + '.plan'
 
1617
            contents = (b'%11s|%s' % l for l in plan)
 
1618
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
 
1619
        textmerge = versionedfile.PlanWeaveMerge(plan, b'<<<<<<< TREE\n',
 
1620
                                                 b'>>>>>>> MERGE-SOURCE\n')
 
1621
        lines, conflicts = textmerge.merge_lines(self.reprocess)
 
1622
        if conflicts:
 
1623
            base_lines = textmerge.base_from_plan()
 
1624
        else:
 
1625
            base_lines = None
 
1626
        return lines, base_lines
 
1627
 
 
1628
    def text_merge(self, trans_id, paths):
 
1629
        """Perform a (weave) text merge for a given file and file-id.
 
1630
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
 
1631
        and a conflict will be noted.
 
1632
        """
 
1633
        base_path, other_path, this_path = paths
 
1634
        lines, base_lines = self._merged_lines(this_path)
 
1635
        lines = list(lines)
 
1636
        # Note we're checking whether the OUTPUT is binary in this case,
 
1637
        # because we don't want to get into weave merge guts.
 
1638
        textfile.check_text_lines(lines)
 
1639
        self.tt.create_file(lines, trans_id)
 
1640
        if base_lines is not None:
 
1641
            # Conflict
 
1642
            self._raw_conflicts.append(('text conflict', trans_id))
 
1643
            name = self.tt.final_name(trans_id)
 
1644
            parent_id = self.tt.final_parent(trans_id)
 
1645
            file_id = self.tt.final_file_id(trans_id)
 
1646
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
 
1647
                                              no_base=False,
 
1648
                                              base_lines=base_lines)
 
1649
            file_group.append(trans_id)
 
1650
 
 
1651
 
 
1652
class LCAMerger(WeaveMerger):
 
1653
 
 
1654
    requires_file_merge_plan = True
 
1655
 
 
1656
    def _generate_merge_plan(self, this_path, base):
 
1657
        return self.this_tree.plan_file_lca_merge(this_path, self.other_tree,
 
1658
                                                  base=base)
 
1659
 
 
1660
 
 
1661
class Diff3Merger(Merge3Merger):
 
1662
    """Three-way merger using external diff3 for text merging"""
 
1663
 
 
1664
    requires_file_merge_plan = False
 
1665
 
 
1666
    def dump_file(self, temp_dir, name, tree, path):
 
1667
        out_path = osutils.pathjoin(temp_dir, name)
 
1668
        with open(out_path, "wb") as out_file:
 
1669
            in_file = tree.get_file(path)
 
1670
            for line in in_file:
 
1671
                out_file.write(line)
 
1672
        return out_path
 
1673
 
 
1674
    def text_merge(self, trans_id, paths):
 
1675
        """Perform a diff3 merge using a specified file-id and trans-id.
 
1676
        If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
 
1677
        will be dumped, and a will be conflict noted.
 
1678
        """
 
1679
        import breezy.patch
 
1680
        base_path, other_path, this_path = paths
 
1681
        temp_dir = osutils.mkdtemp(prefix="bzr-")
 
1682
        try:
 
1683
            new_file = osutils.pathjoin(temp_dir, "new")
 
1684
            this = self.dump_file(
 
1685
                temp_dir, "this", self.this_tree, this_path)
 
1686
            base = self.dump_file(
 
1687
                temp_dir, "base", self.base_tree, base_path)
 
1688
            other = self.dump_file(
 
1689
                temp_dir, "other", self.other_tree, other_path)
 
1690
            status = breezy.patch.diff3(new_file, this, base, other)
 
1691
            if status not in (0, 1):
 
1692
                raise errors.BzrError("Unhandled diff3 exit code")
 
1693
            with open(new_file, 'rb') as f:
 
1694
                self.tt.create_file(f, trans_id)
 
1695
            if status == 1:
 
1696
                name = self.tt.final_name(trans_id)
 
1697
                parent_id = self.tt.final_parent(trans_id)
 
1698
                file_id = self.tt.final_file_id(trans_id)
 
1699
                self._dump_conflicts(name, paths, parent_id, file_id)
 
1700
                self._raw_conflicts.append(('text conflict', trans_id))
 
1701
        finally:
 
1702
            osutils.rmtree(temp_dir)
 
1703
 
 
1704
 
 
1705
class PathNotInTree(errors.BzrError):
 
1706
 
 
1707
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1708
 
 
1709
    def __init__(self, path, tree):
 
1710
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1711
 
 
1712
 
 
1713
class MergeIntoMerger(Merger):
 
1714
    """Merger that understands other_tree will be merged into a subdir.
 
1715
 
 
1716
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1717
    and RevisonTree objects, rather than using revision specs.
 
1718
    """
 
1719
 
 
1720
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1721
                 source_subpath, other_rev_id=None):
 
1722
        """Create a new MergeIntoMerger object.
 
1723
 
 
1724
        source_subpath in other_tree will be effectively copied to
 
1725
        target_subdir in this_tree.
 
1726
 
 
1727
        :param this_tree: The tree that we will be merging into.
 
1728
        :param other_branch: The Branch we will be merging from.
 
1729
        :param other_tree: The RevisionTree object we want to merge.
 
1730
        :param target_subdir: The relative path where we want to merge
 
1731
            other_tree into this_tree
 
1732
        :param source_subpath: The relative path specifying the subtree of
 
1733
            other_tree to merge into this_tree.
 
1734
        """
 
1735
        # It is assumed that we are merging a tree that is not in our current
 
1736
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1737
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1738
            _mod_revision.NULL_REVISION)
 
1739
        super(MergeIntoMerger, self).__init__(
 
1740
            this_branch=this_tree.branch,
 
1741
            this_tree=this_tree,
 
1742
            other_tree=other_tree,
 
1743
            base_tree=null_ancestor_tree,
 
1744
            )
 
1745
        self._target_subdir = target_subdir
 
1746
        self._source_subpath = source_subpath
 
1747
        self.other_branch = other_branch
 
1748
        if other_rev_id is None:
 
1749
            other_rev_id = other_tree.get_revision_id()
 
1750
        self.other_rev_id = self.other_basis = other_rev_id
 
1751
        self.base_is_ancestor = True
 
1752
        self.backup_files = True
 
1753
        self.merge_type = Merge3Merger
 
1754
        self.show_base = False
 
1755
        self.reprocess = False
 
1756
        self.interesting_files = None
 
1757
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1758
                                                  target_subdir=self._target_subdir,
 
1759
                                                  source_subpath=self._source_subpath)
 
1760
        if self._source_subpath != '':
 
1761
            # If this isn't a partial merge make sure the revisions will be
 
1762
            # present.
 
1763
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1764
                              self.other_basis)
 
1765
 
 
1766
    def set_pending(self):
 
1767
        if self._source_subpath != '':
 
1768
            return
 
1769
        Merger.set_pending(self)
 
1770
 
 
1771
 
 
1772
class _MergeTypeParameterizer(object):
 
1773
    """Wrap a merge-type class to provide extra parameters.
 
1774
 
 
1775
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1776
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1777
    the 'merge_type' member.  It is difficult override do_merge without
 
1778
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1779
    the extra parameters.
 
1780
    """
 
1781
 
 
1782
    def __init__(self, merge_type, **kwargs):
 
1783
        self._extra_kwargs = kwargs
 
1784
        self._merge_type = merge_type
 
1785
 
 
1786
    def __call__(self, *args, **kwargs):
 
1787
        kwargs.update(self._extra_kwargs)
 
1788
        return self._merge_type(*args, **kwargs)
 
1789
 
 
1790
    def __getattr__(self, name):
 
1791
        return getattr(self._merge_type, name)
 
1792
 
 
1793
 
 
1794
class MergeIntoMergeType(Merge3Merger):
 
1795
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1796
 
 
1797
    def __init__(self, *args, **kwargs):
 
1798
        """Initialize the merger object.
 
1799
 
 
1800
        :param args: See Merge3Merger.__init__'s args.
 
1801
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1802
            source_subpath and target_subdir.
 
1803
        :keyword source_subpath: The relative path specifying the subtree of
 
1804
            other_tree to merge into this_tree.
 
1805
        :keyword target_subdir: The relative path where we want to merge
 
1806
            other_tree into this_tree
 
1807
        """
 
1808
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1809
        # so we have have to hack in to get our extra parameters set.
 
1810
        self._source_subpath = kwargs.pop('source_subpath')
 
1811
        self._target_subdir = kwargs.pop('target_subdir')
 
1812
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1813
 
 
1814
    def _compute_transform(self):
 
1815
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1816
            entries = self._entries_to_incorporate()
 
1817
            entries = list(entries)
 
1818
            for num, (entry, parent_id, relpath) in enumerate(entries):
 
1819
                child_pb.update(gettext('Preparing file merge'),
 
1820
                                num, len(entries))
 
1821
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1822
                path = osutils.pathjoin(self._source_subpath, relpath)
 
1823
                trans_id = transform.new_by_entry(path, self.tt, entry,
 
1824
                                                  parent_trans_id, self.other_tree)
 
1825
        self._finish_computing_transform()
 
1826
 
 
1827
    def _entries_to_incorporate(self):
 
1828
        """Yields pairs of (inventory_entry, new_parent)."""
 
1829
        subdir_id = self.other_tree.path2id(self._source_subpath)
 
1830
        if subdir_id is None:
 
1831
            # XXX: The error would be clearer if it gave the URL of the source
 
1832
            # branch, but we don't have a reference to that here.
 
1833
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1834
        subdir = next(self.other_tree.iter_entries_by_dir(
 
1835
            specific_files=[self._source_subpath]))[1]
 
1836
        parent_in_target = osutils.dirname(self._target_subdir)
 
1837
        target_id = self.this_tree.path2id(parent_in_target)
 
1838
        if target_id is None:
 
1839
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1840
        name_in_target = osutils.basename(self._target_subdir)
 
1841
        merge_into_root = subdir.copy()
 
1842
        merge_into_root.name = name_in_target
 
1843
        try:
 
1844
            self.this_tree.id2path(merge_into_root.file_id)
 
1845
        except errors.NoSuchId:
 
1846
            pass
 
1847
        else:
 
1848
            # Give the root a new file-id.
 
1849
            # This can happen fairly easily if the directory we are
 
1850
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1851
            # their root_id.  Users will expect this to Just Work, so we
 
1852
            # change the file-id here.
 
1853
            # Non-root file-ids could potentially conflict too.  That's really
 
1854
            # an edge case, so we don't do anything special for those.  We let
 
1855
            # them cause conflicts.
 
1856
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1857
        yield (merge_into_root, target_id, '')
 
1858
        if subdir.kind != 'directory':
 
1859
            # No children, so we are done.
 
1860
            return
 
1861
        for path, entry in self.other_tree.root_inventory.iter_entries_by_dir(subdir_id):
 
1862
            parent_id = entry.parent_id
 
1863
            if parent_id == subdir.file_id:
 
1864
                # The root's parent ID has changed, so make sure children of
 
1865
                # the root refer to the new ID.
 
1866
                parent_id = merge_into_root.file_id
 
1867
            yield (entry, parent_id, path)
 
1868
 
 
1869
 
 
1870
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
 
1871
                backup_files=False,
 
1872
                merge_type=Merge3Merger,
 
1873
                show_base=False,
 
1874
                reprocess=False,
 
1875
                other_rev_id=None,
 
1876
                interesting_files=None,
 
1877
                this_tree=None,
 
1878
                change_reporter=None):
 
1879
    """Primary interface for merging.
 
1880
 
 
1881
    Typical use is probably::
 
1882
 
 
1883
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1884
                    branch.get_revision_tree(base_revision))
 
1885
    """
 
1886
    if this_tree is None:
 
1887
        raise errors.BzrError("breezy.merge.merge_inner requires a this_tree "
 
1888
                              "parameter")
 
1889
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1890
                    change_reporter=change_reporter)
 
1891
    merger.backup_files = backup_files
 
1892
    merger.merge_type = merge_type
 
1893
    merger.ignore_zero = ignore_zero
 
1894
    merger.interesting_files = interesting_files
 
1895
    merger.show_base = show_base
 
1896
    merger.reprocess = reprocess
 
1897
    merger.other_rev_id = other_rev_id
 
1898
    merger.other_basis = other_rev_id
 
1899
    get_revision_id = getattr(base_tree, 'get_revision_id', None)
 
1900
    if get_revision_id is None:
 
1901
        get_revision_id = base_tree.last_revision
 
1902
    merger.cache_trees_with_revision_ids([other_tree, base_tree, this_tree])
 
1903
    merger.set_base_revision(get_revision_id(), this_branch)
 
1904
    return merger.do_merge()
 
1905
 
 
1906
 
 
1907
merge_type_registry = registry.Registry()
 
1908
merge_type_registry.register('diff3', Diff3Merger,
 
1909
                             "Merge using external diff3.")
 
1910
merge_type_registry.register('lca', LCAMerger,
 
1911
                             "LCA-newness merge.")
 
1912
merge_type_registry.register('merge3', Merge3Merger,
 
1913
                             "Native diff3-style merge.")
 
1914
merge_type_registry.register('weave', WeaveMerger,
 
1915
                             "Weave-based merge.")
 
1916
 
 
1917
 
 
1918
def get_merge_type_registry():
 
1919
    """Merge type registry was previously in breezy.option
 
1920
 
 
1921
    This method provides a backwards compatible way to retrieve it.
 
1922
    """
 
1923
    return merge_type_registry
 
1924
 
 
1925
 
 
1926
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
 
1927
    def status_a(revision, text):
 
1928
        if revision in ancestors_b:
 
1929
            return 'killed-b', text
 
1930
        else:
 
1931
            return 'new-a', text
 
1932
 
 
1933
    def status_b(revision, text):
 
1934
        if revision in ancestors_a:
 
1935
            return 'killed-a', text
 
1936
        else:
 
1937
            return 'new-b', text
 
1938
 
 
1939
    plain_a = [t for (a, t) in annotated_a]
 
1940
    plain_b = [t for (a, t) in annotated_b]
 
1941
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
 
1942
    blocks = matcher.get_matching_blocks()
 
1943
    a_cur = 0
 
1944
    b_cur = 0
 
1945
    for ai, bi, l in blocks:
 
1946
        # process all mismatched sections
 
1947
        # (last mismatched section is handled because blocks always
 
1948
        # includes a 0-length last block)
 
1949
        for revision, text in annotated_a[a_cur:ai]:
 
1950
            yield status_a(revision, text)
 
1951
        for revision, text in annotated_b[b_cur:bi]:
 
1952
            yield status_b(revision, text)
 
1953
        # and now the matched section
 
1954
        a_cur = ai + l
 
1955
        b_cur = bi + l
 
1956
        for text_a in plain_a[ai:a_cur]:
 
1957
            yield "unchanged", text_a
 
1958
 
 
1959
 
 
1960
class _PlanMergeBase(object):
 
1961
 
 
1962
    def __init__(self, a_rev, b_rev, vf, key_prefix):
 
1963
        """Contructor.
 
1964
 
 
1965
        :param a_rev: Revision-id of one revision to merge
 
1966
        :param b_rev: Revision-id of the other revision to merge
 
1967
        :param vf: A VersionedFiles containing both revisions
 
1968
        :param key_prefix: A prefix for accessing keys in vf, typically
 
1969
            (file_id,).
 
1970
        """
 
1971
        self.a_rev = a_rev
 
1972
        self.b_rev = b_rev
 
1973
        self.vf = vf
 
1974
        self._last_lines = None
 
1975
        self._last_lines_revision_id = None
 
1976
        self._cached_matching_blocks = {}
 
1977
        self._key_prefix = key_prefix
 
1978
        self._precache_tip_lines()
 
1979
 
 
1980
    def _precache_tip_lines(self):
 
1981
        lines = self.get_lines([self.a_rev, self.b_rev])
 
1982
        self.lines_a = lines[self.a_rev]
 
1983
        self.lines_b = lines[self.b_rev]
 
1984
 
 
1985
    def get_lines(self, revisions):
 
1986
        """Get lines for revisions from the backing VersionedFiles.
 
1987
 
 
1988
        :raises RevisionNotPresent: on absent texts.
 
1989
        """
 
1990
        keys = [(self._key_prefix + (rev,)) for rev in revisions]
 
1991
        result = {}
 
1992
        for record in self.vf.get_record_stream(keys, 'unordered', True):
 
1993
            if record.storage_kind == 'absent':
 
1994
                raise errors.RevisionNotPresent(record.key, self.vf)
 
1995
            result[record.key[-1]] = record.get_bytes_as('lines')
 
1996
        return result
 
1997
 
 
1998
    def plan_merge(self):
 
1999
        """Generate a 'plan' for merging the two revisions.
 
2000
 
 
2001
        This involves comparing their texts and determining the cause of
 
2002
        differences.  If text A has a line and text B does not, then either the
 
2003
        line was added to text A, or it was deleted from B.  Once the causes
 
2004
        are combined, they are written out in the format described in
 
2005
        VersionedFile.plan_merge
 
2006
        """
 
2007
        blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
 
2008
        unique_a, unique_b = self._unique_lines(blocks)
 
2009
        new_a, killed_b = self._determine_status(self.a_rev, unique_a)
 
2010
        new_b, killed_a = self._determine_status(self.b_rev, unique_b)
 
2011
        return self._iter_plan(blocks, new_a, killed_b, new_b, killed_a)
 
2012
 
 
2013
    def _iter_plan(self, blocks, new_a, killed_b, new_b, killed_a):
 
2014
        last_i = 0
 
2015
        last_j = 0
 
2016
        for i, j, n in blocks:
 
2017
            for a_index in range(last_i, i):
 
2018
                if a_index in new_a:
 
2019
                    if a_index in killed_b:
 
2020
                        yield 'conflicted-a', self.lines_a[a_index]
 
2021
                    else:
 
2022
                        yield 'new-a', self.lines_a[a_index]
 
2023
                else:
 
2024
                    yield 'killed-b', self.lines_a[a_index]
 
2025
            for b_index in range(last_j, j):
 
2026
                if b_index in new_b:
 
2027
                    if b_index in killed_a:
 
2028
                        yield 'conflicted-b', self.lines_b[b_index]
 
2029
                    else:
 
2030
                        yield 'new-b', self.lines_b[b_index]
 
2031
                else:
 
2032
                    yield 'killed-a', self.lines_b[b_index]
 
2033
            # handle common lines
 
2034
            for a_index in range(i, i + n):
 
2035
                yield 'unchanged', self.lines_a[a_index]
 
2036
            last_i = i + n
 
2037
            last_j = j + n
 
2038
 
 
2039
    def _get_matching_blocks(self, left_revision, right_revision):
 
2040
        """Return a description of which sections of two revisions match.
 
2041
 
 
2042
        See SequenceMatcher.get_matching_blocks
 
2043
        """
 
2044
        cached = self._cached_matching_blocks.get((left_revision,
 
2045
                                                   right_revision))
 
2046
        if cached is not None:
 
2047
            return cached
 
2048
        if self._last_lines_revision_id == left_revision:
 
2049
            left_lines = self._last_lines
 
2050
            right_lines = self.get_lines([right_revision])[right_revision]
 
2051
        else:
 
2052
            lines = self.get_lines([left_revision, right_revision])
 
2053
            left_lines = lines[left_revision]
 
2054
            right_lines = lines[right_revision]
 
2055
        self._last_lines = right_lines
 
2056
        self._last_lines_revision_id = right_revision
 
2057
        matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
 
2058
                                                       right_lines)
 
2059
        return matcher.get_matching_blocks()
 
2060
 
 
2061
    def _unique_lines(self, matching_blocks):
 
2062
        """Analyse matching_blocks to determine which lines are unique
 
2063
 
 
2064
        :return: a tuple of (unique_left, unique_right), where the values are
 
2065
            sets of line numbers of unique lines.
 
2066
        """
 
2067
        last_i = 0
 
2068
        last_j = 0
 
2069
        unique_left = []
 
2070
        unique_right = []
 
2071
        for i, j, n in matching_blocks:
 
2072
            unique_left.extend(range(last_i, i))
 
2073
            unique_right.extend(range(last_j, j))
 
2074
            last_i = i + n
 
2075
            last_j = j + n
 
2076
        return unique_left, unique_right
 
2077
 
 
2078
    @staticmethod
 
2079
    def _subtract_plans(old_plan, new_plan):
 
2080
        """Remove changes from new_plan that came from old_plan.
 
2081
 
 
2082
        It is assumed that the difference between the old_plan and new_plan
 
2083
        is their choice of 'b' text.
 
2084
 
 
2085
        All lines from new_plan that differ from old_plan are emitted
 
2086
        verbatim.  All lines from new_plan that match old_plan but are
 
2087
        not about the 'b' revision are emitted verbatim.
 
2088
 
 
2089
        Lines that match and are about the 'b' revision are the lines we
 
2090
        don't want, so we convert 'killed-b' -> 'unchanged', and 'new-b'
 
2091
        is skipped entirely.
 
2092
        """
 
2093
        matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
 
2094
                                                       new_plan)
 
2095
        last_j = 0
 
2096
        for i, j, n in matcher.get_matching_blocks():
 
2097
            for jj in range(last_j, j):
 
2098
                yield new_plan[jj]
 
2099
            for jj in range(j, j + n):
 
2100
                plan_line = new_plan[jj]
 
2101
                if plan_line[0] == 'new-b':
 
2102
                    pass
 
2103
                elif plan_line[0] == 'killed-b':
 
2104
                    yield 'unchanged', plan_line[1]
 
2105
                else:
 
2106
                    yield plan_line
 
2107
            last_j = j + n
 
2108
 
 
2109
 
 
2110
class _PlanMerge(_PlanMergeBase):
 
2111
    """Plan an annotate merge using on-the-fly annotation"""
 
2112
 
 
2113
    def __init__(self, a_rev, b_rev, vf, key_prefix):
 
2114
        super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
 
2115
        self.a_key = self._key_prefix + (self.a_rev,)
 
2116
        self.b_key = self._key_prefix + (self.b_rev,)
 
2117
        self.graph = _mod_graph.Graph(self.vf)
 
2118
        heads = self.graph.heads((self.a_key, self.b_key))
 
2119
        if len(heads) == 1:
 
2120
            # one side dominates, so we can just return its values, yay for
 
2121
            # per-file graphs
 
2122
            # Ideally we would know that before we get this far
 
2123
            self._head_key = heads.pop()
 
2124
            if self._head_key == self.a_key:
 
2125
                other = b_rev
 
2126
            else:
 
2127
                other = a_rev
 
2128
            trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
 
2129
                         self._head_key[-1], other)
 
2130
            self._weave = None
 
2131
        else:
 
2132
            self._head_key = None
 
2133
            self._build_weave()
 
2134
 
 
2135
    def _precache_tip_lines(self):
 
2136
        # Turn this into a no-op, because we will do this later
 
2137
        pass
 
2138
 
 
2139
    def _find_recursive_lcas(self):
 
2140
        """Find all the ancestors back to a unique lca"""
 
2141
        cur_ancestors = (self.a_key, self.b_key)
 
2142
        # graph.find_lca(uncommon, keys) now returns plain NULL_REVISION,
 
2143
        # rather than a key tuple. We will just map that directly to no common
 
2144
        # ancestors.
 
2145
        parent_map = {}
 
2146
        while True:
 
2147
            next_lcas = self.graph.find_lca(*cur_ancestors)
 
2148
            # Map a plain NULL_REVISION to a simple no-ancestors
 
2149
            if next_lcas == {_mod_revision.NULL_REVISION}:
 
2150
                next_lcas = ()
 
2151
            # Order the lca's based on when they were merged into the tip
 
2152
            # While the actual merge portion of weave merge uses a set() of
 
2153
            # active revisions, the order of insertion *does* effect the
 
2154
            # implicit ordering of the texts.
 
2155
            for rev_key in cur_ancestors:
 
2156
                ordered_parents = tuple(self.graph.find_merge_order(rev_key,
 
2157
                                                                    next_lcas))
 
2158
                parent_map[rev_key] = ordered_parents
 
2159
            if len(next_lcas) == 0:
 
2160
                break
 
2161
            elif len(next_lcas) == 1:
 
2162
                parent_map[list(next_lcas)[0]] = ()
 
2163
                break
 
2164
            elif len(next_lcas) > 2:
 
2165
                # More than 2 lca's, fall back to grabbing all nodes between
 
2166
                # this and the unique lca.
 
2167
                trace.mutter('More than 2 LCAs, falling back to all nodes for:'
 
2168
                             ' %s, %s\n=> %s',
 
2169
                             self.a_key, self.b_key, cur_ancestors)
 
2170
                cur_lcas = next_lcas
 
2171
                while len(cur_lcas) > 1:
 
2172
                    cur_lcas = self.graph.find_lca(*cur_lcas)
 
2173
                if len(cur_lcas) == 0:
 
2174
                    # No common base to find, use the full ancestry
 
2175
                    unique_lca = None
 
2176
                else:
 
2177
                    unique_lca = list(cur_lcas)[0]
 
2178
                    if unique_lca == _mod_revision.NULL_REVISION:
 
2179
                        # find_lca will return a plain 'NULL_REVISION' rather
 
2180
                        # than a key tuple when there is no common ancestor, we
 
2181
                        # prefer to just use None, because it doesn't confuse
 
2182
                        # _get_interesting_texts()
 
2183
                        unique_lca = None
 
2184
                parent_map.update(self._find_unique_parents(next_lcas,
 
2185
                                                            unique_lca))
 
2186
                break
 
2187
            cur_ancestors = next_lcas
 
2188
        return parent_map
 
2189
 
 
2190
    def _find_unique_parents(self, tip_keys, base_key):
 
2191
        """Find ancestors of tip that aren't ancestors of base.
 
2192
 
 
2193
        :param tip_keys: Nodes that are interesting
 
2194
        :param base_key: Cull all ancestors of this node
 
2195
        :return: The parent map for all revisions between tip_keys and
 
2196
            base_key. base_key will be included. References to nodes outside of
 
2197
            the ancestor set will also be removed.
 
2198
        """
 
2199
        # TODO: this would be simpler if find_unique_ancestors took a list
 
2200
        #       instead of a single tip, internally it supports it, but it
 
2201
        #       isn't a "backwards compatible" api change.
 
2202
        if base_key is None:
 
2203
            parent_map = dict(self.graph.iter_ancestry(tip_keys))
 
2204
            # We remove NULL_REVISION because it isn't a proper tuple key, and
 
2205
            # thus confuses things like _get_interesting_texts, and our logic
 
2206
            # to add the texts into the memory weave.
 
2207
            if _mod_revision.NULL_REVISION in parent_map:
 
2208
                parent_map.pop(_mod_revision.NULL_REVISION)
 
2209
        else:
 
2210
            interesting = set()
 
2211
            for tip in tip_keys:
 
2212
                interesting.update(
 
2213
                    self.graph.find_unique_ancestors(tip, [base_key]))
 
2214
            parent_map = self.graph.get_parent_map(interesting)
 
2215
            parent_map[base_key] = ()
 
2216
        culled_parent_map, child_map, tails = self._remove_external_references(
 
2217
            parent_map)
 
2218
        # Remove all the tails but base_key
 
2219
        if base_key is not None:
 
2220
            tails.remove(base_key)
 
2221
            self._prune_tails(culled_parent_map, child_map, tails)
 
2222
        # Now remove all the uninteresting 'linear' regions
 
2223
        simple_map = _mod_graph.collapse_linear_regions(culled_parent_map)
 
2224
        return simple_map
 
2225
 
 
2226
    @staticmethod
 
2227
    def _remove_external_references(parent_map):
 
2228
        """Remove references that go outside of the parent map.
 
2229
 
 
2230
        :param parent_map: Something returned from Graph.get_parent_map(keys)
 
2231
        :return: (filtered_parent_map, child_map, tails)
 
2232
            filtered_parent_map is parent_map without external references
 
2233
            child_map is the {parent_key: [child_keys]} mapping
 
2234
            tails is a list of nodes that do not have any parents in the map
 
2235
        """
 
2236
        # TODO: The basic effect of this function seems more generic than
 
2237
        #       _PlanMerge. But the specific details of building a child_map,
 
2238
        #       and computing tails seems very specific to _PlanMerge.
 
2239
        #       Still, should this be in Graph land?
 
2240
        filtered_parent_map = {}
 
2241
        child_map = {}
 
2242
        tails = []
 
2243
        for key, parent_keys in parent_map.items():
 
2244
            culled_parent_keys = [p for p in parent_keys if p in parent_map]
 
2245
            if not culled_parent_keys:
 
2246
                tails.append(key)
 
2247
            for parent_key in culled_parent_keys:
 
2248
                child_map.setdefault(parent_key, []).append(key)
 
2249
            # TODO: Do we want to do this, it adds overhead for every node,
 
2250
            #       just to say that the node has no children
 
2251
            child_map.setdefault(key, [])
 
2252
            filtered_parent_map[key] = culled_parent_keys
 
2253
        return filtered_parent_map, child_map, tails
 
2254
 
 
2255
    @staticmethod
 
2256
    def _prune_tails(parent_map, child_map, tails_to_remove):
 
2257
        """Remove tails from the parent map.
 
2258
 
 
2259
        This will remove the supplied revisions until no more children have 0
 
2260
        parents.
 
2261
 
 
2262
        :param parent_map: A dict of {child: [parents]}, this dictionary will
 
2263
            be modified in place.
 
2264
        :param tails_to_remove: A list of tips that should be removed,
 
2265
            this list will be consumed
 
2266
        :param child_map: The reverse dict of parent_map ({parent: [children]})
 
2267
            this dict will be modified
 
2268
        :return: None, parent_map will be modified in place.
 
2269
        """
 
2270
        while tails_to_remove:
 
2271
            next = tails_to_remove.pop()
 
2272
            parent_map.pop(next)
 
2273
            children = child_map.pop(next)
 
2274
            for child in children:
 
2275
                child_parents = parent_map[child]
 
2276
                child_parents.remove(next)
 
2277
                if len(child_parents) == 0:
 
2278
                    tails_to_remove.append(child)
 
2279
 
 
2280
    def _get_interesting_texts(self, parent_map):
 
2281
        """Return a dict of texts we are interested in.
 
2282
 
 
2283
        Note that the input is in key tuples, but the output is in plain
 
2284
        revision ids.
 
2285
 
 
2286
        :param parent_map: The output from _find_recursive_lcas
 
2287
        :return: A dict of {'revision_id':lines} as returned by
 
2288
            _PlanMergeBase.get_lines()
 
2289
        """
 
2290
        all_revision_keys = set(parent_map)
 
2291
        all_revision_keys.add(self.a_key)
 
2292
        all_revision_keys.add(self.b_key)
 
2293
 
 
2294
        # Everything else is in 'keys' but get_lines is in 'revision_ids'
 
2295
        all_texts = self.get_lines([k[-1] for k in all_revision_keys])
 
2296
        return all_texts
 
2297
 
 
2298
    def _build_weave(self):
 
2299
        from .bzr import weave
 
2300
        self._weave = weave.Weave(weave_name='in_memory_weave',
 
2301
                                  allow_reserved=True)
 
2302
        parent_map = self._find_recursive_lcas()
 
2303
 
 
2304
        all_texts = self._get_interesting_texts(parent_map)
 
2305
 
 
2306
        # Note: Unfortunately, the order given by topo_sort will effect the
 
2307
        # ordering resolution in the output. Specifically, if you add A then B,
 
2308
        # then in the output text A lines will show up before B lines. And, of
 
2309
        # course, topo_sort doesn't guarantee any real ordering.
 
2310
        # So we use merge_sort, and add a fake node on the tip.
 
2311
        # This ensures that left-hand parents will always be inserted into the
 
2312
        # weave before right-hand parents.
 
2313
        tip_key = self._key_prefix + (_mod_revision.CURRENT_REVISION,)
 
2314
        parent_map[tip_key] = (self.a_key, self.b_key)
 
2315
 
 
2316
        for seq_num, key, depth, eom in reversed(tsort.merge_sort(parent_map,
 
2317
                                                                  tip_key)):
 
2318
            if key == tip_key:
 
2319
                continue
 
2320
        # for key in tsort.topo_sort(parent_map):
 
2321
            parent_keys = parent_map[key]
 
2322
            revision_id = key[-1]
 
2323
            parent_ids = [k[-1] for k in parent_keys]
 
2324
            self._weave.add_lines(revision_id, parent_ids,
 
2325
                                  all_texts[revision_id])
 
2326
 
 
2327
    def plan_merge(self):
 
2328
        """Generate a 'plan' for merging the two revisions.
 
2329
 
 
2330
        This involves comparing their texts and determining the cause of
 
2331
        differences.  If text A has a line and text B does not, then either the
 
2332
        line was added to text A, or it was deleted from B.  Once the causes
 
2333
        are combined, they are written out in the format described in
 
2334
        VersionedFile.plan_merge
 
2335
        """
 
2336
        if self._head_key is not None:  # There was a single head
 
2337
            if self._head_key == self.a_key:
 
2338
                plan = 'new-a'
 
2339
            else:
 
2340
                if self._head_key != self.b_key:
 
2341
                    raise AssertionError('There was an invalid head: %s != %s'
 
2342
                                         % (self.b_key, self._head_key))
 
2343
                plan = 'new-b'
 
2344
            head_rev = self._head_key[-1]
 
2345
            lines = self.get_lines([head_rev])[head_rev]
 
2346
            return ((plan, line) for line in lines)
 
2347
        return self._weave.plan_merge(self.a_rev, self.b_rev)
 
2348
 
 
2349
 
 
2350
class _PlanLCAMerge(_PlanMergeBase):
 
2351
    """
 
2352
    This merge algorithm differs from _PlanMerge in that:
 
2353
 
 
2354
    1. comparisons are done against LCAs only
 
2355
    2. cases where a contested line is new versus one LCA but old versus
 
2356
       another are marked as conflicts, by emitting the line as conflicted-a
 
2357
       or conflicted-b.
 
2358
 
 
2359
    This is faster, and hopefully produces more useful output.
 
2360
    """
 
2361
 
 
2362
    def __init__(self, a_rev, b_rev, vf, key_prefix, graph):
 
2363
        _PlanMergeBase.__init__(self, a_rev, b_rev, vf, key_prefix)
 
2364
        lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
 
2365
        self.lcas = set()
 
2366
        for lca in lcas:
 
2367
            if lca == _mod_revision.NULL_REVISION:
 
2368
                self.lcas.add(lca)
 
2369
            else:
 
2370
                self.lcas.add(lca[-1])
 
2371
        for lca in self.lcas:
 
2372
            if _mod_revision.is_null(lca):
 
2373
                lca_lines = []
 
2374
            else:
 
2375
                lca_lines = self.get_lines([lca])[lca]
 
2376
            matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_a,
 
2377
                                                           lca_lines)
 
2378
            blocks = list(matcher.get_matching_blocks())
 
2379
            self._cached_matching_blocks[(a_rev, lca)] = blocks
 
2380
            matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_b,
 
2381
                                                           lca_lines)
 
2382
            blocks = list(matcher.get_matching_blocks())
 
2383
            self._cached_matching_blocks[(b_rev, lca)] = blocks
 
2384
 
 
2385
    def _determine_status(self, revision_id, unique_line_numbers):
 
2386
        """Determines the status unique lines versus all lcas.
 
2387
 
 
2388
        Basically, determines why the line is unique to this revision.
 
2389
 
 
2390
        A line may be determined new, killed, or both.
 
2391
 
 
2392
        If a line is determined new, that means it was not present in at least
 
2393
        one LCA, and is not present in the other merge revision.
 
2394
 
 
2395
        If a line is determined killed, that means the line was present in
 
2396
        at least one LCA.
 
2397
 
 
2398
        If a line is killed and new, this indicates that the two merge
 
2399
        revisions contain differing conflict resolutions.
 
2400
 
 
2401
        :param revision_id: The id of the revision in which the lines are
 
2402
            unique
 
2403
        :param unique_line_numbers: The line numbers of unique lines.
 
2404
        :return: a tuple of (new_this, killed_other)
 
2405
        """
 
2406
        new = set()
 
2407
        killed = set()
 
2408
        unique_line_numbers = set(unique_line_numbers)
 
2409
        for lca in self.lcas:
 
2410
            blocks = self._get_matching_blocks(revision_id, lca)
 
2411
            unique_vs_lca, _ignored = self._unique_lines(blocks)
 
2412
            new.update(unique_line_numbers.intersection(unique_vs_lca))
 
2413
            killed.update(unique_line_numbers.difference(unique_vs_lca))
 
2414
        return new, killed