/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-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
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
    transform,
 
34
    tree as _mod_tree,
 
35
    tsort,
 
36
    ui,
 
37
    workingtree,
 
38
    )
 
39
from breezy.bzr import (
 
40
    generate_ids,
 
41
    versionedfile,
 
42
    )
 
43
from breezy.i18n import gettext
 
44
""")
 
45
from . import (
 
46
    decorators,
 
47
    errors,
 
48
    hooks,
 
49
    registry,
 
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.get_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 = transform.TransformPreview(self.working_tree)
 
774
            self._compute_transform()
 
775
            return self.tt
 
776
 
 
777
    def _compute_transform(self):
 
778
        if self._lca_trees is None:
 
779
            entries = self._entries3()
 
780
            resolver = self._three_way
 
781
        else:
 
782
            entries = 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
 
 
794
                # Try merging each entry
 
795
                child_pb.update(gettext('Preparing file merge'),
 
796
                                num, len(entries))
 
797
                self._merge_names(trans_id, file_id, paths3, parents3,
 
798
                                  names3, resolver=resolver)
 
799
                if changed:
 
800
                    file_status = self._do_merge_contents(paths3, trans_id, file_id)
 
801
                else:
 
802
                    file_status = 'unmodified'
 
803
                self._merge_executable(paths3, trans_id, executable3,
 
804
                                       file_status, resolver=resolver)
 
805
        self.tt.fixup_new_roots()
 
806
        self._finish_computing_transform()
 
807
 
 
808
    def _finish_computing_transform(self):
 
809
        """Finalize the transform and report the changes.
 
810
 
 
811
        This is the second half of _compute_transform.
 
812
        """
 
813
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
814
            fs_conflicts = transform.resolve_conflicts(
 
815
                self.tt, child_pb,
 
816
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
 
817
        if self.change_reporter is not None:
 
818
            from breezy import delta
 
819
            delta.report_changes(
 
820
                self.tt.iter_changes(), self.change_reporter)
 
821
        self.cook_conflicts(fs_conflicts)
 
822
        for conflict in self.cooked_conflicts:
 
823
            trace.warning('%s', conflict.describe())
 
824
 
 
825
    def _entries3(self):
 
826
        """Gather data about files modified between three trees.
 
827
 
 
828
        Return a list of tuples of file_id, changed, parents3, names3,
 
829
        executable3.  changed is a boolean indicating whether the file contents
 
830
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
831
        other and this.  names3 is a tuple of names for base, other and this.
 
832
        executable3 is a tuple of execute-bit values for base, other and this.
 
833
        """
 
834
        result = []
 
835
        iterator = self.other_tree.iter_changes(self.base_tree,
 
836
                                                specific_files=self.interesting_files,
 
837
                                                extra_trees=[self.this_tree])
 
838
        this_interesting_files = self.this_tree.find_related_paths_across_trees(
 
839
            self.interesting_files, trees=[self.other_tree])
 
840
        this_entries = dict(self.this_tree.iter_entries_by_dir(
 
841
                            specific_files=this_interesting_files))
 
842
        for change in iterator:
 
843
            if change.path[0] is not None:
 
844
                this_path = _mod_tree.find_previous_path(
 
845
                    self.base_tree, self.this_tree, change.path[0])
 
846
            else:
 
847
                this_path = _mod_tree.find_previous_path(
 
848
                    self.other_tree, self.this_tree, change.path[1])
 
849
            this_entry = this_entries.get(this_path)
 
850
            if this_entry is not None:
 
851
                this_name = this_entry.name
 
852
                this_parent = this_entry.parent_id
 
853
                this_executable = this_entry.executable
 
854
            else:
 
855
                this_name = None
 
856
                this_parent = None
 
857
                this_executable = None
 
858
            parents3 = change.parent_id + (this_parent,)
 
859
            names3 = change.name + (this_name,)
 
860
            paths3 = change.path + (this_path, )
 
861
            executable3 = change.executable + (this_executable,)
 
862
            result.append(
 
863
                (change.file_id, change.changed_content, paths3,
 
864
                 parents3, names3, executable3))
 
865
        return result
 
866
 
 
867
    def _entries_lca(self):
 
868
        """Gather data about files modified between multiple trees.
 
869
 
 
870
        This compares OTHER versus all LCA trees, and for interesting entries,
 
871
        it then compares with THIS and BASE.
 
872
 
 
873
        For the multi-valued entries, the format will be (BASE, [lca1, lca2])
 
874
 
 
875
        :return: [(file_id, changed, paths, parents, names, executable)], where:
 
876
 
 
877
            * file_id: Simple file_id of the entry
 
878
            * changed: Boolean, True if the kind or contents changed else False
 
879
            * paths: ((base, [path, in, lcas]), path_other, path_this)
 
880
            * parents: ((base, [parent_id, in, lcas]), parent_id_other,
 
881
                        parent_id_this)
 
882
            * names:   ((base, [name, in, lcas]), name_in_other, name_in_this)
 
883
            * executable: ((base, [exec, in, lcas]), exec_in_other,
 
884
                        exec_in_this)
 
