/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

  • Committer: Andrew Bennetts
  • Date: 2011-06-09 07:38:32 UTC
  • mto: This revision was merged to the branch mainline in revision 5964.
  • Revision ID: andrew.bennetts@canonical.com-20110609073832-dt6oww033iexli4l
Fix thinko in wording regarding stacking invariants and revisions with multiple parents.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
import contextlib
 
17
import warnings
18
18
 
19
 
from .lazy_import import lazy_import
 
19
from bzrlib.lazy_import import lazy_import
20
20
lazy_import(globals(), """
21
 
import patiencediff
22
 
 
23
 
from breezy import (
 
21
from bzrlib import (
24
22
    branch as _mod_branch,
 
23
    cleanup,
25
24
    conflicts as _mod_conflicts,
26
25
    debug,
 
26
    generate_ids,
27
27
    graph as _mod_graph,
28
28
    merge3,
29
29
    osutils,
 
30
    patiencediff,
30
31
    revision as _mod_revision,
31
32
    textfile,
32
33
    trace,
 
34
    transform,
33
35
    tree as _mod_tree,
34
36
    tsort,
35
37
    ui,
 
38
    versionedfile,
36
39
    workingtree,
37
40
    )
38
 
from breezy.bzr import (
39
 
    generate_ids,
40
 
    versionedfile,
41
 
    )
42
 
from breezy.i18n import gettext
43
41
""")
44
 
from . import (
 
42
from bzrlib import (
45
43
    decorators,
46
44
    errors,
47
45
    hooks,
48
 
    registry,
49
 
    transform,
 
46
    )
 
47
from bzrlib.symbol_versioning import (
 
48
    deprecated_in,
 
49
    deprecated_method,
50
50
    )
51
51
# TODO: Report back as changes are merged in
52
52
 
53
53
 
54
 
def transform_tree(from_tree, to_tree, interesting_files=None):
55
 
    with from_tree.lock_tree_write():
56
 
        merge_inner(from_tree.branch, to_tree, from_tree,
57
 
                    ignore_zero=True, this_tree=from_tree,
58
 
                    interesting_files=interesting_files)
 
54
def transform_tree(from_tree, to_tree, interesting_ids=None):
 
55
    from_tree.lock_tree_write()
 
56
    operation = cleanup.OperationWithCleanups(merge_inner)
 
57
    operation.add_cleanup(from_tree.unlock)
 
58
    operation.run_simple(from_tree.branch, to_tree, from_tree,
 
59
        ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
59
60
 
60
61
 
61
62
class MergeHooks(hooks.Hooks):
62
63
 
63
64
    def __init__(self):
64
 
        hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
 
65
        hooks.Hooks.__init__(self, "bzrlib.merge", "Merger.hooks")
65
66
        self.add_hook('merge_file_content',
66
 
                      "Called with a breezy.merge.Merger object to create a per file "
67
 
                      "merge object when starting a merge. "
68
 
                      "Should return either None or a subclass of "
69
 
                      "``breezy.merge.AbstractPerFileMerger``. "
70
 
                      "Such objects will then be called per file "
71
 
                      "that needs to be merged (including when one "
72
 
                      "side has deleted the file and the other has changed it). "
73
 
                      "See the AbstractPerFileMerger API docs for details on how it is "
74
 
                      "used by merge.",
75
 
                      (2, 1))
76
 
        self.add_hook('pre_merge',
77
 
                      'Called before a merge. '
78
 
                      'Receives a Merger object as the single argument.',
79
 
                      (2, 5))
80
 
        self.add_hook('post_merge',
81
 
                      'Called after a merge. '
82
 
                      'Receives a Merger object as the single argument. '
83
 
                      'The return value is ignored.',
84
 
                      (2, 5))
 
67
            "Called with a bzrlib.merge.Merger object to create a per file "
 
68
            "merge object when starting a merge. "
 
69
            "Should return either None or a subclass of "
 
70
            "``bzrlib.merge.AbstractPerFileMerger``. "
 
71
            "Such objects will then be called per file "
 
72
            "that needs to be merged (including when one "
 
73
            "side has deleted the file and the other has changed it). "
 
74
            "See the AbstractPerFileMerger API docs for details on how it is "
 
75
            "used by merge.",
 
76
            (2, 1))
85
77
 
86
78
 
87
79
class AbstractPerFileMerger(object):
88
 
    """PerFileMerger objects are used by plugins extending merge for breezy.
89
 
 
90
 
    See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
91
 
 
 
80
    """PerFileMerger objects are used by plugins extending merge for bzrlib.
 
81
 
 
82
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
83
    
92
84
    :ivar merger: The Merge3Merger performing the merge.
93
85
    """
94
86
 
98
90
 
99
91
    def merge_contents(self, merge_params):
100
92
        """Attempt to merge the contents of a single file.
101
 
 
102
 
        :param merge_params: A breezy.merge.MergeFileHookParams
 
93
        
 
94
        :param merge_params: A bzrlib.merge.MergeHookParams
103
95
        :return: A tuple of (status, chunks), where status is one of
104
96
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
105
97
            is 'success' or 'conflicted', then chunks should be an iterable of
124
116
        """
125
117
        raise NotImplementedError(self.file_matches)
126
118
 
 
119
    def get_filename(self, params, tree):
 
120
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
 
121
        self.merger.this_tree) and a MergeHookParams.
 
122
        """
 
123
        return osutils.basename(tree.id2path(params.file_id))
 
124
 
 
125
    def get_filepath(self, params, tree):
 
126
        """Calculate the path to the file in a tree.
 
127
 
 
128
        :param params: A MergeHookParams describing the file to merge
 
129
        :param tree: a Tree, e.g. self.merger.this_tree.
 
130
        """
 
131
        return tree.id2path(params.file_id)
 
132
 
127
133
    def merge_contents(self, params):
128
134
        """Merge the contents of a single file."""
129
135
        # Check whether this custom merge logic should be used.
132
138
            params.winner == 'other' or
133
139
            # THIS and OTHER aren't both files.
134
140
            not params.is_file_merge() or
135
 
            # The filename doesn't match
136
 
                not self.file_matches(params)):
 
141
            # The filename doesn't match *.xml
 
142
            not self.file_matches(params)):
137
143
            return 'not_applicable', None
138
144
        return self.merge_matching(params)
139
145
 
153
159
    This is a base class for concrete custom file merging logic. Concrete
154
160
    classes should implement ``merge_text``.
155
161
 
156
 
    See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
157
 
 
 
162
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
163
    
158
164
    :ivar affected_files: The configured file paths to merge.
159
165
 
160
166
    :cvar name_prefix: The prefix to use when looking up configuration
161
167
        details. <name_prefix>_merge_files describes the files targeted by the
162
168
        hook for example.
163
 
 
 
169
        
164
170
    :cvar default_files: The default file paths to merge when no configuration
165
171
        is present.
166
172
    """
187
193
            config = self.merger.this_branch.get_config()
188
194
            # Until bzr provides a better policy for caching the config, we
189
195
            # just add the part we're interested in to the params to avoid
190
 
            # reading the config files repeatedly (breezy.conf, location.conf,
 
196
            # reading the config files repeatedly (bazaar.conf, location.conf,
191
197
            # branch.conf).
192
198
            config_key = self.name_prefix + '_merge_files'
193
199
            affected_files = config.get_user_option_as_list(config_key)
196
202
                affected_files = self.default_files
197
203
            self.affected_files = affected_files
198
204
        if affected_files:
199
 
            filepath = params.this_path
 
205
            filepath = self.get_filepath(params, self.merger.this_tree)
200
206
            if filepath in affected_files:
201
207
                return True
202
208
        return False
209
215
 
210
216
        This is called after checking that the merge should be performed in
211
217
        merge_contents, and it should behave as per
212
 
        ``breezy.merge.AbstractPerFileMerger.merge_contents``.
 
218
        ``bzrlib.merge.AbstractPerFileMerger.merge_contents``.
213
219
        """
214
220
        raise NotImplementedError(self.merge_text)
215
221
 
216
222
 
217
 
class MergeFileHookParams(object):
 
223
class MergeHookParams(object):
218
224
    """Object holding parameters passed to merge_file_content hooks.
219
225
 
220
226
    There are some fields hooks can access:
221
227
 
222
 
    :ivar base_path: Path in base tree
223
 
    :ivar other_path: Path in other tree
224
 
    :ivar this_path: Path in this tree
 
228
    :ivar file_id: the file ID of the file being merged
225
229
    :ivar trans_id: the transform ID for the merge of this file
226
 
    :ivar this_kind: kind of file in 'this' tree
227
 
    :ivar other_kind: kind of file in 'other' tree
 
230
    :ivar this_kind: kind of file_id in 'this' tree
 
231
    :ivar other_kind: kind of file_id in 'other' tree
228
232
    :ivar winner: one of 'this', 'other', 'conflict'
229
233
    """
230
234
 
231
 
    def __init__(self, merger, paths, trans_id, this_kind, other_kind,
232
 
                 winner):
 
235
    def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
 
236
            winner):
233
237
        self._merger = merger
234
 
        self.paths = paths
235
 
        self.base_path, self.other_path, self.this_path = paths
 
238
        self.file_id = file_id
236
239
        self.trans_id = trans_id
237
240
        self.this_kind = this_kind
238
241
        self.other_kind = other_kind
245
248
    @decorators.cachedproperty
246
249
    def base_lines(self):
247
250
        """The lines of the 'base' version of the file."""
248
 
        return self._merger.get_lines(self._merger.base_tree, self.base_path)
 
251
        return self._merger.get_lines(self._merger.base_tree, self.file_id)
249
252
 
250
253
    @decorators.cachedproperty
251
254
    def this_lines(self):
252
255
        """The lines of the 'this' version of the file."""
253
 
        return self._merger.get_lines(self._merger.this_tree, self.this_path)
 
256
        return self._merger.get_lines(self._merger.this_tree, self.file_id)
254
257
 
255
258
    @decorators.cachedproperty
256
259
    def other_lines(self):
257
260
        """The lines of the 'other' version of the file."""
258
 
        return self._merger.get_lines(self._merger.other_tree, self.other_path)
 
261
        return self._merger.get_lines(self._merger.other_tree, self.file_id)
259
262
 
260
263
 
261
264
class Merger(object):
263
266
    hooks = MergeHooks()
264
267
 
265
268
    def __init__(self, this_branch, other_tree=None, base_tree=None,
266
 
                 this_tree=None, change_reporter=None,
 
269
                 this_tree=None, pb=None, change_reporter=None,
267
270
                 recurse='down', revision_graph=None):
268
271
        object.__init__(self)
269
272
        self.this_branch = this_branch
278
281
        self.base_tree = base_tree
279
282
        self.ignore_zero = False
280
283
        self.backup_files = False
 
284
        self.interesting_ids = None
281
285
        self.interesting_files = None
282
286
        self.show_base = False
283
287
        self.reprocess = False
 
288
        if pb is not None:
 
289
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
284
290
        self.pp = None
285
291
        self.recurse = recurse
286
292
        self.change_reporter = change_reporter
334
340
                                      _set_base_is_other_ancestor)
335
341
 
336
342
    @staticmethod
337
 
    def from_uncommitted(tree, other_tree, base_tree=None):
 
343
    def from_uncommitted(tree, other_tree, pb=None, base_tree=None):
338
344
        """Return a Merger for uncommitted changes in other_tree.
339
345
 
340
346
        :param tree: The tree to merge into
341
347
        :param other_tree: The tree to get uncommitted changes from
 
348
        :param pb: A progress indicator
342
349
        :param base_tree: The basis to use for the merge.  If unspecified,
343
350
            other_tree.basis_tree() will be used.
344
351
        """
345
352
        if base_tree is None:
346
353
            base_tree = other_tree.basis_tree()
347
 
        merger = Merger(tree.branch, other_tree, base_tree, tree)
 
354
        merger = Merger(tree.branch, other_tree, base_tree, tree, pb)
348
355
        merger.base_rev_id = merger.base_tree.get_revision_id()
349
356
        merger.other_rev_id = None
350
357
        merger.other_basis = merger.base_rev_id
351
358
        return merger
352
359
 
353
360
    @classmethod
