/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 bzrlib/merge.py

(jelmer) Convert bzrlib.smtp_connection to use config stacks. (Jelmer
 Vernooij)

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