885
        """
 
886
        if self.interesting_files is not None:
 
887
            lookup_trees = [self.this_tree, self.base_tree]
 
888
            lookup_trees.extend(self._lca_trees)
 
889
            # I think we should include the lca trees as well
 
890
            interesting_files = self.other_tree.find_related_paths_across_trees(
 
891
                self.interesting_files, lookup_trees)
 
892
        else:
 
893
            interesting_files = None
 
894
        result = []
 
895
        from .multiwalker import MultiWalker
 
896
        walker = MultiWalker(self.other_tree, self._lca_trees)
 
897
 
 
898
        for other_path, file_id, other_ie, lca_values in walker.iter_all():
 
899
            # Is this modified at all from any of the other trees?
 
900
            if other_ie is None:
 
901
                other_ie = _none_entry
 
902
                other_path = None
 
903
            if interesting_files is not None and other_path not in interesting_files:
 
904
                continue
 
905
 
 
906
            # If other_revision is found in any of the lcas, that means this
 
907
            # node is uninteresting. This is because when merging, if there are
 
908
            # multiple heads(), we have to create a new node. So if we didn't,
 
909
            # we know that the ancestry is linear, and that OTHER did not
 
910
            # modify anything
 
911
            # See doc/developers/lca_merge_resolution.txt for details
 
912
            # We can't use this shortcut when other_revision is None,
 
913
            # because it may be None because things are WorkingTrees, and
 
914
            # not because it is *actually* None.
 
915
            is_unmodified = False
 
916
            for lca_path, ie in lca_values:
 
917
                if ie is not None and other_ie.is_unmodified(ie):
 
918
                    is_unmodified = True
 
919
                    break
 
920
            if is_unmodified:
 
921
                continue
 
922
 
 
923
            lca_entries = []
 
924
            lca_paths = []
 
925
            for lca_path, lca_ie in lca_values:
 
926
                if lca_ie is None:
 
927
                    lca_entries.append(_none_entry)
 
928
                    lca_paths.append(None)
 
929
                else:
 
930
                    lca_entries.append(lca_ie)
 
931
                    lca_paths.append(lca_path)
 
932
 
 
933
            try:
 
934
                base_path = self.base_tree.id2path(file_id)
 
935
            except errors.NoSuchId:
 
936
                base_path = None
 
937
                base_ie = _none_entry
 
938
            else:
 
939
                base_ie = next(self.base_tree.iter_entries_by_dir(specific_files=[base_path]))[1]
 
940
 
 
941
            try:
 
942
                this_path = self.this_tree.id2path(file_id)
 
943
            except errors.NoSuchId:
 
944
                this_ie = _none_entry
 
945
                this_path = None
 
946
            else:
 
947
                this_ie = next(self.this_tree.iter_entries_by_dir(specific_files=[this_path]))[1]
 
948
 
 
949
            lca_kinds = []
 
950
            lca_parent_ids = []
 
951
            lca_names = []
 
952
            lca_executable = []
 
953
            for lca_ie in lca_entries:
 
954
                lca_kinds.append(lca_ie.kind)
 
955
                lca_parent_ids.append(lca_ie.parent_id)
 
956
                lca_names.append(lca_ie.name)
 
957
                lca_executable.append(lca_ie.executable)
 
958
 
 
959
            kind_winner = self._lca_multi_way(
 
960
                (base_ie.kind, lca_kinds),
 
961
                other_ie.kind, this_ie.kind)
 
962
            parent_id_winner = self._lca_multi_way(
 
963
                (base_ie.parent_id, lca_parent_ids),
 
964
                other_ie.parent_id, this_ie.parent_id)
 
965
            name_winner = self._lca_multi_way(
 
966
                (base_ie.name, lca_names),
 
967
                other_ie.name, this_ie.name)
 
968
 
 
969
            content_changed = True
 
970
            if kind_winner == 'this':
 
971
                # No kind change in OTHER, see if there are *any* changes
 
972
                if other_ie.kind == 'directory':
 
973
                    if parent_id_winner == 'this' and name_winner == 'this':
 
974
                        # No change for this directory in OTHER, skip
 
975
                        continue
 
976
                    content_changed = False
 
977
                elif other_ie.kind is None or other_ie.kind == 'file':
 
978
                    def get_sha1(tree, path):
 
979
                        if path is None:
 
980
                            return None
 
981
                        try:
 
982
                            return tree.get_file_sha1(path)
 
983
                        except errors.NoSuchFile:
 
984
                            return None
 
985
                    base_sha1 = get_sha1(self.base_tree, base_path)
 
986
                    lca_sha1s = [get_sha1(tree, lca_path)
 
987
                                 for tree, lca_path
 
988
                                 in zip(self._lca_trees, lca_paths)]
 
989
                    this_sha1 = get_sha1(self.this_tree, this_path)
 
990
                    other_sha1 = get_sha1(self.other_tree, other_path)
 
991
                    sha1_winner = self._lca_multi_way(
 
992
                        (base_sha1, lca_sha1s), other_sha1, this_sha1,
 
993
                        allow_overriding_lca=False)
 
994
                    exec_winner = self._lca_multi_way(
 
995
                        (base_ie.executable, lca_executable),
 
996
                        other_ie.executable, this_ie.executable)
 
997
                    if (parent_id_winner == 'this' and name_winner == 'this'
 
998
                            and sha1_winner == 'this' and exec_winner == 'this'):
 
999
                        # No kind, parent, name, exec, or content change for
 
1000
                        # OTHER, so this node is not considered interesting
 
1001
                        continue
 
1002
                    if sha1_winner == 'this':
 
1003
                        content_changed = False
 
1004
                elif other_ie.kind == 'symlink':
 
1005
                    def get_target(ie, tree, path):
 
1006
                        if ie.kind != 'symlink':
 
1007
                            return None
 
1008
                        return tree.get_symlink_target(path)
 
1009
                    base_target = get_target(base_ie, self.base_tree, base_path)
 
1010
                    lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
 
1011
                                   in zip(lca_entries, self._lca_trees, lca_paths)]
 
1012
                    this_target = get_target(
 
1013
                        this_ie, self.this_tree, this_path)
 
1014
                    other_target = get_target(
 
1015
                        other_ie, self.other_tree, other_path)
 
1016
                    target_winner = self._lca_multi_way(
 
1017
                        (base_target, lca_targets),
 
1018
                        other_target, this_target)
 
1019
                    if (parent_id_winner == 'this' and name_winner == 'this'
 
1020
                            and target_winner == 'this'):
 
1021
                        # No kind, parent, name, or symlink target change
 
1022
                        # not interesting
 
1023
                        continue
 
1024
                    if target_winner == 'this':
 
1025
                        content_changed = False
 
1026
                elif other_ie.kind == 'tree-reference':
 
1027
                    # The 'changed' information seems to be handled at a higher
 
1028
                    # level. At least, _entries3 returns False for content
 
1029
                    # changed, even when at a new revision_id.
 
1030
                    content_changed = False
 
1031
                    if (parent_id_winner == 'this' and name_winner == 'this'):
 
1032
                        # Nothing interesting
 
1033
                        continue
 
1034
                else:
 
1035
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1036
 
 
1037
            # If we have gotten this far, that means something has changed
 
1038
            result.append((file_id, content_changed,
 
1039
                           ((base_path, lca_paths),
 
1040
                            other_path, this_path),
 
1041
                           ((base_ie.parent_id, lca_parent_ids),
 
1042
                            other_ie.parent_id, this_ie.parent_id),
 
1043
                           ((base_ie.name, lca_names),
 
1044
                            other_ie.name, this_ie.name),
 
1045
                           ((base_ie.executable, lca_executable),
 
1046
                            other_ie.executable, this_ie.executable)
 
1047
                           ))
 
1048
        return result
 
1049
 
 
1050
    def write_modified(self, results):
 
1051
        if not self.working_tree.supports_merge_modified():
 
1052
            return
 
1053
        modified_hashes = {}
 
1054
        for path in results.modified_paths:
 
1055
            wt_relpath = self.working_tree.relpath(path)
 
1056
            if not self.working_tree.is_versioned(wt_relpath):
 
1057
                continue
 
1058
            hash = self.working_tree.get_file_sha1(wt_relpath)
 
1059
            if hash is None:
 
1060
                continue
 
1061
            modified_hashes[wt_relpath] = hash
 
1062
        self.working_tree.set_merge_modified(modified_hashes)
 
1063
 
 
1064
    @staticmethod
 
1065
    def parent(entry):
 
1066
        """Determine the parent for a file_id (used as a key method)"""
 
1067
        if entry is None:
 
1068
            return None
 
1069
        return entry.parent_id
 
1070
 
 
1071
    @staticmethod
 
1072
    def name(entry):
 
1073
        """Determine the name for a file_id (used as a key method)"""
 
1074
        if entry is None:
 
1075
            return None
 
1076
        return entry.name
 
1077
 
 
1078
    @staticmethod
 
1079
    def contents_sha1(tree, path):
 
1080
        """Determine the sha1 of the file contents (used as a key method)."""
 
1081
        try:
 
1082
            return tree.get_file_sha1(path)
 
1083
        except errors.NoSuchFile:
 
1084
            return None
 
1085
 
 
1086
    @staticmethod
 
1087
    def executable(tree, path):
 
1088
        """Determine the executability of a file-id (used as a key method)."""
 
1089
        try:
 
1090
            if tree.kind(path) != "file":
 
1091
                return False
 
1092
        except errors.NoSuchFile:
 
1093
            return None
 
1094
        return tree.is_executable(path)
 
1095
 
 
1096
    @staticmethod
 
1097
    def kind(tree, path):
 
1098
        """Determine the kind of a file-id (used as a key method)."""
 
1099
        try:
 
1100
            return tree.kind(path)
 
1101
        except errors.NoSuchFile:
 
1102
            return None
 
1103
 
 
1104
    @staticmethod
 
1105
    def _three_way(base, other, this):
 
1106
        if base == other:
 
1107
            # if 'base == other', either they all agree, or only 'this' has
 
1108
            # changed.
 
1109
            return 'this'
 
1110
        elif this not in (base, other):
 
1111
            # 'this' is neither 'base' nor 'other', so both sides changed
 
1112
            return 'conflict'
 
1113
        elif this == other:
 
1114
            # "Ambiguous clean merge" -- both sides have made the same change.
 
1115
            return "this"
 
1116
        else:
 
1117
            # this == base: only other has changed.
 
1118
            return "other"
 
1119
 
 
1120
    @staticmethod
 
1121
    def _lca_multi_way(bases, other, this, allow_overriding_lca=True):
 
1122
        """Consider LCAs when determining whether a change has occurred.
 
1123
 
 
1124
        If LCAS are all identical, this is the same as a _three_way comparison.
 
1125
 
 
1126
        :param bases: value in (BASE, [LCAS])
 
1127
        :param other: value in OTHER
 
1128
        :param this: value in THIS
 
1129
        :param allow_overriding_lca: If there is more than one unique lca
 
1130
            value, allow OTHER to override THIS if it has a new value, and
 
1131
            THIS only has an lca value, or vice versa. This is appropriate for
 
1132
            truly scalar values, not as much for non-scalars.
 
1133
        :return: 'this', 'other', or 'conflict' depending on whether an entry
 
1134
            changed or not.
 