354
 
    def from_mergeable(klass, tree, mergeable):
 
361
    def from_mergeable(klass, tree, mergeable, pb):
355
362
        """Return a Merger for a bundle or merge directive.
356
363
 
357
364
        :param tree: The tree to merge changes into
358
365
        :param mergeable: A merge directive or bundle
 
366
        :param pb: A progress indicator
359
367
        """
360
368
        mergeable.install_revisions(tree.branch.repository)
361
369
        base_revision_id, other_revision_id, verified =\
364
372
        if base_revision_id is not None:
365
373
            if (base_revision_id != _mod_revision.NULL_REVISION and
366
374
                revision_graph.is_ancestor(
367
 
                    base_revision_id, tree.branch.last_revision())):
 
375
                base_revision_id, tree.branch.last_revision())):
368
376
                base_revision_id = None
369
377
            else:
370
378
                trace.warning('Performing cherrypick')
371
 
        merger = klass.from_revision_ids(tree, other_revision_id,
372
 
                                         base_revision_id, revision_graph=revision_graph)
 
379
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
 
380
                                         base_revision_id, revision_graph=
 
381
                                         revision_graph)
373
382
        return merger, verified
374
383
 
375
384
    @staticmethod
376
 
    def from_revision_ids(tree, other, base=None, other_branch=None,
 
385
    def from_revision_ids(pb, tree, other, base=None, other_branch=None,
377
386
                          base_branch=None, revision_graph=None,
378
387
                          tree_branch=None):
379
388
        """Return a Merger for revision-ids.
380
389
 
 
390
        :param pb: A progress indicator
381
391
        :param tree: The tree to merge changes into
382
392
        :param other: The revision-id to use as OTHER
383
393
        :param base: The revision-id to use as BASE.  If not specified, will
393
403
        """
394
404
        if tree_branch is None:
395
405
            tree_branch = tree.branch
396
 
        merger = Merger(tree_branch, this_tree=tree,
 
406
        merger = Merger(tree_branch, this_tree=tree, pb=pb,
397
407
                        revision_graph=revision_graph)
398
408
        if other_branch is None:
399
409
            other_branch = tree.branch
431
441
        revision_id = _mod_revision.ensure_null(revision_id)
432
442
        return branch, self.revision_tree(revision_id, branch)
433
443
 
 
444
    @deprecated_method(deprecated_in((2, 1, 0)))
 
445
    def ensure_revision_trees(self):
 
446
        if self.this_revision_tree is None:
 
447
            self.this_basis_tree = self.revision_tree(self.this_basis)
 
448
            if self.this_basis == self.this_rev_id:
 
449
                self.this_revision_tree = self.this_basis_tree
 
450
 
 
451
        if self.other_rev_id is None:
 
452
            other_basis_tree = self.revision_tree(self.other_basis)
 
453
            if other_basis_tree.has_changes(self.other_tree):
 
454
                raise errors.WorkingTreeNotRevision(self.this_tree)
 
455
            other_rev_id = self.other_basis
 
456
            self.other_tree = other_basis_tree
 
457
 
 
458
    @deprecated_method(deprecated_in((2, 1, 0)))
 
459
    def file_revisions(self, file_id):
 
460
        self.ensure_revision_trees()
 
461
        if self.this_rev_id is None:
 
462
            if self.this_basis_tree.get_file_sha1(file_id) != \
 
463
                self.this_tree.get_file_sha1(file_id):
 
464
                raise errors.WorkingTreeNotRevision(self.this_tree)
 
465
 
 
466
        trees = (self.this_basis_tree, self.other_tree)
 
467
        return [tree.get_file_revision(file_id) for tree in trees]
 
468
 
 
469
    @deprecated_method(deprecated_in((2, 1, 0)))
 
470
    def check_basis(self, check_clean, require_commits=True):
 
471
        if self.this_basis is None and require_commits is True:
 
472
            raise errors.BzrCommandError(
 
473
                "This branch has no commits."
 
474
                " (perhaps you would prefer 'bzr pull')")
 
475
        if check_clean:
 
476
            self.compare_basis()
 
477
            if self.this_basis != self.this_rev_id:
 
478
                raise errors.UncommittedChanges(self.this_tree)
 
479
 
 
480
    @deprecated_method(deprecated_in((2, 1, 0)))
 
481
    def compare_basis(self):
 
482
        try:
 
483
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
484
        except errors.NoSuchRevision:
 
485
            basis_tree = self.this_tree.basis_tree()
 
486
        if not self.this_tree.has_changes(basis_tree):
 
487
            self.this_rev_id = self.this_basis
 
488
 
434
489
    def set_interesting_files(self, file_list):
435
490
        self.interesting_files = file_list
436
491
 
437
492
    def set_pending(self):
438
493
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
439
 
                or self.other_rev_id is None):
 
494
            or self.other_rev_id is None):
440
495
            return
441
496
        self._add_parent()
442
497
 
443
498
    def _add_parent(self):
444
499
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
445
500
        new_parent_trees = []
446
 
        with contextlib.ExitStack() as stack:
447
 
            for revision_id in new_parents:
448
 
                try:
449
 
                    tree = self.revision_tree(revision_id)
450
 
                except errors.NoSuchRevision:
451
 
                    tree = None
452
 
                else:
453
 
                    stack.enter_context(tree.lock_read())
454
 
                new_parent_trees.append((revision_id, tree))
455
 
            self.this_tree.set_parent_trees(new_parent_trees, allow_leftmost_as_ghost=True)
 
501
        operation = cleanup.OperationWithCleanups(
 
502
            self.this_tree.set_parent_trees)
 
503
        for revision_id in new_parents:
 
504
            try:
 
505
                tree = self.revision_tree(revision_id)
 
506
            except errors.NoSuchRevision:
 
507
                tree = None
 
508
            else:
 
509
                tree.lock_read()
 
510
                operation.add_cleanup(tree.unlock)
 
511
            new_parent_trees.append((revision_id, tree))
 
512
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
456
513
 
457
514
    def set_other(self, other_revision, possible_transports=None):
458
515
        """Set the revision and tree to merge from.
479
536
                raise errors.NoCommits(self.other_branch)
480
537
        if self.other_rev_id is not None:
481
538
            self._cached_trees[self.other_rev_id] = self.other_tree
482
 
        self._maybe_fetch(self.other_branch,
483
 
                          self.this_branch, self.other_basis)
 
539
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
484
540
 
485
541
    def set_other_revision(self, revision_id, other_branch):
486
542
        """Set 'other' based on a branch and revision id
523
579
                self.base_rev_id = _mod_revision.NULL_REVISION
524
580
            elif len(lcas) == 1:
525
581
                self.base_rev_id = list(lcas)[0]
526
 
            else:  # len(lcas) > 1
 
582
            else: # len(lcas) > 1
527
583
                self._is_criss_cross = True
528
584
                if len(lcas) > 2:
529
585
                    # find_unique_lca can only handle 2 nodes, so we have to
531
587
                    # the graph again, but better than re-implementing
532
588
                    # find_unique_lca.
533
589
                    self.base_rev_id = self.revision_graph.find_unique_lca(
534
 
                        revisions[0], revisions[1])
 
590
                                            revisions[0], revisions[1])
535
591
                else:
536
592
                    self.base_rev_id = self.revision_graph.find_unique_lca(
537
 
                        *lcas)
538
 
                sorted_lca_keys = self.revision_graph.find_merge_order(
 
593
                                            *lcas)
 
594
                sorted_lca_keys = self.revision_graph.find_merge_order(                
539
595
                    revisions[0], lcas)
540
596
                if self.base_rev_id == _mod_revision.NULL_REVISION:
541
597
                    self.base_rev_id = sorted_lca_keys[0]
542
 
 
 
598
                
543
599
            if self.base_rev_id == _mod_revision.NULL_REVISION:
544
600
                raise errors.UnrelatedBranches()
545
601
            if self._is_criss_cross:
548
604
                trace.mutter('Criss-cross lcas: %r' % lcas)
549
605
                if self.base_rev_id in lcas:
550
606
                    trace.mutter('Unable to find unique lca. '
551
 
                                 'Fallback %r as best option.'
552
 
                                 % self.base_rev_id)
 
607
                                 'Fallback %r as best option.' % self.base_rev_id)
553
608
                interesting_revision_ids = set(lcas)
554
609
                interesting_revision_ids.add(self.base_rev_id)
555
610
                interesting_trees = dict((t.get_revision_id(), t)
556
 
                                         for t in self.this_branch.repository.revision_trees(
557
 
                    interesting_revision_ids))
 
611
                    for t in self.this_branch.repository.revision_trees(
 
612
                        interesting_revision_ids))
558
613
                self._cached_trees.update(interesting_trees)
559
614
                if self.base_rev_id in lcas:
560
615
                    self.base_tree = interesting_trees[self.base_rev_id]
588
643
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
589
644
 
590
645
    def make_merger(self):
591
 
        kwargs = {'working_tree': self.this_tree, 'this_tree': self.this_tree,
 
646
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
592
647
                  'other_tree': self.other_tree,
 
648
                  'interesting_ids': self.interesting_ids,
593
649
                  'interesting_files': self.interesting_files,
594
650
                  'this_branch': self.this_branch,
595
 
                  'other_branch': self.other_branch,
596
651
                  'do_merge': False}
597
652
        if self.merge_type.requires_base:
598
653
            kwargs['base_tree'] = self.base_tree
608
663
            raise errors.BzrError("Showing base is not supported for this"
609
664
                                  " merge type. %s" % self.merge_type)
610
665
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
611
 
                and not self.base_is_other_ancestor):
 
666
            and not self.base_is_other_ancestor):
612
667
            raise errors.CannotReverseCherrypick()
613
668
        if self.merge_type.supports_cherrypick:
614
669
            kwargs['cherrypick'] = (not self.base_is_ancestor or
616
671
        if self._is_criss_cross and getattr(self.merge_type,
617
672
                                            'supports_lca_trees', False):
618
673
            kwargs['lca_trees'] = self._lca_trees
619
 
        return self.merge_type(change_reporter=self.change_reporter,
 
674
        return self.merge_type(pb=None,
 
675
                               change_reporter=self.change_reporter,
620
676
                               **kwargs)
621
677
 
622
678
    def _do_merge_to(self):
623
679
        merge = self.make_merger()
624
680
        if self.other_branch is not None:
625
681
            self.other_branch.update_references(self.this_branch)
626
 
        for hook in Merger.hooks['pre_merge']:
627
 
            hook(merge)
628
682
        merge.do_merge()
629
 
        for hook in Merger.hooks['post_merge']:
630
 
            hook(merge)
631
683
        if self.recurse == 'down':
632
 
            for relpath in self.this_tree.iter_references():
633
 
                sub_tree = self.this_tree.get_nested_tree(relpath)
 
684
            for relpath, file_id in self.this_tree.iter_references():
 
685
                sub_tree = self.this_tree.get_nested_tree(file_id, relpath)
634
686
                other_revision = self.other_tree.get_reference_revision(
635
 
                    relpath)
636
 
                if other_revision == sub_tree.last_revision():
 
687
                    file_id, relpath)
 
688
                if  other_revision == sub_tree.last_revision():
637
689
                    continue
638
690
                sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
639
691
                sub_merge.merge_type = self.merge_type
640
 
                other_branch = self.other_tree.reference_parent(relpath)
 
692
                other_branch = self.other_branch.reference_parent(file_id, relpath)
641
693
                sub_merge.set_other_revision(other_revision, other_branch)
642
 
                base_tree_path = _mod_tree.find_previous_path(
643
 
                    self.this_tree, self.base_tree, relpath)
644
 
                base_revision = self.base_tree.get_reference_revision(
645
 
                    base_tree_path)
 
694
                base_revision = self.base_tree.get_reference_revision(file_id)
646
695
                sub_merge.base_tree = \
647
696
                    sub_tree.branch.repository.revision_tree(base_revision)
648
697
                sub_merge.base_rev_id = base_revision
650
699
        return merge
651
700
 
652
701
    def do_merge(self):
653
 
        with contextlib.ExitStack() as stack:
654
 
            stack.enter_context(self.this_tree.lock_tree_write())
655
 
            if self.base_tree is not None:
656
 
                stack.enter_context(self.base_tree.lock_read())
657
 
            if self.other_tree is not None:
658
 
                stack.enter_context(self.other_tree.lock_read())
659
 
            merge = self._do_merge_to()
 
702
        operation = cleanup.OperationWithCleanups(self._do_merge_to)
 
703
        self.this_tree.lock_tree_write()
 
704
        operation.add_cleanup(self.this_tree.unlock)
 
705
        if self.base_tree is not None:
 
706
            self.base_tree.lock_read()
 
707
            operation.add_cleanup(self.base_tree.unlock)
 
708
        if self.other_tree is not None:
 
709
            self.other_tree.lock_read()
 
710
            operation.add_cleanup(self.other_tree.unlock)
 
711
        merge = operation.run_simple()
660
712
        if len(merge.cooked_conflicts) == 0:
661
713
            if not self.ignore_zero and not trace.is_quiet():
662
 
                trace.note(gettext("All changes applied successfully."))
 
714
                trace.note("All changes applied successfully.")
663
715
        else:
664
 
            trace.note(gettext("%d conflicts encountered.")
 
716
            trace.note("%d conflicts encountered."
665
717
                       % len(merge.cooked_conflicts))
666
718
 
667
719
        return len(merge.cooked_conflicts)
681
733
    symlink_target = None
682
734
    text_sha1 = None
683
735
 
684
 
    def is_unmodified(self, other):
685
 
        return other is self
686
 
 
687
 
 
688
736
_none_entry = _InventoryNoneEntry()
689
737
 
690
738
 
698
746
    supports_reverse_cherrypick = True
699
747
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
700
748
    supports_lca_trees = True
701
 
    requires_file_merge_plan = False
702
749
 
703
750
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
704
 
                 reprocess=False, show_base=False,
705
 
                 change_reporter=None, interesting_files=None, do_merge=True,
706
 
                 cherrypick=False, lca_trees=None, this_branch=None,
707
 
                 other_branch=None):
 
751
                 interesting_ids=None, reprocess=False, show_base=False,
 
752
                 pb=None, pp=None, change_reporter=None,
 
753
                 interesting_files=None, do_merge=True,
 
754
                 cherrypick=False, lca_trees=None, this_branch=None):
