/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/merge.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

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