1135
        """
 
1136
        # See doc/developers/lca_tree_merging.txt for details about this
 
1137
        # algorithm.
 
1138
        if other == this:
 
1139
            # Either Ambiguously clean, or nothing was actually changed. We
 
1140
            # don't really care
 
1141
            return 'this'
 
1142
        base_val, lca_vals = bases
 
1143
        # Remove 'base_val' from the lca_vals, because it is not interesting
 
1144
        filtered_lca_vals = [lca_val for lca_val in lca_vals
 
1145
                             if lca_val != base_val]
 
1146
        if len(filtered_lca_vals) == 0:
 
1147
            return Merge3Merger._three_way(base_val, other, this)
 
1148
 
 
1149
        unique_lca_vals = set(filtered_lca_vals)
 
1150
        if len(unique_lca_vals) == 1:
 
1151
            return Merge3Merger._three_way(unique_lca_vals.pop(), other, this)
 
1152
 
 
1153
        if allow_overriding_lca:
 
1154
            if other in unique_lca_vals:
 
1155
                if this in unique_lca_vals:
 
1156
                    # Each side picked a different lca, conflict
 
1157
                    return 'conflict'
 
1158
                else:
 
1159
                    # This has a value which supersedes both lca values, and
 
1160
                    # other only has an lca value
 
1161
                    return 'this'
 
1162
            elif this in unique_lca_vals:
 
1163
                # OTHER has a value which supersedes both lca values, and this
 
1164
                # only has an lca value
 
1165
                return 'other'
 
1166
 
 
1167
        # At this point, the lcas disagree, and the tip disagree
 
1168
        return 'conflict'
 
1169
 
 
1170
    def _merge_names(self, trans_id, file_id, paths, parents, names, resolver):
 
1171
        """Perform a merge on file names and parents"""
 
1172
        base_name, other_name, this_name = names
 
1173
        base_parent, other_parent, this_parent = parents
 
1174
        unused_base_path, other_path, this_path = paths
 
1175
 
 
1176
        name_winner = resolver(*names)
 
1177
 
 
1178
        parent_id_winner = resolver(*parents)
 
1179
        if this_name is None:
 
1180
            if name_winner == "this":
 
1181
                name_winner = "other"
 
1182
            if parent_id_winner == "this":
 
1183
                parent_id_winner = "other"
 
1184
        if name_winner == "this" and parent_id_winner == "this":
 
1185
            return
 
1186
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
 
1187
            # Creating helpers (.OTHER or .THIS) here cause problems down the
 
1188
            # road if a ContentConflict needs to be created so we should not do
 
1189
            # that
 
1190
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
 
1191
                                        this_parent, this_name,
 
1192
                                        other_parent, other_name))
 
1193
        if other_path is None:
 
1194
            # it doesn't matter whether the result was 'other' or
 
1195
            # 'conflict'-- if it has no file id, we leave it alone.
 
1196
            return
 
1197
        parent_id = parents[self.winner_idx[parent_id_winner]]
 
1198
        name = names[self.winner_idx[name_winner]]
 
1199
        if parent_id is not None or name is not None:
 
1200
            # if we get here, name_winner and parent_winner are set to safe
 
1201
            # values.
 
1202
            if parent_id is None and name is not None:
 
1203
                # if parent_id is None and name is non-None, current file is
 
1204
                # the tree root.
 
1205
                if names[self.winner_idx[parent_id_winner]] != '':
 
1206
                    raise AssertionError(
 
1207
                        'File looks like a root, but named %s' %
 
1208
                        names[self.winner_idx[parent_id_winner]])
 
1209
                parent_trans_id = transform.ROOT_PARENT
 
1210
            else:
 
1211
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1212
            self.tt.adjust_path(name, parent_trans_id, trans_id)
 
1213
 
 
1214
    def _do_merge_contents(self, paths, trans_id, file_id):
 
1215
        """Performs a merge on file_id contents."""
 
1216
        def contents_pair(tree, path):
 
1217
            if path is None:
 
1218
                return (None, None)
 
1219
            try:
 
1220
                kind = tree.kind(path)
 
1221
            except errors.NoSuchFile:
 
1222
                return (None, None)
 
1223
            if kind == "file":
 
1224
                contents = tree.get_file_sha1(path)
 
1225
            elif kind == "symlink":
 
1226
                contents = tree.get_symlink_target(path)
 
1227
            else:
 
1228
                contents = None
 
1229
            return kind, contents
 
1230
 
 
1231
        base_path, other_path, this_path = paths
 
1232
        # See SPOT run.  run, SPOT, run.
 
1233
        # So we're not QUITE repeating ourselves; we do tricky things with
 
1234
        # file kind...
 
1235
        other_pair = contents_pair(self.other_tree, other_path)
 
1236
        this_pair = contents_pair(self.this_tree, this_path)
 
1237
        if self._lca_trees:
 
1238
            (base_path, lca_paths) = base_path
 
1239
            base_pair = contents_pair(self.base_tree, base_path)
 
1240
            lca_pairs = [contents_pair(tree, path)
 
1241
                         for tree, path in zip(self._lca_trees, lca_paths)]
 
1242
            winner = self._lca_multi_way((base_pair, lca_pairs), other_pair,
 
1243
                                         this_pair, allow_overriding_lca=False)
 
1244
        else:
 
1245
            base_pair = contents_pair(self.base_tree, base_path)
 
1246
            if base_pair == other_pair:
 
1247
                winner = 'this'
 
1248
            else:
 
1249
                # We delayed evaluating this_pair as long as we can to avoid
 
1250
                # unnecessary sha1 calculation
 
1251
                this_pair = contents_pair(self.this_tree, this_path)
 
1252
                winner = self._three_way(base_pair, other_pair, this_pair)
 
1253
        if winner == 'this':
 
1254
            # No interesting changes introduced by OTHER
 
1255
            return "unmodified"
 
1256
        # We have a hypothetical conflict, but if we have files, then we
 
1257
        # can try to merge the content
 
1258
        params = MergeFileHookParams(
 
1259
            self, (base_path, other_path, this_path), trans_id, this_pair[0],
 
1260
            other_pair[0], winner)
 
1261
        hooks = self.active_hooks
 
1262
        hook_status = 'not_applicable'
 
1263
        for hook in hooks:
 
1264
            hook_status, lines = hook.merge_contents(params)
 
1265
            if hook_status != 'not_applicable':
 
1266
                # Don't try any more hooks, this one applies.
 
1267
                break
 
1268
        # If the merge ends up replacing the content of the file, we get rid of
 
1269
        # it at the end of this method (this variable is used to track the
 
1270
        # exceptions to this rule).
 
1271
        keep_this = False
 
1272
        result = "modified"
 
1273
        if hook_status == 'not_applicable':
 
1274
            # No merge hook was able to resolve the situation. Two cases exist:
 
1275
            # a content conflict or a duplicate one.
 
1276
            result = None
 
1277
            name = self.tt.final_name(trans_id)
 
1278
            parent_id = self.tt.final_parent(trans_id)
 
1279
            inhibit_content_conflict = False
 
1280
            if params.this_kind is None:  # file_id is not in THIS
 
1281
                # Is the name used for a different file_id ?
 
1282
                if self.this_tree.is_versioned(other_path):
 
1283
                    # Two entries for the same path
 
1284
                    keep_this = True
 
1285
                    # versioning the merged file will trigger a duplicate
 
1286
                    # conflict
 
1287
                    self.tt.version_file(file_id, trans_id)
 
1288
                    transform.create_from_tree(
 
1289
                        self.tt, trans_id, self.other_tree,
 
1290
                        other_path,
 
1291
                        filter_tree_path=self._get_filter_tree_path(other_path))
 
1292
                    inhibit_content_conflict = True
 
1293
            elif params.other_kind is None:  # file_id is not in OTHER
 
1294
                # Is the name used for a different file_id ?
 
1295
                if self.other_tree.is_versioned(this_path):
 
1296
                    # Two entries for the same path again, but here, the other
 
1297
                    # entry will also be merged.  We simply inhibit the
 
1298
                    # 'content' conflict creation because we know OTHER will
 
1299
                    # create (or has already created depending on ordering) an
 
1300
                    # entry at the same path. This will trigger a 'duplicate'
 
1301
                    # conflict later.
 
1302
                    keep_this = True
 
1303
                    inhibit_content_conflict = True
 
1304
            if not inhibit_content_conflict:
 
1305
                if params.this_kind is not None:
 
1306
                    self.tt.unversion_file(trans_id)
 
1307
                # This is a contents conflict, because none of the available
 
1308
                # functions could merge it.
 
1309
                file_group = self._dump_conflicts(
 
1310
                    name, (base_path, other_path, this_path), parent_id,
 
1311
                    file_id, set_version=True)
 
1312
                self._raw_conflicts.append(('contents conflict', file_group))
 
1313
        elif hook_status == 'success':
 
1314
            self.tt.create_file(lines, trans_id)
 
1315
        elif hook_status == 'conflicted':
 
1316
            # XXX: perhaps the hook should be able to provide
 
1317
            # the BASE/THIS/OTHER files?
 
1318
            self.tt.create_file(lines, trans_id)
 
1319
            self._raw_conflicts.append(('text conflict', trans_id))
 
1320
            name = self.tt.final_name(trans_id)
 
1321
            parent_id = self.tt.final_parent(trans_id)
 
1322
            self._dump_conflicts(
 
1323
                name, (base_path, other_path, this_path), parent_id, file_id)
 
1324
        elif hook_status == 'delete':
 
1325
            self.tt.unversion_file(trans_id)
 
1326
            result = "deleted"
 
1327
        elif hook_status == 'done':
 
1328
            # The hook function did whatever it needs to do directly, no
 
1329
            # further action needed here.
 
1330
            pass
 
1331
        else:
 
1332
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
 
1333
        if not this_path and result == "modified":
 
1334
            self.tt.version_file(file_id, trans_id)
 
1335
        if not keep_this:
 
1336
            # The merge has been performed and produced a new content, so the
 
1337
            # old contents should not be retained.
 
1338
            self.tt.delete_contents(trans_id)
 
1339
        return result
 
1340
 
 
1341
    def _default_other_winner_merge(self, merge_hook_params):
 
1342
        """Replace this contents with other."""
 
1343
        trans_id = merge_hook_params.trans_id
 
1344
        if merge_hook_params.other_path is not None:
 
1345
            # OTHER changed the file
 
1346
            transform.create_from_tree(
 
1347
                self.tt, trans_id, self.other_tree,
 
1348
                merge_hook_params.other_path,
 
1349
                filter_tree_path=self._get_filter_tree_path(merge_hook_params.other_path))
 
1350
            return 'done', None
 
1351
        elif merge_hook_params.this_path is not None:
 
1352
            # OTHER deleted the file
 
1353
            return 'delete', None
 
1354
        else:
 
1355
            raise AssertionError(
 
1356
                'winner is OTHER, but file %r not in THIS or OTHER tree'
 
1357
                % (merge_hook_params.base_path,))
 
1358
 
 
1359
    def merge_contents(self, merge_hook_params):
 
1360
        """Fallback merge logic after user installed hooks."""
 
1361
        # This function is used in merge hooks as the fallback instance.
 
1362
        # Perhaps making this function and the functions it calls be a
 
1363
        # a separate class would be better.
 
1364
        if merge_hook_params.winner == 'other':
 
1365
            # OTHER is a straight winner, so replace this contents with other
 
1366
            return self._default_other_winner_merge(merge_hook_params)
 
1367
        elif merge_hook_params.is_file_merge():
 
1368
            # THIS and OTHER are both files, so text merge.  Either
 
1369
            # BASE is a file, or both converted to files, so at least we
 
1370
            # have agreement that output should be a file.
 
1371
            try:
 
1372
                self.text_merge(merge_hook_params.trans_id,
 
1373
                                merge_hook_params.paths)
 
1374
            except errors.BinaryFile:
 
1375
                return 'not_applicable', None
 
1376
            return 'done', None
 
1377
        else:
 
1378
            return 'not_applicable', None
 
1379
 
 
1380
    def get_lines(self, tree, path):
 
1381
        """Return the lines in a file, or an empty list."""
 
1382
        if path is None:
 
1383
            return []
 
1384
        try:
 
1385
            kind = tree.kind(path)
 
1386
        except errors.NoSuchFile:
 
1387
            return []
 
1388
        else:
 
1389
            if kind != 'file':
 
1390
                return []
 
1391
            return tree.get_file_lines(path)
 
1392
 
 
1393
    def text_merge(self, trans_id, paths):
 
1394
        """Perform a three-way text merge on a file"""
 
1395
        # it's possible that we got here with base as a different type.
 
1396
        # if so, we just want two-way text conflicts.
 
1397
        base_path, other_path, this_path = paths
 
1398
        base_lines = self.get_lines(self.base_tree, base_path)
 
1399
        other_lines = self.get_lines(self.other_tree, other_path)
 
1400
        this_lines = self.get_lines(self.this_tree, this_path)
 
1401
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
 
1402
                           is_cherrypick=self.cherrypick)
 
1403
        start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
 
1404
        if self.show_base is True:
 
1405
            base_marker = b'|' * 7
 
1406
        else:
 
1407
            base_marker = None
 
1408
 
 
1409
        def iter_merge3(retval):
 
1410
            retval["text_conflicts"] = False
 
1411
            for line in m3.merge_lines(name_a=b"TREE",
 
1412
                                       name_b=b"MERGE-SOURCE",
 
1413
                                       name_base=b"BASE-REVISION",
 
1414
                                       start_marker=start_marker,
 
1415
                                       base_marker=base_marker,
 
1416
                                       reprocess=self.reprocess):
 
1417
                if line.startswith(start_marker):
 
1418
                    retval["text_conflicts"] = True
 
1419
                    yield line.replace(start_marker, b'<' * 7)
 
1420
                else:
 
1421
                    yield line
 
1422
        retval = {}
 
1423
        merge3_iterator = iter_merge3(retval)
 
1424
        self.tt.create_file(merge3_iterator, trans_id)
 
1425
        if retval["text_conflicts"] is True:
 
1426
            self._raw_conflicts.append(('text conflict', trans_id))
 
1427
            name = self.tt.final_name(trans_id)
 
1428
            parent_id = self.tt.final_parent(trans_id)
 
1429
            file_id = self.tt.final_file_id(trans_id)
 
1430
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
 
1431
                                              this_lines, base_lines,
 
1432
                                              other_lines)
 
1433
            file_group.append(trans_id)
 
1434
 
 
1435
    def _get_filter_tree_path(self, path):
 
1436
        if self.this_tree.supports_content_filtering():
 
1437
            # We get the path from the working tree if it exists.
 
1438
            # That fails though when OTHER is adding a file, so
 
1439
            # we fall back to the other tree to find the path if
 
1440
            # it doesn't exist locally.
 
1441
            filter_path = _mod_tree.find_previous_path(
 
1442
                self.other_tree, self.working_tree, path)
 
1443
            if filter_path is None:
 
1444
                filter_path = path
 
1445
            return filter_path
 
1446
        # Skip the lookup for older formats
 
1447
        return None
 
1448
 
 
1449
    def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
 
1450
                        base_lines=None, other_lines=None, set_version=False,
 
1451
                        no_base=False):
 
1452
        """Emit conflict files.
 