708
755
        """Initialize the merger object and perform the merge.
709
756
 
710
757
        :param working_tree: The working tree to apply the merge to
713
760
        :param other_tree: The other tree to merge changes from
714
761
        :param this_branch: The branch associated with this_tree.  Defaults to
715
762
            this_tree.branch if not supplied.
716
 
        :param other_branch: The branch associated with other_tree, if any.
 
763
        :param interesting_ids: The file_ids of files that should be
 
764
            participate in the merge.  May not be combined with
 
765
            interesting_files.
717
766
        :param: reprocess If True, perform conflict-reduction processing.
718
767
        :param show_base: If True, show the base revision in text conflicts.
719
768
            (incompatible with reprocess)
 
769
        :param pb: ignored
 
770
        :param pp: A ProgressPhase object
720
771
        :param change_reporter: An object that should report changes made
721
772
        :param interesting_files: The tree-relative paths of files that should
722
773
            participate in the merge.  If these paths refer to directories,
723
 
            the contents of those directories will also be included.  If not
724
 
            specified, all files may participate in the
 
774
            the contents of those directories will also be included.  May not
 
775
            be combined with interesting_ids.  If neither interesting_files nor
 
776
            interesting_ids is specified, all files may participate in the
725
777
            merge.
726
778
        :param lca_trees: Can be set to a dictionary of {revision_id:rev_tree}
727
779
            if the ancestry was found to include a criss-cross merge.
728
780
            Otherwise should be None.
729
781
        """
730
782
        object.__init__(self)
 
783
        if interesting_files is not None and interesting_ids is not None:
 
784
            raise ValueError(
 
785
                'specify either interesting_ids or interesting_files')
731
786
        if this_branch is None:
732
787
            this_branch = this_tree.branch
 
788
        self.interesting_ids = interesting_ids
733
789
        self.interesting_files = interesting_files
734
 
        self.working_tree = working_tree
735
 
        self.this_tree = this_tree
 
790
        self.this_tree = working_tree
736
791
        self.base_tree = base_tree
737
792
        self.other_tree = other_tree
738
793
        self.this_branch = this_branch
739
 
        self.other_branch = other_branch
740
794
        self._raw_conflicts = []
741
795
        self.cooked_conflicts = []
742
796
        self.reprocess = reprocess
751
805
        self.cherrypick = cherrypick
752
806
        if do_merge:
753
807
            self.do_merge()
 
808
        if pp is not None:
 
809
            warnings.warn("pp argument to Merge3Merger is deprecated")
 
810
        if pb is not None:
 
811
            warnings.warn("pb argument to Merge3Merger is deprecated")
754
812
 
755
813
    def do_merge(self):
756
 
        with contextlib.ExitStack() as stack:
757
 
            stack.enter_context(self.working_tree.lock_tree_write())
758
 
            stack.enter_context(self.this_tree.lock_read())
759
 
            stack.enter_context(self.base_tree.lock_read())
760
 
            stack.enter_context(self.other_tree.lock_read())
761
 
            self.tt = self.working_tree.transform()
762
 
            stack.enter_context(self.tt)
763
 
            self._compute_transform()
764
 
            results = self.tt.apply(no_conflicts=True)
765
 
            self.write_modified(results)
766
 
            try:
767
 
                self.working_tree.add_conflicts(self.cooked_conflicts)
768
 
            except errors.UnsupportedOperation:
769
 
                pass
 
814
        operation = cleanup.OperationWithCleanups(self._do_merge)
 
815
        self.this_tree.lock_tree_write()
 
816
        operation.add_cleanup(self.this_tree.unlock)
 
817
        self.base_tree.lock_read()
 
818
        operation.add_cleanup(self.base_tree.unlock)
 
819
        self.other_tree.lock_read()
 
820
        operation.add_cleanup(self.other_tree.unlock)
 
821
        operation.run()
 
822
 
 
823
    def _do_merge(self, operation):
 
824
        self.tt = transform.TreeTransform(self.this_tree, None)
 
825
        operation.add_cleanup(self.tt.finalize)
 
826
        self._compute_transform()
 
827
        results = self.tt.apply(no_conflicts=True)
 
828
        self.write_modified(results)
 
829
        try:
 
830
            self.this_tree.add_conflicts(self.cooked_conflicts)
 
831
        except errors.UnsupportedOperation:
 
832
            pass
770
833
 
771
834
    def make_preview_transform(self):
772
 
        with self.base_tree.lock_read(), self.other_tree.lock_read():
773
 
            self.tt = self.working_tree.preview_transform()
774
 
            self._compute_transform()
775
 
            return self.tt
 
835
        operation = cleanup.OperationWithCleanups(self._make_preview_transform)
 
836
        self.base_tree.lock_read()
 
837
        operation.add_cleanup(self.base_tree.unlock)
 
838
        self.other_tree.lock_read()
 
839
        operation.add_cleanup(self.other_tree.unlock)
 
840
        return operation.run_simple()
 
841
 
 
842
    def _make_preview_transform(self):
 
843
        self.tt = transform.TransformPreview(self.this_tree)
 
844
        self._compute_transform()
 
845
        return self.tt
776
846
 
777
847
    def _compute_transform(self):
778
848
        if self._lca_trees is None:
779
 
            entries = list(self._entries3())
 
849
            entries = self._entries3()
780
850
            resolver = self._three_way
781
851
        else:
782
 
            entries = list(self._entries_lca())
 
852
            entries = self._entries_lca()
783
853
            resolver = self._lca_multi_way
784
 
        # Prepare merge hooks
785
 
        factories = Merger.hooks['merge_file_content']
786
 
        # One hook for each registered one plus our default merger
787
 
        hooks = [factory(self) for factory in factories] + [self]
788
 
        self.active_hooks = [hook for hook in hooks if hook is not None]
789
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
790
 
            for num, (file_id, changed, paths3, parents3, names3,
 
854
        child_pb = ui.ui_factory.nested_progress_bar()
 
855
        try:
 
856
            factories = Merger.hooks['merge_file_content']
 
857
            hooks = [factory(self) for factory in factories] + [self]
 
858
            self.active_hooks = [hook for hook in hooks if hook is not None]
 
859
            for num, (file_id, changed, parents3, names3,
791
860
                      executable3) in enumerate(entries):
792
 
                trans_id = self.tt.trans_id_file_id(file_id)
793
 
                # Try merging each entry
794
 
                child_pb.update(gettext('Preparing file merge'),
795
 
                                num, len(entries))
796
 
                self._merge_names(trans_id, file_id, paths3, parents3,
797
 
                                  names3, resolver=resolver)
 
861
                child_pb.update('Preparing file merge', num, len(entries))
 
862
                self._merge_names(file_id, parents3, names3, resolver=resolver)
798
863
                if changed:
799
 
                    file_status = self._do_merge_contents(paths3, trans_id, file_id)
 
864
                    file_status = self._do_merge_contents(file_id)
800
865
                else:
801
866
                    file_status = 'unmodified'
802
 
                self._merge_executable(paths3, trans_id, executable3,
803
 
                                       file_status, resolver=resolver)
804
 
        self.tt.fixup_new_roots()
 
867
                self._merge_executable(file_id,
 
868
                    executable3, file_status, resolver=resolver)
 
869
        finally:
 
870
            child_pb.finished()
 
871
        self.fix_root()
805
872
        self._finish_computing_transform()
806
873
 
807
874
    def _finish_computing_transform(self):
809
876
 
810
877
        This is the second half of _compute_transform.
811
878
        """
812
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
813
 
            fs_conflicts = transform.resolve_conflicts(
814
 
                self.tt, child_pb,
 
879
        child_pb = ui.ui_factory.nested_progress_bar()
 
880
        try:
 
881
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
815
882
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
 
883
        finally:
 
884
            child_pb.finished()
816
885
        if self.change_reporter is not None:
817
 
            from breezy import delta
 
886
            from bzrlib import delta
818
887
            delta.report_changes(
819
888
                self.tt.iter_changes(), self.change_reporter)
820
889
        self.cook_conflicts(fs_conflicts)
821
890
        for conflict in self.cooked_conflicts:
822
 
            trace.warning('%s', conflict.describe())
 
891
            trace.warning(unicode(conflict))
823
892
 
824
893
    def _entries3(self):
825
894
        """Gather data about files modified between three trees.
830
899
        other and this.  names3 is a tuple of names for base, other and this.
831
900
        executable3 is a tuple of execute-bit values for base, other and this.
832
901
        """
 
902
        result = []
833
903
        iterator = self.other_tree.iter_changes(self.base_tree,
834
 
                                                specific_files=self.interesting_files,
835
 
                                                extra_trees=[self.this_tree])
836
 
        this_interesting_files = self.this_tree.find_related_paths_across_trees(
837
 
            self.interesting_files, trees=[self.other_tree])
838
 
        this_entries = dict(self.this_tree.iter_entries_by_dir(
839
 
                            specific_files=this_interesting_files))
840
 
        for change in iterator:
841
 
            if change.path[0] is not None:
842
 
                this_path = _mod_tree.find_previous_path(
843
 
                    self.base_tree, self.this_tree, change.path[0])
844
 
            else:
845
 
                this_path = _mod_tree.find_previous_path(
846
 
                    self.other_tree, self.this_tree, change.path[1])
847
 
            this_entry = this_entries.get(this_path)
848
 
            if this_entry is not None:
849
 
                this_name = this_entry.name
850
 
                this_parent = this_entry.parent_id
851
 
                this_executable = this_entry.executable
 
904
                specific_files=self.interesting_files,
 
905
                extra_trees=[self.this_tree])
 
906
        this_entries = dict((e.file_id, e) for p, e in
 
907
                            self.this_tree.iter_entries_by_dir(
 
908
                            self.interesting_ids))
 
909
        for (file_id, paths, changed, versioned, parents, names, kind,
 
910
             executable) in iterator:
 
911
            if (self.interesting_ids is not None and
 
912
                file_id not in self.interesting_ids):
 
913
                continue
 
914
            entry = this_entries.get(file_id)
 
915
            if entry is not None:
 
916
                this_name = entry.name
 
917
                this_parent = entry.parent_id
 
918
                this_executable = entry.executable
852
919
            else:
853
920
                this_name = None
854
921
                this_parent = None
855
922
                this_executable = None
856
 
            parents3 = change.parent_id + (this_parent,)
857
 
            names3 = change.name + (this_name,)
858
 
            paths3 = change.path + (this_path, )
859
 
            executable3 = change.executable + (this_executable,)
860
 
            yield (
861
 
                (change.file_id, change.changed_content, paths3,
862
 
                 parents3, names3, executable3))
 
923
            parents3 = parents + (this_parent,)
 
924
            names3 = names + (this_name,)
 
925
            executable3 = executable + (this_executable,)
 
926
            result.append((file_id, changed, parents3, names3, executable3))
 
927
        return result
863
928
 
864
929
    def _entries_lca(self):
865
930
        """Gather data about files modified between multiple trees.
869
934
 
870
935
        For the multi-valued entries, the format will be (BASE, [lca1, lca2])
871
936
 
872
 
        :return: [(file_id, changed, paths, parents, names, executable)], where:
 
937
        :return: [(file_id, changed, parents, names, executable)], where:
873
938
 
874
939
            * file_id: Simple file_id of the entry
875
940
            * changed: Boolean, True if the kind or contents changed else False
876
 
            * paths: ((base, [path, in, lcas]), path_other, path_this)
877
941
            * parents: ((base, [parent_id, in, lcas]), parent_id_other,
878
942
                        parent_id_this)
879
943
            * names:   ((base, [name, in, lcas]), name_in_other, name_in_this)
884
948
            lookup_trees = [self.this_tree, self.base_tree]
885
949
            lookup_trees.extend(self._lca_trees)
886
950
            # I think we should include the lca trees as well
887
 
            interesting_files = self.other_tree.find_related_paths_across_trees(
888
 
                self.interesting_files, lookup_trees)
 
951
            interesting_ids = self.other_tree.paths2ids(self.interesting_files,
 
952
                                                        lookup_trees)
889
953
        else:
890
 
            interesting_files = None
891
 
        from .multiwalker import MultiWalker
892
 
        walker = MultiWalker(self.other_tree, self._lca_trees)
 
954
            interesting_ids = self.interesting_ids
 
955
        result = []
 
956
        walker = _mod_tree.MultiWalker(self.other_tree, self._lca_trees)
893
957
 
894
 
        for other_path, file_id, other_ie, lca_values in walker.iter_all():
 
958
        base_inventory = self.base_tree.inventory
 
959
        this_inventory = self.this_tree.inventory
 
960
        for path, file_id, other_ie, lca_values in walker.iter_all():
895
961
            # Is this modified at all from any of the other trees?
896
962
            if other_ie is None:
897
963
                other_ie = _none_entry
898
 
                other_path = None
899
 
            if interesting_files is not None and other_path not in interesting_files:
 
964
            if interesting_ids is not None and file_id not in interesting_ids:
900
965
                continue
901
966
 
902
967
            # If other_revision is found in any of the lcas, that means this
905
970
            # we know that the ancestry is linear, and that OTHER did not
906
971
            # modify anything
907
972
            # See doc/developers/lca_merge_resolution.txt for details
908
 
            # We can't use this shortcut when other_revision is None,
909
 
            # because it may be None because things are WorkingTrees, and
910
 
            # not because it is *actually* None.
911
 
            is_unmodified = False
912
 
            for lca_path, ie in lca_values:
913
 
                if ie is not None and other_ie.is_unmodified(ie):
914
 
                    is_unmodified = True
915
 
                    break
916
 
            if is_unmodified:
917
 
                continue
 
973
            other_revision = other_ie.revision
 
974
            if other_revision is not None:
 
975
                # We can't use this shortcut when other_revision is None,
 
976
                # because it may be None because things are WorkingTrees, and
 
977
                # not because it is *actually* None.
 
978
                is_unmodified = False
 
979
                for lca_path, ie in lca_values:
 
980
                    if ie is not None and ie.revision == other_revision:
 
981
                        is_unmodified = True
 
982
                        break
 
983
                if is_unmodified:
 
984
                    continue
918
985
 
919
986
            lca_entries = []
920
 
            lca_paths = []
921
987
            for lca_path, lca_ie in lca_values:
922
988
                if lca_ie is None:
923
989
                    lca_entries.append(_none_entry)
924
 
                    lca_paths.append(None)
925
990
                else:
926
991
                    lca_entries.append(lca_ie)
927
 
                    lca_paths.append(lca_path)
928
992
 
929
 
            try:
930
 
                base_path = self.base_tree.id2path(file_id)
931
 
            except errors.NoSuchId:
932
 
                base_path = None
 
993
            if file_id in base_inventory:
 
994
                base_ie = base_inventory[file_id]
 
995
            else:
933
996
                base_ie = _none_entry
 
997
 
 
998
            if file_id in this_inventory:
 
999
                this_ie = this_inventory[file_id]
934
1000
            else:
935
 
                base_ie = next(self.base_tree.iter_entries_by_dir(specific_files=[base_path]))[1]
936
 
 
937
 
            try:
938
 
                this_path = self.this_tree.id2path(file_id)
939
 
            except errors.NoSuchId:
940
1001
                this_ie = _none_entry
941
 
                this_path = None
942
 
            else:
943
 
                this_ie = next(self.this_tree.iter_entries_by_dir(specific_files=[this_path]))[1]
944
1002
 
945
1003
            lca_kinds = []
946
1004
            lca_parent_ids = []
971
1029
                        continue
972
1030
                    content_changed = False
973
1031
                elif other_ie.kind is None or other_ie.kind == 'file':
974
 
                    def get_sha1(tree, path):
975
 
                        if path is None:
976
 
                            return None
977
 
                        try:
978
 
                            return tree.get_file_sha1(path)
979
 
                        except errors.NoSuchFile:
980
 
                            return None
981
 
                    base_sha1 = get_sha1(self.base_tree, base_path)
982
 
                    lca_sha1s = [get_sha1(tree, lca_path)
983
 
                                 for tree, lca_path
984
 
                                 in zip(self._lca_trees, lca_paths)]
985
 
                    this_sha1 = get_sha1(self.this_tree, this_path)
986
 
                    other_sha1 = get_sha1(self.other_tree, other_path)
 
1032
                    def get_sha1(ie, tree):
 
1033
                        if ie.kind != 'file':
 
1034
                            return None
 
1035
                        return tree.get_file_sha1(file_id)
 
1036
                    base_sha1 = get_sha1(base_ie, self.base_tree)
 
1037
                    lca_sha1s = [get_sha1(ie, tree) for ie, tree
 
1038
                                 in zip(lca_entries, self._lca_trees)]
 
1039
                    this_sha1 = get_sha1(this_ie, self.this_tree)
 
1040
                    other_sha1 = get_sha1(other_ie, self.other_tree)
987
1041
                    sha1_winner = self._lca_multi_way(
988
1042
                        (base_sha1, lca_sha1s), other_sha1, this_sha1,
989
1043
                        allow_overriding_lca=False)
991
1045
                        (base_ie.executable, lca_executable),
992
1046
                        other_ie.executable, this_ie.executable)
993
1047
                    if (parent_id_winner == 'this' and name_winner == 'this'
994
 
                            and sha1_winner == 'this' and exec_winner == 'this'):
 
1048
                        and sha1_winner == 'this' and exec_winner == 'this'):
995
1049
                        # No kind, parent, name, exec, or content change for
996
1050
                        # OTHER, so this node is not considered interesting
997
1051
                        continue
998
1052
                    if sha1_winner == 'this':
999
1053
                        content_changed = False
1000
1054
                elif other_ie.kind == 'symlink':
1001
 
                    def get_target(ie, tree, path):
 
1055
                    def get_target(ie, tree):
1002
1056
                        if ie.kind != 'symlink':
1003
1057
                            return None
1004
 
                        return tree.get_symlink_target(path)
1005
 
                    base_target = get_target(base_ie, self.base_tree, base_path)
1006
 
                    lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
1007
 
                                   in zip(lca_entries, self._lca_trees, lca_paths)]
1008
 
                    this_target = get_target(
1009
 
                        this_ie, self.this_tree, this_path)
1010
 
                    other_target = get_target(
1011
 
                        other_ie, self.other_tree, other_path)
 
1058
                        return tree.get_symlink_target(file_id)
 
1059
                    base_target = get_target(base_ie, self.base_tree)
 
1060
                    lca_targets = [get_target(ie, tree) for ie, tree
 
1061
                                   in zip(lca_entries, self._lca_trees)]
 
1062
                    this_target = get_target(this_ie, self.this_tree)
 
1063
                    other_target = get_target(other_ie, self.other_tree)
1012
1064
                    target_winner = self._lca_multi_way(
1013
1065
                        (base_target, lca_targets),
1014
1066
                        other_target, this_target)
1015
1067
                    if (parent_id_winner == 'this' and name_winner == 'this'
1016
 
                            and target_winner == 'this'):
 
1068
                        and target_winner == 'this'):
1017
1069
                        # No kind, parent, name, or symlink target change
1018
1070
                        # not interesting
1019
1071
                        continue
1031
1083
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
1032
1084
 
1033
1085
            # If we have gotten this far, that means something has changed
1034
 
            yield (file_id, content_changed,
1035
 
                           ((base_path, lca_paths),
1036
 
                            other_path, this_path),
 
1086
            result.append((file_id, content_changed,
1037
1087
                           ((base_ie.parent_id, lca_parent_ids),
1038
1088
                            other_ie.parent_id, this_ie.parent_id),
1039
1089
                           ((base_ie.name, lca_names),
1040
1090
                            other_ie.name, this_ie.name),
1041
1091
                           ((base_ie.executable, lca_executable),
1042
1092
                            other_ie.executable, this_ie.executable)
1043
 
                           )
 
1093
                          ))
 
1094
        return result
 
1095
 
 
1096
    def fix_root(self):
 
1097
        if self.tt.final_kind(self.tt.root) is None:
 
1098
            self.tt.cancel_deletion(self.tt.root)
 
1099
        if self.tt.final_file_id(self.tt.root) is None:
 
1100
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
 
1101
                                 self.tt.root)
 
1102
        other_root_file_id = self.other_tree.get_root_id()
 
1103
        if other_root_file_id is None:
 
1104
            return
 
1105
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
1106
        if other_root == self.tt.root:
 
1107
            return
 
1108
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
 
1109
            # the other tree's root is a non-root in the current tree (as when
 
1110
            # a previously unrelated branch is merged into another)
 
1111
            return
 
1112
        if self.tt.final_kind(other_root) is not None:
 
1113
            other_root_is_present = True
 
1114
        else:
 
1115
            # other_root doesn't have a physical representation. We still need
 
1116
            # to move any references to the actual root of the tree.
 
1117
            other_root_is_present = False
 
1118
        # 'other_tree.inventory.root' is not present in this tree. We are
 
1119
        # calling adjust_path for children which *want* to be present with a
 
1120
        # correct place to go.
 
1121
        for _, child in self.other_tree.inventory.root.children.iteritems():
 
1122
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
1123
            if not other_root_is_present:
 
1124
                if self.tt.final_kind(trans_id) is not None:
 
1125
                    # The item exist in the final tree and has a defined place
 
1126
                    # to go already.
 
1127
                    continue
 
1128
            # Move the item into the root
 
1129
            try:
 
1130
                final_name = self.tt.final_name(trans_id)
 
1131
            except errors.NoFinalPath:
 
1132
                # This file is not present anymore, ignore it.
 
1133
                continue
 
1134
            self.tt.adjust_path(final_name, self.tt.root, trans_id)
 
1135
        if other_root_is_present:
 
1136
            self.tt.cancel_creation(other_root)
 
1137
            self.tt.cancel_versioning(other_root)
1044
1138
 
1045
1139
    def write_modified(self, results):
1046
 
        if not self.working_tree.supports_merge_modified():
1047
 
            return
1048
1140
        modified_hashes = {}
1049
1141
        for path in results.modified_paths:
1050
 
            wt_relpath = self.working_tree.relpath(path)