1453
        If this_lines, base_lines, or other_lines are omitted, they will be
 
1454
        determined automatically.  If set_version is true, the .OTHER, .THIS
 
1455
        or .BASE (in that order) will be created as versioned files.
 
1456
        """
 
1457
        base_path, other_path, this_path = paths
 
1458
        data = [('OTHER', self.other_tree, other_path, other_lines),
 
1459
                ('THIS', self.this_tree, this_path, this_lines)]
 
1460
        if not no_base:
 
1461
            data.append(('BASE', self.base_tree, base_path, base_lines))
 
1462
 
 
1463
        # We need to use the actual path in the working tree of the file here,
 
1464
        # ignoring the conflict suffixes
 
1465
        wt = self.this_tree
 
1466
        if wt.supports_content_filtering():
 
1467
            try:
 
1468
                filter_tree_path = wt.id2path(file_id)
 
1469
            except errors.NoSuchId:
 
1470
                # file has been deleted
 
1471
                filter_tree_path = None
 
1472
        else:
 
1473
            # Skip the id2path lookup for older formats
 
1474
            filter_tree_path = None
 
1475
 
 
1476
        versioned = False
 
1477
        file_group = []
 
1478
        for suffix, tree, path, lines in data:
 
1479
            if path is not None:
 
1480
                trans_id = self._conflict_file(
 
1481
                    name, parent_id, path, tree, suffix, lines,
 
1482
                    filter_tree_path)
 
1483
                file_group.append(trans_id)
 
1484
                if set_version and not versioned:
 
1485
                    self.tt.version_file(file_id, trans_id)
 
1486
                    versioned = True
 
1487
        return file_group
 
1488
 
 
1489
    def _conflict_file(self, name, parent_id, path, tree, suffix,
 
1490
                       lines=None, filter_tree_path=None):
 
1491
        """Emit a single conflict file."""
 
1492
        name = name + '.' + suffix
 
1493
        trans_id = self.tt.create_path(name, parent_id)
 
1494
        transform.create_from_tree(
 
1495
            self.tt, trans_id, tree, path,
 
1496
            chunks=lines,
 
1497
            filter_tree_path=filter_tree_path)
 
1498
        return trans_id
 
1499
 
 
1500
    def _merge_executable(self, paths, trans_id, executable, file_status,
 
1501
                          resolver):
 
1502
        """Perform a merge on the execute bit."""
 
1503
        base_executable, other_executable, this_executable = executable
 
1504
        base_path, other_path, this_path = paths
 
1505
        if file_status == "deleted":
 
1506
            return
 
1507
        winner = resolver(*executable)
 
1508
        if winner == "conflict":
 
1509
            # There must be a None in here, if we have a conflict, but we
 
1510
            # need executability since file status was not deleted.
 
1511
            if other_path is None:
 
1512
                winner = "this"
 
1513
            else:
 
1514
                winner = "other"
 
1515
        if winner == 'this' and file_status != "modified":
 
1516
            return
 
1517
        if self.tt.final_kind(trans_id) != "file":
 
1518
            return
 
1519
        if winner == "this":
 
1520
            executability = this_executable
 
1521
        else:
 
1522
            if other_path is not None:
 
1523
                executability = other_executable
 
1524
            elif this_path is not None:
 
1525
                executability = this_executable
 
1526
            elif base_path is not None:
 
1527
                executability = base_executable
 
1528
        if executability is not None:
 
1529
            self.tt.set_executability(executability, trans_id)
 
1530
 
 
1531
    def cook_conflicts(self, fs_conflicts):
 
1532
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1533
        content_conflict_file_ids = set()
 
1534
        cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
 
1535
        fp = transform.FinalPaths(self.tt)
 
1536
        for conflict in self._raw_conflicts:
 
1537
            conflict_type = conflict[0]
 
1538
            if conflict_type == 'path conflict':
 
1539
                (trans_id, file_id,
 
1540
                 this_parent, this_name,
 
1541
                 other_parent, other_name) = conflict[1:]
 
1542
                if this_parent is None or this_name is None:
 
1543
                    this_path = '<deleted>'
 
1544
                else:
 
1545
                    parent_path = fp.get_path(
 
1546
                        self.tt.trans_id_file_id(this_parent))
 
1547
                    this_path = osutils.pathjoin(parent_path, this_name)
 
1548
                if other_parent is None or other_name is None:
 
1549
                    other_path = '<deleted>'
 
1550
                else:
 
1551
                    if other_parent == self.other_tree.path2id(''):
 
1552
                        # The tree transform doesn't know about the other root,
 
1553
                        # so we special case here to avoid a NoFinalPath
 
1554
                        # exception
 
1555
                        parent_path = ''
 
1556
                    else:
 
1557
                        parent_path = fp.get_path(
 
1558
                            self.tt.trans_id_file_id(other_parent))
 
1559
                    other_path = osutils.pathjoin(parent_path, other_name)
 
1560
                c = _mod_conflicts.Conflict.factory(
 
1561
                    'path conflict', path=this_path,
 
1562
                    conflict_path=other_path,
 
1563
                    file_id=file_id)
 
1564
            elif conflict_type == 'contents conflict':
 
1565
                for trans_id in conflict[1]:
 
1566
                    file_id = self.tt.final_file_id(trans_id)
 
1567
                    if file_id is not None:
 
1568
                        # Ok we found the relevant file-id
 
1569
                        break
 
1570
                path = fp.get_path(trans_id)
 
1571
                for suffix in ('.BASE', '.THIS', '.OTHER'):
 
1572
                    if path.endswith(suffix):
 
1573
                        # Here is the raw path
 
1574
                        path = path[:-len(suffix)]
 
1575
                        break
 
1576
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1577
                                                    path=path, file_id=file_id)
 
1578
                content_conflict_file_ids.add(file_id)
 
1579
            elif conflict_type == 'text conflict':
 
1580
                trans_id = conflict[1]
 
1581
                path = fp.get_path(trans_id)
 
1582
                file_id = self.tt.final_file_id(trans_id)
 
1583
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1584
                                                    path=path, file_id=file_id)
 
1585
            else:
 
1586
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1587
            cooked_conflicts.append(c)
 
1588
 
 
1589
        self.cooked_conflicts = []
 
1590
        # We want to get rid of path conflicts when a corresponding contents
 
1591
        # conflict exists. This can occur when one branch deletes a file while
 
1592
        # the other renames *and* modifies it. In this case, the content
 
1593
        # conflict is enough.
 
1594
        for c in cooked_conflicts:
 
1595
            if (c.typestring == 'path conflict'
 
1596
                    and c.file_id in content_conflict_file_ids):
 
1597
                continue
 
1598
            self.cooked_conflicts.append(c)
 
1599
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
 
1600
 
 
1601
 
 
1602
class WeaveMerger(Merge3Merger):
 
1603
    """Three-way tree merger, text weave merger."""
 
1604
    supports_reprocess = True
 
1605
    supports_show_base = False
 
1606
    supports_reverse_cherrypick = False
 
1607
    history_based = True
 
1608
    requires_file_merge_plan = True
 
1609
 
 
1610
    def _generate_merge_plan(self, this_path, base):
 
1611
        return self.this_tree.plan_file_merge(this_path, self.other_tree,
 
1612
                                              base=base)
 
1613
 
 
1614
    def _merged_lines(self, this_path):
 
1615
        """Generate the merged lines.
 
1616
        There is no distinction between lines that are meant to contain <<<<<<<
 
1617
        and conflicts.
 
1618
        """
 
1619
        if self.cherrypick:
 
1620
            base = self.base_tree
 
1621
        else:
 
1622
            base = None
 
1623
        plan = self._generate_merge_plan(this_path, base)
 
1624
        if 'merge' in debug.debug_flags:
 
1625
            plan = list(plan)
 
1626
            trans_id = self.tt.trans_id_file_id(file_id)
 
1627
            name = self.tt.final_name(trans_id) + '.plan'
 
1628
            contents = (b'%11s|%s' % l for l in plan)
 
1629
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
 
1630
        textmerge = versionedfile.PlanWeaveMerge(plan, b'<<<<<<< TREE\n',
 
1631
                                                 b'>>>>>>> MERGE-SOURCE\n')
 
1632
        lines, conflicts = textmerge.merge_lines(self.reprocess)
 
1633
        if conflicts:
 
1634
            base_lines = textmerge.base_from_plan()
 
1635
        else:
 
1636
            base_lines = None
 
1637
        return lines, base_lines
 
1638
 
 
1639
    def text_merge(self, trans_id, paths):
 
1640
        """Perform a (weave) text merge for a given file and file-id.
 
1641
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
 
1642
        and a conflict will be noted.
 
1643
        """
 
1644
        base_path, other_path, this_path = paths
 
1645
        lines, base_lines = self._merged_lines(this_path)
 
1646
        lines = list(lines)
 
1647
        # Note we're checking whether the OUTPUT is binary in this case,
 
1648
        # because we don't want to get into weave merge guts.
 
1649
        textfile.check_text_lines(lines)
 
1650
        self.tt.create_file(lines, trans_id)
 
1651
        if base_lines is not None:
 
1652
            # Conflict
 
1653
            self._raw_conflicts.append(('text conflict', trans_id))
 
1654
            name = self.tt.final_name(trans_id)
 
1655
            parent_id = self.tt.final_parent(trans_id)
 
1656
            file_id = self.tt.final_file_id(trans_id)
 
1657
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
 
1658
                                              no_base=False,
 
1659
                                              base_lines=base_lines)
 
1660
            file_group.append(trans_id)
 
1661
 
 
1662
 
 
1663
class LCAMerger(WeaveMerger):
 
1664
 
 
1665
    requires_file_merge_plan = True
 
1666
 
 
1667
    def _generate_merge_plan(self, this_path, base):
 
1668
        return self.this_tree.plan_file_lca_merge(this_path, self.other_tree,
 
1669
                                                  base=base)
 
1670
 
 
1671
 
 
1672
class Diff3Merger(Merge3Merger):
 
1673
    """Three-way merger using external diff3 for text merging"""
 
1674
 
 
1675
    requires_file_merge_plan = False
 
1676
 
 
1677
    def dump_file(self, temp_dir, name, tree, path):
 
1678
        out_path = osutils.pathjoin(temp_dir, name)
 
1679
        with open(out_path, "wb") as out_file:
 
1680
            in_file = tree.get_file(path)
 
1681
            for line in in_file:
 
1682
                out_file.write(line)
 
1683
        return out_path
 
1684
 
 
1685
    def text_merge(self, trans_id, paths):
 
1686
        """Perform a diff3 merge using a specified file-id and trans-id.
 
1687
        If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
 
1688
        will be dumped, and a will be conflict noted.
 
1689
        """
 
1690
        import breezy.patch
 
1691
        base_path, other_path, this_path = paths
 
1692
        temp_dir = osutils.mkdtemp(prefix="bzr-")
 
1693
        try:
 
1694
            new_file = osutils.pathjoin(temp_dir, "new")
 
1695
            this = self.dump_file(
 
1696
                temp_dir, "this", self.this_tree, this_path)
 
1697
            base = self.dump_file(
 
1698
                temp_dir, "base", self.base_tree, base_path)
 
1699
            other = self.dump_file(
 
1700
                temp_dir, "other", self.other_tree, other_path)
 
1701
            status = breezy.patch.diff3(new_file, this, base, other)
 
1702
            if status not in (0, 1):
 
1703
                raise errors.BzrError("Unhandled diff3 exit code")
 
1704
            with open(new_file, 'rb') as f:
 
1705
                self.tt.create_file(f, trans_id)
 
1706
            if status == 1:
 
1707
                name = self.tt.final_name(trans_id)
 
1708
                parent_id = self.tt.final_parent(trans_id)
 
1709
                file_id = self.tt.final_file_id(trans_id)
 
1710
                self._dump_conflicts(name, paths, parent_id, file_id)
 
1711
                self._raw_conflicts.append(('text conflict', trans_id))
 
1712
        finally:
 
1713
            osutils.rmtree(temp_dir)
 
1714
 
 
1715
 
 
1716
class PathNotInTree(errors.BzrError):
 
1717
 
 
1718
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1719
 
 
1720
    def __init__(self, path, tree):
 
1721
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1722
 
 
1723
 
 
1724
class MergeIntoMerger(Merger):
 
1725
    """Merger that understands other_tree will be merged into a subdir.
 
1726
 
 
1727
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1728
    and RevisonTree objects, rather than using revision specs.
 
1729
    """
 
1730
 
 
1731
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1732
                 source_subpath, other_rev_id=None):
 
1733
        """Create a new MergeIntoMerger object.
 
1734
 
 
1735
        source_subpath in other_tree will be effectively copied to
 
1736
        target_subdir in this_tree.
 
1737
 
 
1738
        :param this_tree: The tree that we will be merging into.
 
1739
        :param other_branch: The Branch we will be merging from.
 
1740
        :param other_tree: The RevisionTree object we want to merge.
 
1741
        :param target_subdir: The relative path where we want to merge
 
1742
            other_tree into this_tree
 
1743
        :param source_subpath: The relative path specifying the subtree of
 
1744
            other_tree to merge into this_tree.
 
1745
        """
 
1746
        # It is assumed that we are merging a tree that is not in our current
 
1747
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1748
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1749
            _mod_revision.NULL_REVISION)
 
1750
        super(MergeIntoMerger, self).__init__(
 
1751
            this_branch=this_tree.branch,
 
1752
            this_tree=this_tree,
 
1753
            other_tree=other_tree,
 
1754
            base_tree=null_ancestor_tree,
 
1755
            )
 
1756
        self._target_subdir = target_subdir
 
1757
        self._source_subpath = source_subpath
 
1758
        self.other_branch = other_branch
 
1759
        if other_rev_id is None:
 
1760
            other_rev_id = other_tree.get_revision_id()
 
1761
        self.other_rev_id = self.other_basis = other_rev_id
 
1762
        self.base_is_ancestor = True
 
1763
        self.backup_files = True
 
1764
        self.merge_type = Merge3Merger
 
1765
        self.show_base = False
 
1766
        self.reprocess = False
 
1767
        self.interesting_files = None
 
1768
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1769
                                                  target_subdir=self._target_subdir,
 
1770
                                                  source_subpath=self._source_subpath)
 
1771
        if self._source_subpath != '':
 
1772
            # If this isn't a partial merge make sure the revisions will be
 
1773
            # present.
 
1774
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1775
                              self.other_basis)
 
1776
 
 
1777
    def set_pending(self):
 
1778
        if self._source_subpath != '':
 
1779
            return
 
1780
        Merger.set_pending(self)
 
1781
 
 
1782
 
 
1783
class _MergeTypeParameterizer(object):
 
1784
    """Wrap a merge-type class to provide extra parameters.
 
1785
 
 
1786
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1787
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1788
    the 'merge_type' member.  It is difficult override do_merge without
 
1789
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1790
    the extra parameters.
 
1791
    """
 
1792
 
 
1793
    def __init__(self, merge_type, **kwargs):
 
1794
        self._extra_kwargs = kwargs
 
1795
        self._merge_type = merge_type
 
1796
 
 
1797
    def __call__(self, *args, **kwargs):
 
1798
        kwargs.update(self._extra_kwargs)
 
1799
        return self._merge_type(*args, **kwargs)
 
1800
 
 
1801
    def __getattr__(self, name):
 
1802
        return getattr(self._merge_type, name)
 
1803
 
 
1804
 
 
1805
class MergeIntoMergeType(Merge3Merger):
 
1806
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1807
 
 
1808
    def __init__(self, *args, **kwargs):
 
1809
        """Initialize the merger object.
 
1810
 
 
1811
        :param args: See Merge3Merger.__init__'s args.
 
1812
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1813
            source_subpath and target_subdir.
 
1814
        :keyword source_subpath: The relative path specifying the subtree of
 
1815
            other_tree to merge into this_tree.
 
1816
        :keyword target_subdir: The relative path where we want to merge
 
1817
            other_tree into this_tree
 