1051
 
            if not self.working_tree.is_versioned(wt_relpath):
 
1142
            file_id = self.this_tree.path2id(self.this_tree.relpath(path))
 
1143
            if file_id is None:
1052
1144
                continue
1053
 
            hash = self.working_tree.get_file_sha1(wt_relpath)
 
1145
            hash = self.this_tree.get_file_sha1(file_id)
1054
1146
            if hash is None:
1055
1147
                continue
1056
 
            modified_hashes[wt_relpath] = hash
1057
 
        self.working_tree.set_merge_modified(modified_hashes)
 
1148
            modified_hashes[file_id] = hash
 
1149
        self.this_tree.set_merge_modified(modified_hashes)
1058
1150
 
1059
1151
    @staticmethod
1060
 
    def parent(entry):
 
1152
    def parent(entry, file_id):
1061
1153
        """Determine the parent for a file_id (used as a key method)"""
1062
1154
        if entry is None:
1063
1155
            return None
1064
1156
        return entry.parent_id
1065
1157
 
1066
1158
    @staticmethod
1067
 
    def name(entry):
 
1159
    def name(entry, file_id):
1068
1160
        """Determine the name for a file_id (used as a key method)"""
1069
1161
        if entry is None:
1070
1162
            return None
1071
1163
        return entry.name
1072
1164
 
1073
1165
    @staticmethod
1074
 
    def contents_sha1(tree, path):
 
1166
    def contents_sha1(tree, file_id):
1075
1167
        """Determine the sha1 of the file contents (used as a key method)."""
1076
 
        try:
1077
 
            return tree.get_file_sha1(path)
1078
 
        except errors.NoSuchFile:
 
1168
        if file_id not in tree:
1079
1169
            return None
 
1170
        return tree.get_file_sha1(file_id)
1080
1171
 
1081
1172
    @staticmethod
1082
 
    def executable(tree, path):
 
1173
    def executable(tree, file_id):
1083
1174
        """Determine the executability of a file-id (used as a key method)."""
1084
 
        try:
1085
 
            if tree.kind(path) != "file":
1086
 
                return False
1087
 
        except errors.NoSuchFile:
 
1175
        if not tree.has_id(file_id):
1088
1176
            return None
1089
 
        return tree.is_executable(path)
 
1177
        if tree.kind(file_id) != "file":
 
1178
            return False
 
1179
        return tree.is_executable(file_id)
1090
1180
 
1091
1181
    @staticmethod
1092
 
    def kind(tree, path):
 
1182
    def kind(tree, file_id):
1093
1183
        """Determine the kind of a file-id (used as a key method)."""
1094
 
        try:
1095
 
            return tree.kind(path)
1096
 
        except errors.NoSuchFile:
 
1184
        if not tree.has_id(file_id):
1097
1185
            return None
 
1186
        return tree.kind(file_id)
1098
1187
 
1099
1188
    @staticmethod
1100
1189
    def _three_way(base, other, this):
1137
1226
        base_val, lca_vals = bases
1138
1227
        # Remove 'base_val' from the lca_vals, because it is not interesting
1139
1228
        filtered_lca_vals = [lca_val for lca_val in lca_vals
1140
 
                             if lca_val != base_val]
 
1229
                                      if lca_val != base_val]
1141
1230
        if len(filtered_lca_vals) == 0:
1142
1231
            return Merge3Merger._three_way(base_val, other, this)
1143
1232
 
1162
1251
        # At this point, the lcas disagree, and the tip disagree
1163
1252
        return 'conflict'
1164
1253
 
1165
 
    def _merge_names(self, trans_id, file_id, paths, parents, names, resolver):
1166
 
        """Perform a merge on file names and parents"""
 
1254
    @staticmethod
 
1255
    @deprecated_method(deprecated_in((2, 2, 0)))
 
1256
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
 
1257
        """Do a three-way test on a scalar.
 
1258
        Return "this", "other" or "conflict", depending whether a value wins.
 
1259
        """
 
1260
        key_base = key(base_tree, file_id)
 
1261
        key_other = key(other_tree, file_id)
 
1262
        #if base == other, either they all agree, or only THIS has changed.
 
1263
        if key_base == key_other:
 
1264
            return "this"
 
1265
        key_this = key(this_tree, file_id)
 
1266
        # "Ambiguous clean merge"
 
1267
        if key_this == key_other:
 
1268
            return "this"
 
1269
        elif key_this == key_base:
 
1270
            return "other"
 
1271
        else:
 
1272
            return "conflict"
 
1273
 
 
1274
    def merge_names(self, file_id):
 
1275
        def get_entry(tree):
 
1276
            if tree.has_id(file_id):
 
1277
                return tree.inventory[file_id]
 
1278
            else:
 
1279
                return None
 
1280
        this_entry = get_entry(self.this_tree)
 
1281
        other_entry = get_entry(self.other_tree)
 
1282
        base_entry = get_entry(self.base_tree)
 
1283
        entries = (base_entry, other_entry, this_entry)
 
1284
        names = []
 
1285
        parents = []
 
1286
        for entry in entries:
 
1287
            if entry is None:
 
1288
                names.append(None)
 
1289
                parents.append(None)
 
1290
            else:
 
1291
                names.append(entry.name)
 
1292
                parents.append(entry.parent_id)
 
1293
        return self._merge_names(file_id, parents, names,
 
1294
                                 resolver=self._three_way)
 
1295
 
 
1296
    def _merge_names(self, file_id, parents, names, resolver):
 
1297
        """Perform a merge on file_id names and parents"""
1167
1298
        base_name, other_name, this_name = names
1168
1299
        base_parent, other_parent, this_parent = parents
1169
 
        unused_base_path, other_path, this_path = paths
1170
1300
 
1171
1301
        name_winner = resolver(*names)
1172
1302
 
1182
1312
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1183
1313
            # road if a ContentConflict needs to be created so we should not do
1184
1314
            # that
 
1315
            trans_id = self.tt.trans_id_file_id(file_id)
1185
1316
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1186
1317
                                        this_parent, this_name,
1187
1318
                                        other_parent, other_name))
1188
 
        if other_path is None:
 
1319
        if other_name is None:
1189
1320
            # it doesn't matter whether the result was 'other' or
1190
 
            # 'conflict'-- if it has no file id, we leave it alone.
 
1321
            # 'conflict'-- if there's no 'other', we leave it alone.
1191
1322
            return
1192
1323
        parent_id = parents[self.winner_idx[parent_id_winner]]
1193
 
        name = names[self.winner_idx[name_winner]]
1194
 
        if parent_id is not None or name is not None:
 
1324
        if parent_id is not None:
1195
1325
            # if we get here, name_winner and parent_winner are set to safe
1196
1326
            # values.
1197
 
            if parent_id is None and name is not None:
1198
 
                # if parent_id is None and name is non-None, current file is
1199
 
                # the tree root.
1200
 
                if names[self.winner_idx[parent_id_winner]] != '':
1201
 
                    raise AssertionError(
1202
 
                        'File looks like a root, but named %s' %
1203
 
                        names[self.winner_idx[parent_id_winner]])
1204
 
                parent_trans_id = transform.ROOT_PARENT
1205
 
            else:
1206
 
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1207
 
            self.tt.adjust_path(name, parent_trans_id, trans_id)
 
1327
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
 
1328
                                self.tt.trans_id_file_id(parent_id),
 
1329
                                self.tt.trans_id_file_id(file_id))
1208
1330
 
1209
 
    def _do_merge_contents(self, paths, trans_id, file_id):
 
1331
    def _do_merge_contents(self, file_id):
1210
1332
        """Performs a merge on file_id contents."""
1211
 
        def contents_pair(tree, path):
1212
 
            if path is None:
1213
 
                return (None, None)
1214
 
            try:
1215
 
                kind = tree.kind(path)
1216
 
            except errors.NoSuchFile:
1217
 
                return (None, None)
 
1333
        def contents_pair(tree):
 
1334
            if file_id not in tree:
 
1335
                return (None, None)
 
1336
            kind = tree.kind(file_id)
1218
1337
            if kind == "file":
1219
 
                contents = tree.get_file_sha1(path)
 
1338
                contents = tree.get_file_sha1(file_id)
1220
1339
            elif kind == "symlink":
1221
 
                contents = tree.get_symlink_target(path)
 
1340
                contents = tree.get_symlink_target(file_id)
1222
1341
            else:
1223
1342
                contents = None
1224
1343
            return kind, contents
1225
1344
 
1226
 
        base_path, other_path, this_path = paths
1227
1345
        # See SPOT run.  run, SPOT, run.
1228
1346
        # So we're not QUITE repeating ourselves; we do tricky things with
1229
1347
        # file kind...
1230
 
        other_pair = contents_pair(self.other_tree, other_path)
1231
 
        this_pair = contents_pair(self.this_tree, this_path)
 
1348
        base_pair = contents_pair(self.base_tree)
 
1349
        other_pair = contents_pair(self.other_tree)
1232
1350
        if self._lca_trees:
1233
 
            (base_path, lca_paths) = base_path
1234
 
            base_pair = contents_pair(self.base_tree, base_path)
1235
 
            lca_pairs = [contents_pair(tree, path)
1236
 
                         for tree, path in zip(self._lca_trees, lca_paths)]
 
1351
            this_pair = contents_pair(self.this_tree)
 
1352
            lca_pairs = [contents_pair(tree) for tree in self._lca_trees]
1237
1353
            winner = self._lca_multi_way((base_pair, lca_pairs), other_pair,
1238
1354
                                         this_pair, allow_overriding_lca=False)
1239
1355
        else:
1240
 
            base_pair = contents_pair(self.base_tree, base_path)
1241
1356
            if base_pair == other_pair:
1242
1357
                winner = 'this'
1243
1358
            else:
1244
1359
                # We delayed evaluating this_pair as long as we can to avoid
1245
1360
                # unnecessary sha1 calculation
1246
 
                this_pair = contents_pair(self.this_tree, this_path)
 
1361
                this_pair = contents_pair(self.this_tree)
1247
1362
                winner = self._three_way(base_pair, other_pair, this_pair)
1248
1363
        if winner == 'this':
1249
1364
            # No interesting changes introduced by OTHER
1250
1365
            return "unmodified"
1251
1366
        # We have a hypothetical conflict, but if we have files, then we
1252
1367
        # can try to merge the content
1253
 
        params = MergeFileHookParams(
1254
 
            self, (base_path, other_path, this_path), trans_id, this_pair[0],
 
1368
        trans_id = self.tt.trans_id_file_id(file_id)
 
1369
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1255
1370
            other_pair[0], winner)
1256
1371
        hooks = self.active_hooks
1257
1372
        hook_status = 'not_applicable'
1260
1375
            if hook_status != 'not_applicable':
1261
1376
                # Don't try any more hooks, this one applies.
1262
1377
                break
1263
 
        # If the merge ends up replacing the content of the file, we get rid of
1264
 
        # it at the end of this method (this variable is used to track the
1265
 
        # exceptions to this rule).
1266
 
        keep_this = False
1267
1378
        result = "modified"
1268
1379
        if hook_status == 'not_applicable':
1269
 
            # No merge hook was able to resolve the situation. Two cases exist:
1270
 
            # a content conflict or a duplicate one.
 
1380
            # This is a contents conflict, because none of the available
 
1381
            # functions could merge it.
1271
1382
            result = None
1272
1383
            name = self.tt.final_name(trans_id)
1273
1384
            parent_id = self.tt.final_parent(trans_id)
1274
 
            inhibit_content_conflict = False
1275
 
            if params.this_kind is None:  # file_id is not in THIS
1276
 
                # Is the name used for a different file_id ?
1277
 
                if self.this_tree.is_versioned(other_path):
1278
 
                    # Two entries for the same path
1279
 
                    keep_this = True
1280
 
                    # versioning the merged file will trigger a duplicate
1281
 
                    # conflict
1282
 
                    self.tt.version_file(trans_id, file_id=file_id)
1283
 
                    transform.create_from_tree(
1284
 
                        self.tt, trans_id, self.other_tree,
1285
 
                        other_path,
1286
 
                        filter_tree_path=self._get_filter_tree_path(other_path))