1818
        """
 
1819
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1820
        # so we have have to hack in to get our extra parameters set.
 
1821
        self._source_subpath = kwargs.pop('source_subpath')
 
1822
        self._target_subdir = kwargs.pop('target_subdir')
 
1823
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1824
 
 
1825
    def _compute_transform(self):
 
1826
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1827
            entries = self._entries_to_incorporate()
 
1828
            entries = list(entries)
 
1829
            for num, (entry, parent_id, relpath) in enumerate(entries):
 
1830
                child_pb.update(gettext('Preparing file merge'),
 
1831
                                num, len(entries))
 
1832
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1833
                path = osutils.pathjoin(self._source_subpath, relpath)
 
1834
                trans_id = transform.new_by_entry(path, self.tt, entry,
 
1835
                                                  parent_trans_id, self.other_tree)
 
1836
        self._finish_computing_transform()
 
1837
 
 
1838
    def _entries_to_incorporate(self):
 
1839
        """Yields pairs of (inventory_entry, new_parent)."""
 
1840
        subdir_id = self.other_tree.path2id(self._source_subpath)
 
1841
        if subdir_id is None:
 
1842
            # XXX: The error would be clearer if it gave the URL of the source
 
1843
            # branch, but we don't have a reference to that here.
 
1844
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1845
        subdir = next(self.other_tree.iter_entries_by_dir(
 
1846
            specific_files=[self._source_subpath]))[1]
 
1847
        parent_in_target = osutils.dirname(self._target_subdir)
 
1848
        target_id = self.this_tree.path2id(parent_in_target)
 
1849
        if target_id is None:
 
1850
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1851
        name_in_target = osutils.basename(self._target_subdir)
 
1852
        merge_into_root = subdir.copy()
 
1853
        merge_into_root.name = name_in_target
 
1854
        try:
 
1855
            self.this_tree.id2path(merge_into_root.file_id)
 
1856
        except errors.NoSuchId:
 
1857
            pass
 
1858
        else:
 
1859
            # Give the root a new file-id.
 
1860
            # This can happen fairly easily if the directory we are
 
1861
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1862
            # their root_id.  Users will expect this to Just Work, so we
 
1863
            # change the file-id here.
 
1864
            # Non-root file-ids could potentially conflict too.  That's really
 
1865
            # an edge case, so we don't do anything special for those.  We let
 
1866
            # them cause conflicts.
 
1867
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1868
        yield (merge_into_root, target_id, '')
 
1869
        if subdir.kind != 'directory':
 
1870
            # No children, so we are done.
 
1871
            return
 
1872
        for path, entry in self.other_tree.root_inventory.iter_entries_by_dir(subdir_id):
 
1873
            parent_id = entry.parent_id
 
1874
            if parent_id == subdir.file_id:
 
1875
                # The root's parent ID has changed, so make sure children of
 
1876
                # the root refer to the new ID.
 
1877
                parent_id = merge_into_root.file_id
 
1878
            yield (entry, parent_id, path)
 
1879
 
 
1880
 
 
1881
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
 
1882
                backup_files=False,
 
1883
                merge_type=Merge3Merger,
 
1884
                show_base=False,
 
1885
                reprocess=False,
 
1886
                other_rev_id=None,
 
1887
                interesting_files=None,
 
1888
                this_tree=None,
 
1889
                change_reporter=None):
 
1890
    """Primary interface for merging.
 
1891
 
 
1892
    Typical use is probably::
 
1893
 
 
1894
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1895
                    branch.get_revision_tree(base_revision))
 
1896
    """
 
1897
    if this_tree is None:
 
1898
        raise errors.BzrError("breezy.merge.merge_inner requires a this_tree "
 
1899
                              "parameter")
 
1900
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1901
                    change_reporter=change_reporter)
 
1902
    merger.backup_files = backup_files
 
1903
    merger.merge_type = merge_type
 
1904
    merger.ignore_zero = ignore_zero
 
1905
    merger.interesting_files = interesting_files
 
1906
    merger.show_base = show_base
 
1907
    merger.reprocess = reprocess
 
1908
    merger.other_rev_id = other_rev_id
 
1909
    merger.other_basis = other_rev_id
 
1910
    get_revision_id = getattr(base_tree, 'get_revision_id', None)
 
1911
    if get_revision_id is None:
 
1912
        get_revision_id = base_tree.last_revision
 
1913
    merger.cache_trees_with_revision_ids([other_tree, base_tree, this_tree])
 
1914
    merger.set_base_revision(get_revision_id(), this_branch)
 
1915
    return merger.do_merge()
 
1916
 
 
1917
 
 
1918
merge_type_registry = registry.Registry()
 
1919
merge_type_registry.register('diff3', Diff3Merger,
 
1920
                             "Merge using external diff3.")
 
1921
merge_type_registry.register('lca', LCAMerger,
 
1922
                             "LCA-newness merge.")
 
1923
merge_type_registry.register('merge3', Merge3Merger,
 
1924
                             "Native diff3-style merge.")
 
1925
merge_type_registry.register('weave', WeaveMerger,
 
1926
                             "Weave-based merge.")
 
1927
 
 
1928
 
 
1929
def get_merge_type_registry():
 
1930
    """Merge type registry was previously in breezy.option
 
1931
 
 
1932
    This method provides a backwards compatible way to retrieve it.
 
1933
    """
 
1934
    return merge_type_registry
 
1935
 
 
1936
 
 
1937
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
 
1938
    def status_a(revision, text):
 
1939
        if revision in ancestors_b:
 
1940
            return 'killed-b', text
 
1941
        else:
 
1942
            return 'new-a', text
 
1943
 
 
1944
    def status_b(revision, text):
 
1945
        if revision in ancestors_a:
 
1946
            return 'killed-a', text
 
1947
        else:
 
1948
            return 'new-b', text
 
1949
 
 
1950
    plain_a = [t for (a, t) in annotated_a]
 
1951
    plain_b = [t for (a, t) in annotated_b]
 
1952
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
 
1953
    blocks = matcher.get_matching_blocks()
 
1954
    a_cur = 0
 
1955
    b_cur = 0
 
1956
    for ai, bi, l in blocks:
 
1957
        # process all mismatched sections
 
1958
        # (last mismatched section is handled because blocks always
 
1959
        # includes a 0-length last block)
 
1960
        for revision, text in annotated_a[a_cur:ai]:
 
1961
            yield status_a(revision, text)
 
1962
        for revision, text in annotated_b[b_cur:bi]:
 
1963
            yield status_b(revision, text)
 
1964
        # and now the matched section
 
1965
        a_cur = ai + l
 
1966
        b_cur = bi + l
 
1967
        for text_a in plain_a[ai:a_cur]:
 
1968
            yield "unchanged", text_a
 
1969
 
 
1970
 
 
1971
class _PlanMergeBase(object):
 
1972
 
 
1973
    def __init__(self, a_rev, b_rev, vf, key_prefix):
 
1974
        """Contructor.
 
1975
 
 
1976
        :param a_rev: Revision-id of one revision to merge
 
1977
        :param b_rev: Revision-id of the other revision to merge
 
1978
        :param vf: A VersionedFiles containing both revisions
 
1979
        :param key_prefix: A prefix for accessing keys in vf, typically
 
1980
            (file_id,).
 
1981
        """
 
1982
        self.a_rev = a_rev
 
1983
        self.b_rev = b_rev
 
1984
        self.vf = vf
 
1985
        self._last_lines = None
 
1986
        self._last_lines_revision_id = None
 
1987
        self._cached_matching_blocks = {}
 
1988
        self._key_prefix = key_prefix
 
1989
        self._precache_tip_lines()
 
1990
 
 
1991
    def _precache_tip_lines(self):
 
1992
        lines = self.get_lines([self.a_rev, self.b_rev])
 
1993
        self.lines_a = lines[self.a_rev]
 
1994
        self.lines_b = lines[self.b_rev]
 
1995
 
 
1996
    def get_lines(self, revisions):
 
1997
        """Get lines for revisions from the backing VersionedFiles.
 
1998
 
 
1999
        :raises RevisionNotPresent: on absent texts.
 
2000
        """
 
2001
        keys = [(self._key_prefix + (rev,)) for rev in revisions]
 
2002
        result = {}
 
2003
        for record in self.vf.get_record_stream(keys, 'unordered', True):
 
2004
            if record.storage_kind == 'absent':
 
2005
                raise errors.RevisionNotPresent(record.key, self.vf)
 
2006
            result[record.key[-1]] = record.get_bytes_as('lines')
 
2007
        return result
 
2008
 
 
2009
    def plan_merge(self):
 
2010
        """Generate a 'plan' for merging the two revisions.
 
2011
 
 
2012
        This involves comparing their texts and determining the cause of
 
2013
        differences.  If text A has a line and text B does not, then either the
 
2014
        line was added to text A, or it was deleted from B.  Once the causes
 
2015
        are combined, they are written out in the format described in
 
2016
        VersionedFile.plan_merge
 
2017
        """
 
2018
        blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
 
2019
        unique_a, unique_b = self._unique_lines(blocks)
 
2020
        new_a, killed_b = self._determine_status(self.a_rev, unique_a)
 
2021
        new_b, killed_a = self._determine_status(self.b_rev, unique_b)
 
2022
        return self._iter_plan(blocks, new_a, killed_b, new_b, killed_a)
 
2023
 
 
2024
    def _iter_plan(self, blocks, new_a, killed_b, new_b, killed_a):
 
2025
        last_i = 0
 
2026
        last_j = 0
 
2027
        for i, j, n in blocks:
 
2028
            for a_index in range(last_i, i):
 
2029
                if a_index in new_a:
 
2030
                    if a_index in killed_b:
 
2031
                        yield 'conflicted-a', self.lines_a[a_index]
 
2032
                    else:
 
2033
                        yield 'new-a', self.lines_a[a_index]
 
2034
                else:
 
2035
                    yield 'killed-b', self.lines_a[a_index]
 
2036
            for b_index in range(last_j, j):
 
2037
                if b_index in new_b:
 
2038
                    if b_index in killed_a:
 
2039
                        yield 'conflicted-b', self.lines_b[b_index]
 
2040
                    else:
 
2041
                        yield 'new-b', self.lines_b[b_index]
 
2042
                else:
 
2043
                    yield 'killed-a', self.lines_b[b_index]
 
2044
            # handle common lines
 
2045
            for a_index in range(i, i + n):
 
2046
                yield 'unchanged', self.lines_a[a_index]
 
2047
            last_i = i + n
 
2048
            last_j = j + n
 
2049
 
 
2050
    def _get_matching_blocks(self, left_revision, right_revision):
 
2051
        """Return a description of which sections of two revisions match.
 
2052
 
 
2053
        See SequenceMatcher.get_matching_blocks
 
2054
        """
 
2055
        cached = self._cached_matching_blocks.get((left_revision,
 
2056
                                                   right_revision))
 
2057
        if cached is not None:
 
2058
            return cached
 
2059
        if self._last_lines_revision_id == left_revision:
 
2060
            left_lines = self._last_lines
 
2061
            right_lines = self.get_lines([right_revision])[right_revision]
 
2062
        else:
 
2063
            lines = self.get_lines([left_revision, right_revision])
 
2064
            left_lines = lines[left_revision]
 
2065
            right_lines = lines[right_revision]
 
2066
        self._last_lines = right_lines
 
2067
        self._last_lines_revision_id = right_revision
 
2068
        matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
 
2069
                                                       right_lines)
 
2070
        return matcher.get_matching_blocks()
 
2071
 
 
2072
    def _unique_lines(self, matching_blocks):
 
2073
        """Analyse matching_blocks to determine which lines are unique
 
2074
 
 
2075
        :return: a tuple of (unique_left, unique_right), where the values are
 
2076
            sets of line numbers of unique lines.
 
2077
        """
 
2078
        last_i = 0
 
2079
        last_j = 0
 
2080
        unique_left = []
 
2081
        unique_right = []
 
2082
        for i, j, n in matching_blocks:
 
2083
            unique_left.extend(range(last_i, i))
 
2084
            unique_right.extend(range(last_j, j))
 
2085
            last_i = i + n
 
2086
            last_j = j + n
 
2087
        return unique_left, unique_right
 
2088
 
 
2089
    @staticmethod
 
2090
    def _subtract_plans(old_plan, new_plan):
 
2091
        """Remove changes from new_plan that came from old_plan.
 
2092
 
 
2093
        It is assumed that the difference between the old_plan and new_plan
 
2094
        is their choice of 'b' text.
 
2095
 
 
2096
        All lines from new_plan that differ from old_plan are emitted
 
2097
        verbatim.  All lines from new_plan that match old_plan but are
 
2098
        not about the 'b' revision are emitted verbatim.
 
2099
 
 
2100
        Lines that match and are about the 'b' revision are the lines we
 
2101
        don't want, so we convert 'killed-b' -> 'unchanged', and 'new-b'
 
2102
        is skipped entirely.
 
2103
        """
 
2104
        matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
 
2105
                                                       new_plan)
 
2106
        last_j = 0
 
2107
        for i, j, n in matcher.get_matching_blocks():
 
2108
            for jj in range(last_j, j):
 
2109
                yield new_plan[jj]
 
2110
            for jj in range(j, j + n):
 
2111
                plan_line = new_plan[jj]
 
2112
                if plan_line[0] == 'new-b':
 
2113
                    pass
 
2114
                elif plan_line[0] == 'killed-b':
 
2115
                    yield 'unchanged', plan_line[1]
 
2116
                else:
 
2117
                    yield plan_line
 
2118
            last_j = j + n
 
2119
 
 
2120
 
 
2121
class _PlanMerge(_PlanMergeBase):
 
2122
    """Plan an annotate merge using on-the-fly annotation"""
 
2123
 
 
2124
    def __init__(self, a_rev, b_rev, vf, key_prefix):
 
2125
        super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
 
2126
        self.a_key = self._key_prefix + (self.a_rev,)
 
2127
        self.b_key = self._key_prefix + (self.b_rev,)
 
2128
        self.graph = _mod_graph.Graph(self.vf)
 
2129
        heads = self.graph.heads((self.a_key, self.b_key))
 
2130
        if len(heads) == 1:
 
2131
            # one side dominates, so we can just return its values, yay for
 
2132
            # per-file graphs
 
2133
            # Ideally we would know that before we get this far
 
2134
            self._head_key = heads.pop()
 
2135
            if self._head_key == self.a_key:
 
2136
                other = b_rev
 
2137
            else:
 
2138
                other = a_rev
 
2139
            trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
 
2140
                         self._head_key[-1], other)
 
2141
            self._weave = None
 
2142
        else:
 
2143
            self._head_key = None
 
2144
            self._build_weave()
 
2145
 
 
2146
    def _precache_tip_lines(self):
 
2147
        # Turn this into a no-op, because we will do this later
 
2148
        pass
 
2149
 
 
2150
    def _find_recursive_lcas(self):
 
2151
        """Find all the ancestors back to a unique lca"""
 
2152
        cur_ancestors = (self.a_key, self.b_key)
 
2153
        # graph.find_lca(uncommon, keys) now returns plain NULL_REVISION,
 
2154
        # rather than a key tuple. We will just map that directly to no common
 
2155
        # ancestors.
 
2156
        parent_map = {}
 
2157
        while True:
 
2158
            next_lcas = self.graph.find_lca(*cur_ancestors)
 
2159
            # Map a plain NULL_REVISION to a simple no-ancestors
 
2160
            if next_lcas == {_mod_revision.NULL_REVISION}:
 
2161
                next_lcas = ()
 
2162
            # Order the lca's based on when they were merged into the tip
 
2163
            # While the actual merge portion of weave merge uses a set() of
 
2164
            # active revisions, the order of insertion *does* effect the
 
2165
            # implicit ordering of the texts.
 
2166
            for rev_key in cur_ancestors:
 
2167
                ordered_parents = tuple(self.graph.find_merge_order(rev_key,
 
2168
                                                                    next_lcas))
 
2169
                parent_map[rev_key] = ordered_parents
 
2170
            if len(next_lcas) == 0:
 
2171
                break
 
2172
            elif len(next_lcas) == 1:
 
2173
                parent_map[list(next_lcas)[0]] = ()
 
2174
                break
 
2175
            elif len(next_lcas) > 2:
 
2176
                # More than 2 lca's, fall back to grabbing all nodes between
 
2177
                # this and the unique lca.
 
2178
                trace.mutter('More than 2 LCAs, falling back to all nodes for:'
 
2179
                             ' %s, %s\n=> %s',
 
2180
                             self.a_key, self.b_key, cur_ancestors)
 
2181
                cur_lcas = next_lcas
 
2182
                while len(cur_lcas) > 1:
 
2183
                    cur_lcas = self.graph.find_lca(*cur_lcas)
 
2184
                if len(cur_lcas) == 0:
 
2185
                    # No common base to find, use the full ancestry
 
2186
                    unique_lca = None
 
2187
                else:
 
2188
                    unique_lca = list(cur_lcas)[0]
 
2189
                    if unique_lca == _mod_revision.NULL_REVISION:
 
2190
                        # find_lca will return a plain 'NULL_REVISION' rather
 
2191
                        # than a key tuple when there is no common ancestor, we
 
2192
                        # prefer to just use None, because it doesn't confuse
 
2193
                        # _get_interesting_texts()
 
2194
                        unique_lca = None
 
2195
                parent_map.update(self._find_unique_parents(next_lcas,
 
2196
                                                            unique_lca))
 
2197
                break
 
2198
            cur_ancestors = next_lcas
 
2199
        return parent_map
 
2200
 
 
2201
    def _find_unique_parents(self, tip_keys, base_key):
 
2202
        """Find ancestors of tip that aren't ancestors of base.
 
2203
 
 
2204
        :param tip_keys: Nodes that are interesting
 
2205
        :param base_key: Cull all ancestors of this node
 
2206
        :return: The parent map for all revisions between tip_keys and
 
2207
            base_key. base_key will be included. References to nodes outside of
 
2208
            the ancestor set will also be removed.
 
2209
        """
 
2210
        # TODO: this would be simpler if find_unique_ancestors took a list
 
2211
        #       instead of a single tip, internally it supports it, but it
 
2212
        #       isn't a "backwards compatible" api change.
 
2213
        if base_key is None:
 
2214
            parent_map = dict(self.graph.iter_ancestry(tip_keys))
 
2215
            # We remove NULL_REVISION because it isn't a proper tuple key, and
 
2216
            # thus confuses things like _get_interesting_texts, and our logic
 
2217
            # to add the texts into the memory weave.
 
2218
            if _mod_revision.NULL_REVISION in parent_map:
 
2219
                parent_map.pop(_mod_revision.NULL_REVISION)
 
2220
        else:
 
2221
            interesting = set()
 
2222
            for tip in tip_keys:
 
2223
                interesting.update(
 
2224
                    self.graph.find_unique_ancestors(tip, [base_key]))
 
2225
            parent_map = self.graph.get_parent_map(interesting)
 
2226
            parent_map[base_key] = ()
 
2227
        culled_parent_map, child_map, tails = self._remove_external_references(
 
2228
            parent_map)
 
2229
        # Remove all the tails but base_key
 
2230
        if base_key is not None:
 
2231
            tails.remove(base_key)
 
2232
            self._prune_tails(culled_parent_map, child_map, tails)
 
2233
        # Now remove all the uninteresting 'linear' regions
 
2234
        simple_map = _mod_graph.collapse_linear_regions(culled_parent_map)
 
2235
        return simple_map
 
2236
 
 
2237
    @staticmethod
 
2238
    def _remove_external_references(parent_map):
 
2239
        """Remove references that go outside of the parent map.
 