1287
 
                    inhibit_content_conflict = True
1288
 
            elif params.other_kind is None:  # file_id is not in OTHER
1289
 
                # Is the name used for a different file_id ?
1290
 
                if self.other_tree.is_versioned(this_path):
1291
 
                    # Two entries for the same path again, but here, the other
1292
 
                    # entry will also be merged.  We simply inhibit the
1293
 
                    # 'content' conflict creation because we know OTHER will
1294
 
                    # create (or has already created depending on ordering) an
1295
 
                    # entry at the same path. This will trigger a 'duplicate'
1296
 
                    # conflict later.
1297
 
                    keep_this = True
1298
 
                    inhibit_content_conflict = True
1299
 
            if not inhibit_content_conflict:
1300
 
                if params.this_kind is not None:
1301
 
                    self.tt.unversion_file(trans_id)
1302
 
                # This is a contents conflict, because none of the available
1303
 
                # functions could merge it.
1304
 
                file_group = self._dump_conflicts(
1305
 
                    name, (base_path, other_path, this_path), parent_id,
1306
 
                    file_id, set_version=True)
1307
 
                self._raw_conflicts.append(('contents conflict', file_group))
 
1385
            if self.this_tree.has_id(file_id):
 
1386
                self.tt.unversion_file(trans_id)
 
1387
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1388
                                              set_version=True)
 
1389
            self._raw_conflicts.append(('contents conflict', file_group))
1308
1390
        elif hook_status == 'success':
1309
1391
            self.tt.create_file(lines, trans_id)
1310
1392
        elif hook_status == 'conflicted':
1314
1396
            self._raw_conflicts.append(('text conflict', trans_id))
1315
1397
            name = self.tt.final_name(trans_id)
1316
1398
            parent_id = self.tt.final_parent(trans_id)
1317
 
            self._dump_conflicts(
1318
 
                name, (base_path, other_path, this_path), parent_id, file_id)
 
1399
            self._dump_conflicts(name, parent_id, file_id)
1319
1400
        elif hook_status == 'delete':
1320
1401
            self.tt.unversion_file(trans_id)
1321
1402
            result = "deleted"
1325
1406
            pass
1326
1407
        else:
1327
1408
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
1328
 
        if not this_path and result == "modified":
1329
 
            self.tt.version_file(trans_id, file_id=file_id)
1330
 
        if not keep_this:
1331
 
            # The merge has been performed and produced a new content, so the
1332
 
            # old contents should not be retained.
1333
 
            self.tt.delete_contents(trans_id)
 
1409
        if not self.this_tree.has_id(file_id) and result == "modified":
 
1410
            self.tt.version_file(file_id, trans_id)
 
1411
        # The merge has been performed, so the old contents should not be
 
1412
        # retained.
 
1413
        self.tt.delete_contents(trans_id)
1334
1414
        return result
1335
1415
 
1336
1416
    def _default_other_winner_merge(self, merge_hook_params):
1337
1417
        """Replace this contents with other."""
 
1418
        file_id = merge_hook_params.file_id
1338
1419
        trans_id = merge_hook_params.trans_id
1339
 
        if merge_hook_params.other_path is not None:
 
1420
        file_in_this = self.this_tree.has_id(file_id)
 
1421
        if self.other_tree.has_id(file_id):
1340
1422
            # OTHER changed the file
1341
 
            transform.create_from_tree(
1342
 
                self.tt, trans_id, self.other_tree,
1343
 
                merge_hook_params.other_path,
1344
 
                filter_tree_path=self._get_filter_tree_path(merge_hook_params.other_path))
 
1423
            wt = self.this_tree
 
1424
            if wt.supports_content_filtering():
 
1425
                # We get the path from the working tree if it exists.
 
1426
                # That fails though when OTHER is adding a file, so
 
1427
                # we fall back to the other tree to find the path if
 
1428
                # it doesn't exist locally.
 
1429
                try:
 
1430
                    filter_tree_path = wt.id2path(file_id)
 
1431
                except errors.NoSuchId:
 
1432
                    filter_tree_path = self.other_tree.id2path(file_id)
 
1433
            else:
 
1434
                # Skip the id2path lookup for older formats
 
1435
                filter_tree_path = None
 
1436
            transform.create_from_tree(self.tt, trans_id,
 
1437
                             self.other_tree, file_id,
 
1438
                             filter_tree_path=filter_tree_path)
1345
1439
            return 'done', None
1346
 
        elif merge_hook_params.this_path is not None:
 
1440
        elif file_in_this:
1347
1441
            # OTHER deleted the file
1348
1442
            return 'delete', None
1349
1443
        else:
1350
1444
            raise AssertionError(
1351
 
                'winner is OTHER, but file %r not in THIS or OTHER tree'
1352
 
                % (merge_hook_params.base_path,))
 
1445
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
 
1446
                % (file_id,))
1353
1447
 
1354
1448
    def merge_contents(self, merge_hook_params):
1355
1449
        """Fallback merge logic after user installed hooks."""
1356
1450
        # This function is used in merge hooks as the fallback instance.
1357
 
        # Perhaps making this function and the functions it calls be a
 
1451
        # Perhaps making this function and the functions it calls be a 
1358
1452
        # a separate class would be better.
1359
1453
        if merge_hook_params.winner == 'other':
1360
1454
            # OTHER is a straight winner, so replace this contents with other
1364
1458
            # BASE is a file, or both converted to files, so at least we
1365
1459
            # have agreement that output should be a file.
1366
1460
            try:
1367
 
                self.text_merge(merge_hook_params.trans_id,
1368
 
                                merge_hook_params.paths)
 
1461
                self.text_merge(merge_hook_params.file_id,
 
1462
                    merge_hook_params.trans_id)
1369
1463
            except errors.BinaryFile:
1370
1464
                return 'not_applicable', None
1371
1465
            return 'done', None
1372
1466
        else:
1373
1467
            return 'not_applicable', None
1374
1468
 
1375
 
    def get_lines(self, tree, path):
 
1469
    def get_lines(self, tree, file_id):
1376
1470
        """Return the lines in a file, or an empty list."""
1377
 
        if path is None:
1378
 
            return []
1379
 
        try:
1380
 
            kind = tree.kind(path)
1381
 
        except errors.NoSuchFile:
1382
 
            return []
 
1471
        if tree.has_id(file_id):
 
1472
            return tree.get_file_lines(file_id)
1383
1473
        else:
1384
 
            if kind != 'file':
1385
 
                return []
1386
 
            return tree.get_file_lines(path)
 
1474
            return []
1387
1475
 
1388
 
    def text_merge(self, trans_id, paths):
1389
 
        """Perform a three-way text merge on a file"""
 
1476
    def text_merge(self, file_id, trans_id):
 
1477
        """Perform a three-way text merge on a file_id"""
1390
1478
        # it's possible that we got here with base as a different type.
1391
1479
        # if so, we just want two-way text conflicts.
1392
 
        base_path, other_path, this_path = paths
1393
 
        base_lines = self.get_lines(self.base_tree, base_path)
1394
 
        other_lines = self.get_lines(self.other_tree, other_path)
1395
 
        this_lines = self.get_lines(self.this_tree, this_path)
 
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)
1396
1487
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1397
1488
                           is_cherrypick=self.cherrypick)
1398
 
        start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
 
1489
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1399
1490
        if self.show_base is True:
1400
 
            base_marker = b'|' * 7
 
1491
            base_marker = '|' * 7
1401
1492
        else:
1402
1493
            base_marker = None
1403
1494
 
1404
1495
        def iter_merge3(retval):
1405
1496
            retval["text_conflicts"] = False
1406
 
            for line in m3.merge_lines(name_a=b"TREE",
1407
 
                                       name_b=b"MERGE-SOURCE",
1408
 
                                       name_base=b"BASE-REVISION",
 
1497
            for line in m3.merge_lines(name_a = "TREE",
 
1498
                                       name_b = "MERGE-SOURCE",
 
1499
                                       name_base = "BASE-REVISION",
1409
1500
                                       start_marker=start_marker,
1410
1501
                                       base_marker=base_marker,
1411
1502
                                       reprocess=self.reprocess):
1412
1503
                if line.startswith(start_marker):
1413
1504
                    retval["text_conflicts"] = True
1414
 
                    yield line.replace(start_marker, b'<' * 7)
 
1505
                    yield line.replace(start_marker, '<' * 7)
1415
1506
                else:
1416
1507
                    yield line
1417
1508
        retval = {}
1421
1512
            self._raw_conflicts.append(('text conflict', trans_id))
1422
1513
            name = self.tt.final_name(trans_id)
1423
1514
            parent_id = self.tt.final_parent(trans_id)
1424
 
            file_id = self.tt.final_file_id(trans_id)
1425
 
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
 
1515
            file_group = self._dump_conflicts(name, parent_id, file_id,
1426
1516
                                              this_lines, base_lines,
1427
1517
                                              other_lines)
1428
1518
            file_group.append(trans_id)
1429
1519
 
1430
 
    def _get_filter_tree_path(self, path):
1431
 
        if self.this_tree.supports_content_filtering():
1432
 
            # We get the path from the working tree if it exists.
1433
 
            # That fails though when OTHER is adding a file, so
1434
 
            # we fall back to the other tree to find the path if
1435
 
            # it doesn't exist locally.
1436
 
            filter_path = _mod_tree.find_previous_path(
1437
 
                self.other_tree, self.working_tree, path)
1438
 
            if filter_path is None:
1439
 
                filter_path = path
1440
 
            return filter_path
1441
 
        # Skip the lookup for older formats
1442
 
        return None
1443
 
 
1444
 
    def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
 
1520
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
1445
1521
                        base_lines=None, other_lines=None, set_version=False,
1446
1522
                        no_base=False):
1447
1523
        """Emit conflict files.
1449
1525
        determined automatically.  If set_version is true, the .OTHER, .THIS
1450
1526
        or .BASE (in that order) will be created as versioned files.
1451
1527
        """
1452
 
        base_path, other_path, this_path = paths
1453
 
        data = [('OTHER', self.other_tree, other_path, other_lines),
1454
 
                ('THIS', self.this_tree, this_path, this_lines)]
 
1528
        data = [('OTHER', self.other_tree, other_lines),
 
1529
                ('THIS', self.this_tree, this_lines)]
1455
1530
        if not no_base:
1456
 
            data.append(('BASE', self.base_tree, base_path, base_lines))
 
1531
            data.append(('BASE', self.base_tree, base_lines))
1457
1532
 
1458
1533
        # We need to use the actual path in the working tree of the file here,
1459
 
        if self.this_tree.supports_content_filtering():
1460
 
            filter_tree_path = this_path
 
1534
        # ignoring the conflict suffixes
 
1535
        wt = self.this_tree
 
1536
        if wt.supports_content_filtering():
 
1537
            try:
 
1538
                filter_tree_path = wt.id2path(file_id)
 
1539
            except errors.NoSuchId:
 
1540
                # file has been deleted
 
1541
                filter_tree_path = None
1461
1542
        else:
1462
1543
            # Skip the id2path lookup for older formats
1463
1544
            filter_tree_path = None
1464
1545
 
1465
1546
        versioned = False
1466
1547
        file_group = []
1467
 
        for suffix, tree, path, lines in data:
1468
 
            if path is not None:
1469
 
                trans_id = self._conflict_file(
1470
 
                    name, parent_id, path, tree, suffix, lines,
1471
 
                    filter_tree_path)
 
1548
        for suffix, tree, lines in data:
 
1549
            if tree.has_id(file_id):
 
1550
                trans_id = self._conflict_file(name, parent_id, tree, file_id,
 
1551
                                               suffix, lines, filter_tree_path)
1472
1552
                file_group.append(trans_id)
1473
1553
                if set_version and not versioned:
1474
 
                    self.tt.version_file(trans_id, file_id=file_id)
 
1554
                    self.tt.version_file(file_id, trans_id)
1475
1555
                    versioned = True
1476
1556
        return file_group
1477
1557
 
1478
 
    def _conflict_file(self, name, parent_id, path, tree, suffix,
 
1558
    def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1479
1559
                       lines=None, filter_tree_path=None):
1480
1560
        """Emit a single conflict file."""