2240
 
 
2241
        :param parent_map: Something returned from Graph.get_parent_map(keys)
 
2242
        :return: (filtered_parent_map, child_map, tails)
 
2243
            filtered_parent_map is parent_map without external references
 
2244
            child_map is the {parent_key: [child_keys]} mapping
 
2245
            tails is a list of nodes that do not have any parents in the map
 
2246
        """
 
2247
        # TODO: The basic effect of this function seems more generic than
 
2248
        #       _PlanMerge. But the specific details of building a child_map,
 
2249
        #       and computing tails seems very specific to _PlanMerge.
 
2250
        #       Still, should this be in Graph land?
 
2251
        filtered_parent_map = {}
 
2252
        child_map = {}
 
2253
        tails = []
 
2254
        for key, parent_keys in parent_map.items():
 
2255
            culled_parent_keys = [p for p in parent_keys if p in parent_map]
 
2256
            if not culled_parent_keys:
 
2257
                tails.append(key)
 
2258
            for parent_key in culled_parent_keys:
 
2259
                child_map.setdefault(parent_key, []).append(key)
 
2260
            # TODO: Do we want to do this, it adds overhead for every node,
 
2261
            #       just to say that the node has no children
 
2262
            child_map.setdefault(key, [])
 
2263
            filtered_parent_map[key] = culled_parent_keys
 
2264
        return filtered_parent_map, child_map, tails
 
2265
 
 
2266
    @staticmethod
 
2267
    def _prune_tails(parent_map, child_map, tails_to_remove):
 
2268
        """Remove tails from the parent map.
 
2269
 
 
2270
        This will remove the supplied revisions until no more children have 0
 
2271
        parents.
 
2272
 
 
2273
        :param parent_map: A dict of {child: [parents]}, this dictionary will
 
2274
            be modified in place.
 
2275
        :param tails_to_remove: A list of tips that should be removed,
 
2276
            this list will be consumed
 
2277
        :param child_map: The reverse dict of parent_map ({parent: [children]})
 
2278
            this dict will be modified
 
2279
        :return: None, parent_map will be modified in place.
 
2280
        """
 
2281
        while tails_to_remove:
 
2282
            next = tails_to_remove.pop()
 
2283
            parent_map.pop(next)
 
2284
            children = child_map.pop(next)
 
2285
            for child in children:
 
2286
                child_parents = parent_map[child]
 
2287
                child_parents.remove(next)
 
2288
                if len(child_parents) == 0:
 
2289
                    tails_to_remove.append(child)
 
2290
 
 
2291
    def _get_interesting_texts(self, parent_map):
 
2292
        """Return a dict of texts we are interested in.
 
2293
 
 
2294
        Note that the input is in key tuples, but the output is in plain
 
2295
        revision ids.
 
2296
 
 
2297
        :param parent_map: The output from _find_recursive_lcas
 
2298
        :return: A dict of {'revision_id':lines} as returned by
 
2299
            _PlanMergeBase.get_lines()
 
2300
        """
 
2301
        all_revision_keys = set(parent_map)
 
2302
        all_revision_keys.add(self.a_key)
 
2303
        all_revision_keys.add(self.b_key)
 
2304
 
 
2305
        # Everything else is in 'keys' but get_lines is in 'revision_ids'
 
2306
        all_texts = self.get_lines([k[-1] for k in all_revision_keys])
 
2307
        return all_texts
 
2308
 
 
2309
    def _build_weave(self):
 
2310
        from .bzr import weave
 
2311
        self._weave = weave.Weave(weave_name='in_memory_weave',
 
2312
                                  allow_reserved=True)
 
2313
        parent_map = self._find_recursive_lcas()
 
2314
 
 
2315
        all_texts = self._get_interesting_texts(parent_map)
 
2316
 
 
2317
        # Note: Unfortunately, the order given by topo_sort will effect the
 
2318
        # ordering resolution in the output. Specifically, if you add A then B,
 
2319
        # then in the output text A lines will show up before B lines. And, of
 
2320
        # course, topo_sort doesn't guarantee any real ordering.
 
2321
        # So we use merge_sort, and add a fake node on the tip.
 
2322
        # This ensures that left-hand parents will always be inserted into the
 
2323
        # weave before right-hand parents.
 
2324
        tip_key = self._key_prefix + (_mod_revision.CURRENT_REVISION,)
 
2325
        parent_map[tip_key] = (self.a_key, self.b_key)
 
2326
 
 
2327
        for seq_num, key, depth, eom in reversed(tsort.merge_sort(parent_map,
 
2328
                                                                  tip_key)):
 
2329
            if key == tip_key:
 
2330
                continue
 
2331
        # for key in tsort.topo_sort(parent_map):
 
2332
            parent_keys = parent_map[key]
 
2333
            revision_id = key[-1]
 
2334
            parent_ids = [k[-1] for k in parent_keys]
 
2335
            self._weave.add_lines(revision_id, parent_ids,
 
2336
                                  all_texts[revision_id])
 
2337
 
 
2338
    def plan_merge(self):
 
2339
        """Generate a 'plan' for merging the two revisions.
 
2340
 
 
2341
        This involves comparing their texts and determining the cause of
 
2342
        differences.  If text A has a line and text B does not, then either the
 
2343
        line was added to text A, or it was deleted from B.  Once the causes
 
2344
        are combined, they are written out in the format described in
 
2345
        VersionedFile.plan_merge
 
2346
        """
 
2347
        if self._head_key is not None:  # There was a single head
 
2348
            if self._head_key == self.a_key:
 
2349
                plan = 'new-a'
 
2350
            else:
 
2351
                if self._head_key != self.b_key:
 
2352
                    raise AssertionError('There was an invalid head: %s != %s'
 
2353
                                         % (self.b_key, self._head_key))
 
2354
                plan = 'new-b'
 
2355
            head_rev = self._head_key[-1]
 
2356
            lines = self.get_lines([head_rev])[head_rev]
 
2357
            return ((plan, line) for line in lines)
 
2358
        return self._weave.plan_merge(self.a_rev, self.b_rev)
 
2359
 
 
2360
 
 
2361
class _PlanLCAMerge(_PlanMergeBase):
 
2362
    """
 
2363
    This merge algorithm differs from _PlanMerge in that:
 
2364
 
 
2365
    1. comparisons are done against LCAs only
 
2366
    2. cases where a contested line is new versus one LCA but old versus
 
2367
       another are marked as conflicts, by emitting the line as conflicted-a
 
2368
       or conflicted-b.
 
2369
 
 
2370
    This is faster, and hopefully produces more useful output.
 
2371
    """
 
2372
 
 
2373
    def __init__(self, a_rev, b_rev, vf, key_prefix, graph):
 
2374
        _PlanMergeBase.__init__(self, a_rev, b_rev, vf, key_prefix)
 
2375
        lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
 
2376
        self.lcas = set()
 
2377
        for lca in lcas:
 
2378
            if lca == _mod_revision.NULL_REVISION:
 
2379
                self.lcas.add(lca)
 
2380
            else:
 
2381
                self.lcas.add(lca[-1])
 
2382
        for lca in self.lcas:
 
2383
            if _mod_revision.is_null(lca):
 
2384
                lca_lines = []
 
2385
            else:
 
2386
                lca_lines = self.get_lines([lca])[lca]
 
2387
            matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_a,
 
2388
                                                           lca_lines)
 
2389
            blocks = list(matcher.get_matching_blocks())
 
2390
            self._cached_matching_blocks[(a_rev, lca)] = blocks
 
2391
            matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_b,
 
2392
                                                           lca_lines)
 
2393
            blocks = list(matcher.get_matching_blocks())
 
2394
            self._cached_matching_blocks[(b_rev, lca)] = blocks
 
2395
 
 
2396
    def _determine_status(self, revision_id, unique_line_numbers):
 
2397
        """Determines the status unique lines versus all lcas.
 
2398
 
 
2399
        Basically, determines why the line is unique to this revision.
 
2400
 
 
2401
        A line may be determined new, killed, or both.
 
2402
 
 
2403
        If a line is determined new, that means it was not present in at least
 
2404
        one LCA, and is not present in the other merge revision.
 
2405
 
 
2406
        If a line is determined killed, that means the line was present in
 
2407
        at least one LCA.
 
2408
 
 
2409
        If a line is killed and new, this indicates that the two merge
 
2410
        revisions contain differing conflict resolutions.
 
2411
 
 
2412
        :param revision_id: The id of the revision in which the lines are
 
2413
            unique
 
2414
        :param unique_line_numbers: The line numbers of unique lines.
 
2415
        :return: a tuple of (new_this, killed_other)
 
2416
        """
 
2417
        new = set()
 
2418
        killed = set()
 
2419
        unique_line_numbers = set(unique_line_numbers)
 
2420
        for lca in self.lcas:
 
2421
            blocks = self._get_matching_blocks(revision_id, lca)
 
2422
            unique_vs_lca, _ignored = self._unique_lines(blocks)
 
2423
            new.update(unique_line_numbers.intersection(unique_vs_lca))
 
2424
            killed.update(unique_line_numbers.difference(unique_vs_lca))
 
2425
        return new, killed