1481
1561
        name = name + '.' + suffix
1482
1562
        trans_id = self.tt.create_path(name, parent_id)
1483
 
        transform.create_from_tree(
1484
 
            self.tt, trans_id, tree, path,
1485
 
            chunks=lines,
1486
 
            filter_tree_path=filter_tree_path)
 
1563
        transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
 
1564
            filter_tree_path)
1487
1565
        return trans_id
1488
1566
 
1489
 
    def _merge_executable(self, paths, trans_id, executable, file_status,
 
1567
    def merge_executable(self, file_id, file_status):
 
1568
        """Perform a merge on the execute bit."""
 
1569
        executable = [self.executable(t, file_id) for t in (self.base_tree,
 
1570
                      self.other_tree, self.this_tree)]
 
1571
        self._merge_executable(file_id, executable, file_status,
 
1572
                               resolver=self._three_way)
 
1573
 
 
1574
    def _merge_executable(self, file_id, executable, file_status,
1490
1575
                          resolver):
1491
1576
        """Perform a merge on the execute bit."""
1492
1577
        base_executable, other_executable, this_executable = executable
1493
 
        base_path, other_path, this_path = paths
1494
1578
        if file_status == "deleted":
1495
1579
            return
1496
1580
        winner = resolver(*executable)
1497
1581
        if winner == "conflict":
1498
 
            # There must be a None in here, if we have a conflict, but we
1499
 
            # need executability since file status was not deleted.
1500
 
            if other_path is None:
 
1582
        # There must be a None in here, if we have a conflict, but we
 
1583
        # need executability since file status was not deleted.
 
1584
            if self.executable(self.other_tree, file_id) is None:
1501
1585
                winner = "this"
1502
1586
            else:
1503
1587
                winner = "other"
1504
1588
        if winner == 'this' and file_status != "modified":
1505
1589
            return
 
1590
        trans_id = self.tt.trans_id_file_id(file_id)
1506
1591
        if self.tt.final_kind(trans_id) != "file":
1507
1592
            return
1508
1593
        if winner == "this":
1509
1594
            executability = this_executable
1510
1595
        else:
1511
 
            if other_path is not None:
 
1596
            if self.other_tree.has_id(file_id):
1512
1597
                executability = other_executable
1513
 
            elif this_path is not None:
 
1598
            elif self.this_tree.has_id(file_id):
1514
1599
                executability = this_executable
1515
 
            elif base_path is not None:
 
1600
            elif self.base_tree_has_id(file_id):
1516
1601
                executability = base_executable
1517
1602
        if executability is not None:
 
1603
            trans_id = self.tt.trans_id_file_id(file_id)
1518
1604
            self.tt.set_executability(executability, trans_id)
1519
1605
 
1520
1606
    def cook_conflicts(self, fs_conflicts):
1521
1607
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1522
 
        content_conflict_file_ids = set()
1523
 
        cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
 
1608
        self.cooked_conflicts.extend(transform.cook_conflicts(
 
1609
                fs_conflicts, self.tt))
1524
1610
        fp = transform.FinalPaths(self.tt)
1525
1611
        for conflict in self._raw_conflicts:
1526
1612
            conflict_type = conflict[0]
1527
1613
            if conflict_type == 'path conflict':
1528
1614
                (trans_id, file_id,
1529
 
                 this_parent, this_name,
1530
 
                 other_parent, other_name) = conflict[1:]
 
1615
                this_parent, this_name,
 
1616
                other_parent, other_name) = conflict[1:]
1531
1617
                if this_parent is None or this_name is None:
1532
1618
                    this_path = '<deleted>'
1533
1619
                else:
1534
 
                    parent_path = fp.get_path(
 
1620
                    parent_path =  fp.get_path(
1535
1621
                        self.tt.trans_id_file_id(this_parent))
1536
1622
                    this_path = osutils.pathjoin(parent_path, this_name)
1537
1623
                if other_parent is None or other_name is None:
1538
1624
                    other_path = '<deleted>'
1539
1625
                else:
1540
 
                    if other_parent == self.other_tree.path2id(''):
1541
 
                        # The tree transform doesn't know about the other root,
1542
 
                        # so we special case here to avoid a NoFinalPath
1543
 
                        # exception
1544
 
                        parent_path = ''
1545
 
                    else:
1546
 
                        parent_path = fp.get_path(
1547
 
                            self.tt.trans_id_file_id(other_parent))
 
1626
                    parent_path =  fp.get_path(
 
1627
                        self.tt.trans_id_file_id(other_parent))
1548
1628
                    other_path = osutils.pathjoin(parent_path, other_name)
1549
1629
                c = _mod_conflicts.Conflict.factory(
1550
1630
                    'path conflict', path=this_path,
1554
1634
                for trans_id in conflict[1]:
1555
1635
                    file_id = self.tt.final_file_id(trans_id)
1556
1636
                    if file_id is not None:
1557
 
                        # Ok we found the relevant file-id
1558
1637
                        break
1559
1638
                path = fp.get_path(trans_id)
1560
1639
                for suffix in ('.BASE', '.THIS', '.OTHER'):
1561
1640
                    if path.endswith(suffix):
1562
 
                        # Here is the raw path
1563
1641
                        path = path[:-len(suffix)]
1564
1642
                        break
1565
1643
                c = _mod_conflicts.Conflict.factory(conflict_type,
1566
1644
                                                    path=path, file_id=file_id)
1567
 
                content_conflict_file_ids.add(file_id)
1568
1645
            elif conflict_type == 'text conflict':
1569
1646
                trans_id = conflict[1]
1570
1647
                path = fp.get_path(trans_id)
1573
1650
                                                    path=path, file_id=file_id)
1574
1651
            else:
1575
1652
                raise AssertionError('bad conflict type: %r' % (conflict,))
1576
 
            cooked_conflicts.append(c)
1577
 
 
1578
 
        self.cooked_conflicts = []
1579
 
        # We want to get rid of path conflicts when a corresponding contents
1580
 
        # conflict exists. This can occur when one branch deletes a file while
1581
 
        # the other renames *and* modifies it. In this case, the content
1582
 
        # conflict is enough.
1583
 
        for c in cooked_conflicts:
1584
 
            if (c.typestring == 'path conflict'
1585
 
                    and c.file_id in content_conflict_file_ids):
1586
 
                continue
1587
1653
            self.cooked_conflicts.append(c)
1588
1654
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1589
1655
 
1594
1660
    supports_show_base = False
1595
1661
    supports_reverse_cherrypick = False
1596
1662
    history_based = True
1597
 
    requires_file_merge_plan = True
1598
1663
 
1599
 
    def _generate_merge_plan(self, this_path, base):
1600
 
        return self.this_tree.plan_file_merge(this_path, self.other_tree,
 
1664
    def _generate_merge_plan(self, file_id, base):
 
1665
        return self.this_tree.plan_file_merge(file_id, self.other_tree,
1601
1666
                                              base=base)
1602
1667
 
1603
 
    def _merged_lines(self, this_path):
 
1668
    def _merged_lines(self, file_id):
1604
1669
        """Generate the merged lines.
1605
1670
        There is no distinction between lines that are meant to contain <<<<<<<
1606
1671
        and conflicts.
1609
1674
            base = self.base_tree
1610
1675
        else:
1611
1676
            base = None
1612
 
        plan = self._generate_merge_plan(this_path, base)
 
1677
        plan = self._generate_merge_plan(file_id, base)
1613
1678
        if 'merge' in debug.debug_flags:
1614
1679
            plan = list(plan)
1615
1680
            trans_id = self.tt.trans_id_file_id(file_id)
1616
1681
            name = self.tt.final_name(trans_id) + '.plan'
1617
 
            contents = (b'%11s|%s' % l for l in plan)
 
1682
            contents = ('%11s|%s' % l for l in plan)
1618
1683
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1619
 
        textmerge = versionedfile.PlanWeaveMerge(plan, b'<<<<<<< TREE\n',
1620
 
                                                 b'>>>>>>> MERGE-SOURCE\n')
 
1684
        textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
1685
                                                 '>>>>>>> MERGE-SOURCE\n')
1621
1686
        lines, conflicts = textmerge.merge_lines(self.reprocess)
1622
1687
        if conflicts:
1623
1688
            base_lines = textmerge.base_from_plan()
1625
1690
            base_lines = None
1626
1691
        return lines, base_lines
1627
1692
 
1628
 
    def text_merge(self, trans_id, paths):
 
1693
    def text_merge(self, file_id, trans_id):
1629
1694
        """Perform a (weave) text merge for a given file and file-id.
1630
1695
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1631
1696
        and a conflict will be noted.
1632
1697
        """
1633
 
        base_path, other_path, this_path = paths
1634
 
        lines, base_lines = self._merged_lines(this_path)
 
1698
        lines, base_lines = self._merged_lines(file_id)
1635
1699
        lines = list(lines)
1636
1700
        # Note we're checking whether the OUTPUT is binary in this case,
1637
1701
        # because we don't want to get into weave merge guts.
1642
1706
            self._raw_conflicts.append(('text conflict', trans_id))
1643
1707
            name = self.tt.final_name(trans_id)
1644
1708
            parent_id = self.tt.final_parent(trans_id)
1645
 
            file_id = self.tt.final_file_id(trans_id)
1646
 
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
 
1709
            file_group = self._dump_conflicts(name, parent_id, file_id,
1647
1710
                                              no_base=False,
1648
1711
                                              base_lines=base_lines)
1649
1712
            file_group.append(trans_id)
1651
1714
 
1652
1715
class LCAMerger(WeaveMerger):
1653
1716
 
1654
 
    requires_file_merge_plan = True
1655
 
 
1656
 
    def _generate_merge_plan(self, this_path, base):
1657
 
        return self.this_tree.plan_file_lca_merge(this_path, self.other_tree,
 
1717
    def _generate_merge_plan(self, file_id, base):
 
1718
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1658
1719
                                                  base=base)
1659
1720
 
1660
 
 
1661
1721
class Diff3Merger(Merge3Merger):
1662
1722
    """Three-way merger using external diff3 for text merging"""
1663
1723
 
1664
 
    requires_file_merge_plan = False
1665
 
 
1666
 
    def dump_file(self, temp_dir, name, tree, path):
 
1724
    def dump_file(self, temp_dir, name, tree, file_id):
1667
1725
        out_path = osutils.pathjoin(temp_dir, name)
1668
 
        with open(out_path, "wb") as out_file:
1669
 
            in_file = tree.get_file(path)
 
1726
        out_file = open(out_path, "wb")
 
1727
        try:
 
1728
            in_file = tree.get_file(file_id)
1670
1729
            for line in in_file:
1671
1730
                out_file.write(line)
 
1731
        finally:
 
1732
            out_file.close()
1672
1733
        return out_path
1673
1734
 
1674
 
    def text_merge(self, trans_id, paths):
 
1735
    def text_merge(self, file_id, trans_id):
1675
1736
        """Perform a diff3 merge using a specified file-id and trans-id.
1676
1737
        If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1677
1738
        will be dumped, and a will be conflict noted.
1678
1739
        """
1679
 
        import breezy.patch
1680
 
        base_path, other_path, this_path = paths
 
1740
        import bzrlib.patch
1681
1741
        temp_dir = osutils.mkdtemp(prefix="bzr-")
1682
1742
        try:
1683
1743
            new_file = osutils.pathjoin(temp_dir, "new")
1684
 
            this = self.dump_file(
1685
 
                temp_dir, "this", self.this_tree, this_path)
1686
 
            base = self.dump_file(
1687
 
                temp_dir, "base", self.base_tree, base_path)
1688
 
            other = self.dump_file(
1689
 
                temp_dir, "other", self.other_tree, other_path)
1690
 
            status = breezy.patch.diff3(new_file, this, base, other)
 
1744
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
 
1745
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
 
1746
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
 
1747
            status = bzrlib.patch.diff3(new_file, this, base, other)
1691
1748
            if status not in (0, 1):
1692
1749
                raise errors.BzrError("Unhandled diff3 exit code")
1693
 
            with open(new_file, 'rb') as f:
 
1750
            f = open(new_file, 'rb')
 
1751
            try:
1694
1752
                self.tt.create_file(f, trans_id)
 
1753
            finally:
 
1754
                f.close()
1695
1755
            if status == 1:
1696
1756
                name = self.tt.final_name(trans_id)
1697
1757
                parent_id = self.tt.final_parent(trans_id)
1698
 
                file_id = self.tt.final_file_id(trans_id)
1699
 
                self._dump_conflicts(name, paths, parent_id, file_id)
 
1758
                self._dump_conflicts(name, parent_id, file_id)
1700
1759
                self._raw_conflicts.append(('text conflict', trans_id))
1701
1760
        finally:
1702
1761
            osutils.rmtree(temp_dir)
1718
1777
    """
1719
1778
 
1720
1779
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1721
 
                 source_subpath, other_rev_id=None):
 
1780
            source_subpath, other_rev_id=None):
1722
1781
        """Create a new MergeIntoMerger object.
1723
1782
 
1724
1783
        source_subpath in other_tree will be effectively copied to
1735
1794
        # It is assumed that we are merging a tree that is not in our current
1736
1795
        # ancestry, which means we are using the "EmptyTree" as our basis.
1737
1796
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
1738
 
            _mod_revision.NULL_REVISION)
 
1797
                                _mod_revision.NULL_REVISION)
1739
1798
        super(MergeIntoMerger, self).__init__(
1740
1799
            this_branch=this_tree.branch,
1741
1800
            this_tree=this_tree,
1753
1812
        self.merge_type = Merge3Merger
1754
1813
        self.show_base = False
1755
1814
        self.reprocess = False
1756
 
        self.interesting_files = None
 
1815
        self.interesting_ids = None
1757
1816
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1758
 
                                                  target_subdir=self._target_subdir,
1759
 
                                                  source_subpath=self._source_subpath)
 
1817
              target_subdir=self._target_subdir,
 
1818
              source_subpath=self._source_subpath)
1760
1819
        if self._source_subpath != '':
1761
1820
            # If this isn't a partial merge make sure the revisions will be
1762
1821
            # present.
1763
1822
            self._maybe_fetch(self.other_branch, self.this_branch,
1764
 
                              self.other_basis)
 
1823
                self.other_basis)
1765
1824
 
1766
1825
    def set_pending(self):
1767
1826
        if self._source_subpath != '':
1771
1830
 
1772
1831
class _MergeTypeParameterizer(object):
1773
1832
    """Wrap a merge-type class to provide extra parameters.
1774
 
 
 
1833
    
1775
1834
    This is hack used by MergeIntoMerger to pass some extra parameters to its
1776
1835
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
1777
1836
    the 'merge_type' member.  It is difficult override do_merge without
1812
1871
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1813
1872
 
1814
1873
    def _compute_transform(self):
1815
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1874
        child_pb = ui.ui_factory.nested_progress_bar()
 
1875
        try:
1816
1876
            entries = self._entries_to_incorporate()
1817
1877
            entries = list(entries)
1818
 
            for num, (entry, parent_id, relpath) in enumerate(entries):
1819
 
                child_pb.update(gettext('Preparing file merge'),
1820
 
                                num, len(entries))
 
1878
            for num, (entry, parent_id) in enumerate(entries):
 
1879
                child_pb.update('Preparing file merge', num, len(entries))
1821
1880
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1822
 
                path = osutils.pathjoin(self._source_subpath, relpath)
1823
 
                trans_id = transform.new_by_entry(path, self.tt, entry,
1824
 
                                                  parent_trans_id, self.other_tree)
 
1881
                trans_id = transform.new_by_entry(self.tt, entry,
 
1882
                    parent_trans_id, self.other_tree)
 
1883
        finally:
 
1884
            child_pb.finished()
1825
1885
        self._finish_computing_transform()
1826
1886
 
1827
1887
    def _entries_to_incorporate(self):
1828
1888
        """Yields pairs of (inventory_entry, new_parent)."""
1829
 
        subdir_id = self.other_tree.path2id(self._source_subpath)
 
1889
        other_inv = self.other_tree.inventory
 
1890
        subdir_id = other_inv.path2id(self._source_subpath)
1830
1891
        if subdir_id is None:
1831
1892
            # XXX: The error would be clearer if it gave the URL of the source
1832
1893
            # branch, but we don't have a reference to that here.
1833
1894
            raise PathNotInTree(self._source_subpath, "Source tree")
1834
 
        subdir = next(self.other_tree.iter_entries_by_dir(
1835
 
            specific_files=[self._source_subpath]))[1]
 
1895
        subdir = other_inv[subdir_id]
1836
1896
        parent_in_target = osutils.dirname(self._target_subdir)
1837
 
        target_id = self.this_tree.path2id(parent_in_target)
 
1897
        target_id = self.this_tree.inventory.path2id(parent_in_target)
1838
1898
        if target_id is None:
1839
1899
            raise PathNotInTree(self._target_subdir, "Target tree")
1840
1900
        name_in_target = osutils.basename(self._target_subdir)
1841
1901
        merge_into_root = subdir.copy()
1842
1902
        merge_into_root.name = name_in_target
1843
 
        try:
1844
 
            self.this_tree.id2path(merge_into_root.file_id)
1845
 
        except errors.NoSuchId:
1846
 
            pass
1847
 
        else:
 
1903
        if merge_into_root.file_id in self.this_tree.inventory:
1848
1904
            # Give the root a new file-id.
1849
1905
            # This can happen fairly easily if the directory we are
1850
1906
            # incorporating is the root, and both trees have 'TREE_ROOT' as
1854
1910
            # an edge case, so we don't do anything special for those.  We let
1855
1911
            # them cause conflicts.
1856
1912
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
1857
 
        yield (merge_into_root, target_id, '')
 
1913
        yield (merge_into_root, target_id)
1858
1914
        if subdir.kind != 'directory':
1859
1915
            # No children, so we are done.
1860
1916
            return
1861
 
        for path, entry in self.other_tree.root_inventory.iter_entries_by_dir(subdir_id):
 
1917
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
1862
1918
            parent_id = entry.parent_id
1863
1919
            if parent_id == subdir.file_id:
1864
1920
                # The root's parent ID has changed, so make sure children of
1865
1921
                # the root refer to the new ID.
1866
1922
                parent_id = merge_into_root.file_id
1867
 
            yield (entry, parent_id, path)
 
1923
            yield (entry, parent_id)
1868
1924
 
1869
1925
 
1870
1926
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1871
1927
                backup_files=False,
1872
1928
                merge_type=Merge3Merger,
 
1929
                interesting_ids=None,
1873
1930
                show_base=False,
1874
1931
                reprocess=False,
1875
1932
                other_rev_id=None,
1876
1933
                interesting_files=None,
1877
1934
                this_tree=None,
 
1935
                pb=None,
1878
1936
                change_reporter=None):
1879
1937
    """Primary interface for merging.
1880
1938
 
1884
1942
                    branch.get_revision_tree(base_revision))
1885
1943
    """
1886
1944
    if this_tree is None:
1887
 
        raise errors.BzrError("breezy.merge.merge_inner requires a this_tree "
1888
 
                              "parameter")
 
1945
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1946
                              "parameter as of bzrlib version 0.8.")
1889
1947
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1890
 
                    change_reporter=change_reporter)
 
1948
                    pb=pb, change_reporter=change_reporter)
1891
1949
    merger.backup_files = backup_files
1892
1950
    merger.merge_type = merge_type
 
1951
    merger.interesting_ids = interesting_ids
1893
1952
    merger.ignore_zero = ignore_zero
1894
 
    merger.interesting_files = interesting_files
 
1953
    if interesting_files:
 
1954
        if interesting_ids:
 
1955
            raise ValueError('Only supply interesting_ids'
 
1956
                             ' or interesting_files')
 
1957
        merger.interesting_files = interesting_files
1895
1958
    merger.show_base = show_base
1896
1959
    merger.reprocess = reprocess
1897
1960
    merger.other_rev_id = other_rev_id
1903
1966
    merger.set_base_revision(get_revision_id(), this_branch)
1904
1967
    return merger.do_merge()
1905
1968
 
1906
 
 
1907
 
merge_type_registry = registry.Registry()
1908
 
merge_type_registry.register('diff3', Diff3Merger,
1909
 
                             "Merge using external diff3.")
1910
 
merge_type_registry.register('lca', LCAMerger,
1911
 
                             "LCA-newness merge.")
1912
 
merge_type_registry.register('merge3', Merge3Merger,
1913
 
                             "Native diff3-style merge.")
1914
 
merge_type_registry.register('weave', WeaveMerger,
1915
 
                             "Weave-based merge.")
1916
 
 
1917
 
 
1918
1969
def get_merge_type_registry():
1919
 
    """Merge type registry was previously in breezy.option
 
1970
    """Merge type registry is in bzrlib.option to avoid circular imports.
1920
1971
 
1921
 
    This method provides a backwards compatible way to retrieve it.
 
1972
    This method provides a sanctioned way to retrieve it.
1922
1973
    """
1923
 
    return merge_type_registry
 
1974
    from bzrlib import option
 
1975
    return option._merge_type_registry
1924
1976
 
1925
1977
 
1926
1978
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1992
2044
        for record in self.vf.get_record_stream(keys, 'unordered', True):
1993
2045
            if record.storage_kind == 'absent':
1994
2046
                raise errors.RevisionNotPresent(record.key, self.vf)
1995
 
            result[record.key[-1]] = record.get_bytes_as('lines')
 
2047
            result[record.key[-1]] = osutils.chunks_to_lines(
 
2048
                record.get_bytes_as('chunked'))
1996
2049
        return result
1997
2050
 
1998
2051
    def plan_merge(self):
2031
2084
                else:
2032
2085
                    yield 'killed-a', self.lines_b[b_index]
2033
2086
            # handle common lines
2034
 
            for a_index in range(i, i + n):
 
2087
            for a_index in range(i, i+n):
2035
2088
                yield 'unchanged', self.lines_a[a_index]
2036
 
            last_i = i + n
2037
 
            last_j = j + n
 
2089
            last_i = i+n
 
2090
            last_j = j+n
2038
2091
 
2039
2092
    def _get_matching_blocks(self, left_revision, right_revision):
2040
2093
        """Return a description of which sections of two revisions match.
2096
2149
        for i, j, n in matcher.get_matching_blocks():
2097
2150
            for jj in range(last_j, j):
2098
2151
                yield new_plan[jj]
2099
 
            for jj in range(j, j + n):
 
2152
            for jj in range(j, j+n):
2100
2153
                plan_line = new_plan[jj]
2101
2154
                if plan_line[0] == 'new-b':
2102
2155
                    pass
2146
2199
        while True:
2147
2200
            next_lcas = self.graph.find_lca(*cur_ancestors)
2148
2201
            # Map a plain NULL_REVISION to a simple no-ancestors
2149
 
            if next_lcas == {_mod_revision.NULL_REVISION}:
 
2202
            if next_lcas == set([_mod_revision.NULL_REVISION]):
2150
2203
                next_lcas = ()
2151
2204
            # Order the lca's based on when they were merged into the tip
2152
2205
            # While the actual merge portion of weave merge uses a set() of
2240
2293
        filtered_parent_map = {}
2241
2294
        child_map = {}
2242
2295
        tails = []
2243
 
        for key, parent_keys in parent_map.items():
 
2296
        for key, parent_keys in parent_map.iteritems():
2244
2297
            culled_parent_keys = [p for p in parent_keys if p in parent_map]
2245
2298
            if not culled_parent_keys:
2246
2299
                tails.append(key)
2296
2349
        return all_texts
2297
2350
 
2298
2351
    def _build_weave(self):
2299
 
        from .bzr import weave
 
2352
        from bzrlib import weave
2300
2353
        self._weave = weave.Weave(weave_name='in_memory_weave',
2301
2354
                                  allow_reserved=True)
2302
2355
        parent_map = self._find_recursive_lcas()
2333
2386
        are combined, they are written out in the format described in
2334
2387
        VersionedFile.plan_merge
2335
2388
        """
2336
 
        if self._head_key is not None:  # There was a single head
 
2389
        if self._head_key is not None: # There was a single head
2337
2390
            if self._head_key == self.a_key:
2338
2391
                plan = 'new-a'
2339
2392
            else: