~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-01-22 08:05:06 UTC
  • mfrom: (4980.1.2 news-details)
  • Revision ID: pqm@pqm.ubuntu.com-20100122080506-7p37edg2vai36qb4
(nmb) Clarify NEWS entries handling

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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 warnings
18
17
 
19
 
from bzrlib.lazy_import import lazy_import
20
 
lazy_import(globals(), """
21
18
from bzrlib import (
22
19
    branch as _mod_branch,
23
 
    cleanup,
24
20
    conflicts as _mod_conflicts,
25
21
    debug,
26
 
    generate_ids,
 
22
    decorators,
 
23
    errors,
27
24
    graph as _mod_graph,
 
25
    hooks,
28
26
    merge3,
29
27
    osutils,
30
28
    patiencediff,
 
29
    progress,
31
30
    revision as _mod_revision,
32
31
    textfile,
33
32
    trace,
35
34
    tree as _mod_tree,
36
35
    tsort,
37
36
    ui,
38
 
    versionedfile,
39
 
    workingtree,
40
 
    )
41
 
""")
42
 
from bzrlib import (
43
 
    decorators,
44
 
    errors,
45
 
    hooks,
 
37
    versionedfile
46
38
    )
47
39
from bzrlib.symbol_versioning import (
48
40
    deprecated_in,
53
45
 
54
46
def transform_tree(from_tree, to_tree, interesting_ids=None):
55
47
    from_tree.lock_tree_write()
56
 
    operation = cleanup.OperationWithCleanups(merge_inner)
57
 
    operation.add_cleanup(from_tree.unlock)
58
 
    operation.run_simple(from_tree.branch, to_tree, from_tree,
59
 
        ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
 
48
    try:
 
49
        merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
 
50
                    interesting_ids=interesting_ids, this_tree=from_tree)
 
51
    finally:
 
52
        from_tree.unlock()
60
53
 
61
54
 
62
55
class MergeHooks(hooks.Hooks):
63
56
 
64
57
    def __init__(self):
65
 
        hooks.Hooks.__init__(self, "bzrlib.merge", "Merger.hooks")
66
 
        self.add_hook('merge_file_content',
67
 
            "Called with a bzrlib.merge.Merger object to create a per file "
68
 
            "merge object when starting a merge. "
69
 
            "Should return either None or a subclass of "
70
 
            "``bzrlib.merge.AbstractPerFileMerger``. "
71
 
            "Such objects will then be called per file "
72
 
            "that needs to be merged (including when one "
73
 
            "side has deleted the file and the other has changed it). "
74
 
            "See the AbstractPerFileMerger API docs for details on how it is "
75
 
            "used by merge.",
76
 
            (2, 1))
77
 
 
78
 
 
79
 
class AbstractPerFileMerger(object):
80
 
    """PerFileMerger objects are used by plugins extending merge for bzrlib.
81
 
 
82
 
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
83
 
    
84
 
    :ivar merger: The Merge3Merger performing the merge.
85
 
    """
86
 
 
87
 
    def __init__(self, merger):
88
 
        """Create a PerFileMerger for use with merger."""
89
 
        self.merger = merger
90
 
 
91
 
    def merge_contents(self, merge_params):
92
 
        """Attempt to merge the contents of a single file.
93
 
        
94
 
        :param merge_params: A bzrlib.merge.MergeHookParams
95
 
        :return: A tuple of (status, chunks), where status is one of
96
 
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
97
 
            is 'success' or 'conflicted', then chunks should be an iterable of
98
 
            strings for the new file contents.
99
 
        """
100
 
        return ('not applicable', None)
101
 
 
102
 
 
103
 
class PerFileMerger(AbstractPerFileMerger):
104
 
    """Merge individual files when self.file_matches returns True.
105
 
 
106
 
    This class is intended to be subclassed.  The file_matches and
107
 
    merge_matching methods should be overridden with concrete implementations.
108
 
    """
109
 
 
110
 
    def file_matches(self, params):
111
 
        """Return True if merge_matching should be called on this file.
112
 
 
113
 
        Only called with merges of plain files with no clear winner.
114
 
 
115
 
        Subclasses must override this.
116
 
        """
117
 
        raise NotImplementedError(self.file_matches)
118
 
 
119
 
    def get_filename(self, params, tree):
120
 
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
121
 
        self.merger.this_tree) and a MergeHookParams.
122
 
        """
123
 
        return osutils.basename(tree.id2path(params.file_id))
124
 
 
125
 
    def get_filepath(self, params, tree):
126
 
        """Calculate the path to the file in a tree.
127
 
 
128
 
        :param params: A MergeHookParams describing the file to merge
129
 
        :param tree: a Tree, e.g. self.merger.this_tree.
130
 
        """
131
 
        return tree.id2path(params.file_id)
132
 
 
133
 
    def merge_contents(self, params):
134
 
        """Merge the contents of a single file."""
135
 
        # Check whether this custom merge logic should be used.
136
 
        if (
137
 
            # OTHER is a straight winner, rely on default merge.
138
 
            params.winner == 'other' or
139
 
            # THIS and OTHER aren't both files.
140
 
            not params.is_file_merge() or
141
 
            # The filename doesn't match *.xml
142
 
            not self.file_matches(params)):
143
 
            return 'not_applicable', None
144
 
        return self.merge_matching(params)
145
 
 
146
 
    def merge_matching(self, params):
147
 
        """Merge the contents of a single file that has matched the criteria
148
 
        in PerFileMerger.merge_contents (is a conflict, is a file,
149
 
        self.file_matches is True).
150
 
 
151
 
        Subclasses must override this.
152
 
        """
153
 
        raise NotImplementedError(self.merge_matching)
154
 
 
155
 
 
156
 
class ConfigurableFileMerger(PerFileMerger):
157
 
    """Merge individual files when configured via a .conf file.
158
 
 
159
 
    This is a base class for concrete custom file merging logic. Concrete
160
 
    classes should implement ``merge_text``.
161
 
 
162
 
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
163
 
    
164
 
    :ivar affected_files: The configured file paths to merge.
165
 
 
166
 
    :cvar name_prefix: The prefix to use when looking up configuration
167
 
        details. <name_prefix>_merge_files describes the files targeted by the
168
 
        hook for example.
169
 
        
170
 
    :cvar default_files: The default file paths to merge when no configuration
171
 
        is present.
172
 
    """
173
 
 
174
 
    name_prefix = None
175
 
    default_files = None
176
 
 
177
 
    def __init__(self, merger):
178
 
        super(ConfigurableFileMerger, self).__init__(merger)
179
 
        self.affected_files = None
180
 
        self.default_files = self.__class__.default_files or []
181
 
        self.name_prefix = self.__class__.name_prefix
182
 
        if self.name_prefix is None:
183
 
            raise ValueError("name_prefix must be set.")
184
 
 
185
 
    def file_matches(self, params):
186
 
        """Check whether the file should call the merge hook.
187
 
 
188
 
        <name_prefix>_merge_files configuration variable is a list of files
189
 
        that should use the hook.
190
 
        """
191
 
        affected_files = self.affected_files
192
 
        if affected_files is None:
193
 
            config = self.merger.this_branch.get_config()
194
 
            # Until bzr provides a better policy for caching the config, we
195
 
            # just add the part we're interested in to the params to avoid
196
 
            # reading the config files repeatedly (bazaar.conf, location.conf,
197
 
            # branch.conf).
198
 
            config_key = self.name_prefix + '_merge_files'
199
 
            affected_files = config.get_user_option_as_list(config_key)
200
 
            if affected_files is None:
201
 
                # If nothing was specified in the config, use the default.
202
 
                affected_files = self.default_files
203
 
            self.affected_files = affected_files
204
 
        if affected_files:
205
 
            filepath = self.get_filepath(params, self.merger.this_tree)
206
 
            if filepath in affected_files:
207
 
                return True
208
 
        return False
209
 
 
210
 
    def merge_matching(self, params):
211
 
        return self.merge_text(params)
212
 
 
213
 
    def merge_text(self, params):
214
 
        """Merge the byte contents of a single file.
215
 
 
216
 
        This is called after checking that the merge should be performed in
217
 
        merge_contents, and it should behave as per
218
 
        ``bzrlib.merge.AbstractPerFileMerger.merge_contents``.
219
 
        """
220
 
        raise NotImplementedError(self.merge_text)
 
58
        hooks.Hooks.__init__(self)
 
59
        self.create_hook(hooks.HookPoint('merge_file_content',
 
60
            "Called when file content needs to be merged (including when one "
 
61
            "side has deleted the file and the other has changed it)."
 
62
            "merge_file_content is called with a "
 
63
            "bzrlib.merge.MergeHookParams. The function should return a tuple "
 
64
            "of (status, lines), where status is one of 'not_applicable', "
 
65
            "'success', 'conflicted', or 'delete'.  If status is success or "
 
66
            "conflicted, then lines should be an iterable of strings of the "
 
67
            "new file contents.",
 
68
            (2, 1), None))
221
69
 
222
70
 
223
71
class MergeHookParams(object):
224
72
    """Object holding parameters passed to merge_file_content hooks.
225
73
 
226
 
    There are some fields hooks can access:
 
74
    There are 3 fields hooks can access:
227
75
 
 
76
    :ivar merger: the Merger object
228
77
    :ivar file_id: the file ID of the file being merged
229
78
    :ivar trans_id: the transform ID for the merge of this file
230
79
    :ivar this_kind: kind of file_id in 'this' tree
234
83
 
235
84
    def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
236
85
            winner):
237
 
        self._merger = merger
 
86
        self.merger = merger
238
87
        self.file_id = file_id
239
88
        self.trans_id = trans_id
240
89
        self.this_kind = this_kind
248
97
    @decorators.cachedproperty
249
98
    def base_lines(self):
250
99
        """The lines of the 'base' version of the file."""
251
 
        return self._merger.get_lines(self._merger.base_tree, self.file_id)
 
100
        return self.merger.get_lines(self.merger.base_tree, self.file_id)
252
101
 
253
102
    @decorators.cachedproperty
254
103
    def this_lines(self):
255
104
        """The lines of the 'this' version of the file."""
256
 
        return self._merger.get_lines(self._merger.this_tree, self.file_id)
 
105
        return self.merger.get_lines(self.merger.this_tree, self.file_id)
257
106
 
258
107
    @decorators.cachedproperty
259
108
    def other_lines(self):
260
109
        """The lines of the 'other' version of the file."""
261
 
        return self._merger.get_lines(self._merger.other_tree, self.file_id)
 
110
        return self.merger.get_lines(self.merger.other_tree, self.file_id)
262
111
 
263
112
 
264
113
class Merger(object):
285
134
        self.interesting_files = None
286
135
        self.show_base = False
287
136
        self.reprocess = False
288
 
        if pb is not None:
289
 
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
 
137
        if pb is None:
 
138
            pb = progress.DummyProgress()
 
139
        self._pb = pb
290
140
        self.pp = None
291
141
        self.recurse = recurse
292
142
        self.change_reporter = change_reporter
428
278
        return self._cached_trees[revision_id]
429
279
 
430
280
    def _get_tree(self, treespec, possible_transports=None):
 
281
        from bzrlib import workingtree
431
282
        location, revno = treespec
432
283
        if revno is None:
433
284
            tree = workingtree.WorkingTree.open_containing(location)[0]
458
309
    @deprecated_method(deprecated_in((2, 1, 0)))
459
310
    def file_revisions(self, file_id):
460
311
        self.ensure_revision_trees()
 
312
        def get_id(tree, file_id):
 
313
            revision_id = tree.inventory[file_id].revision
 
314
            return revision_id
461
315
        if self.this_rev_id is None:
462
316
            if self.this_basis_tree.get_file_sha1(file_id) != \
463
317
                self.this_tree.get_file_sha1(file_id):
464
318
                raise errors.WorkingTreeNotRevision(self.this_tree)
465
319
 
466
320
        trees = (self.this_basis_tree, self.other_tree)
467
 
        return [tree.get_file_revision(file_id) for tree in trees]
 
321
        return [get_id(tree, file_id) for tree in trees]
468
322
 
469
323
    @deprecated_method(deprecated_in((2, 1, 0)))
470
324
    def check_basis(self, check_clean, require_commits=True):
498
352
    def _add_parent(self):
499
353
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
500
354
        new_parent_trees = []
501
 
        operation = cleanup.OperationWithCleanups(
502
 
            self.this_tree.set_parent_trees)
503
355
        for revision_id in new_parents:
504
356
            try:
505
357
                tree = self.revision_tree(revision_id)
507
359
                tree = None
508
360
            else:
509
361
                tree.lock_read()
510
 
                operation.add_cleanup(tree.unlock)
511
362
            new_parent_trees.append((revision_id, tree))
512
 
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
 
363
        try:
 
364
            self.this_tree.set_parent_trees(new_parent_trees,
 
365
                                            allow_leftmost_as_ghost=True)
 
366
        finally:
 
367
            for _revision_id, tree in new_parent_trees:
 
368
                if tree is not None:
 
369
                    tree.unlock()
513
370
 
514
371
    def set_other(self, other_revision, possible_transports=None):
515
372
        """Set the revision and tree to merge from.
580
437
            elif len(lcas) == 1:
581
438
                self.base_rev_id = list(lcas)[0]
582
439
            else: # len(lcas) > 1
583
 
                self._is_criss_cross = True
584
440
                if len(lcas) > 2:
585
441
                    # find_unique_lca can only handle 2 nodes, so we have to
586
442
                    # start back at the beginning. It is a shame to traverse
591
447
                else:
592
448
                    self.base_rev_id = self.revision_graph.find_unique_lca(
593
449
                                            *lcas)
594
 
                sorted_lca_keys = self.revision_graph.find_merge_order(                
595
 
                    revisions[0], lcas)
596
 
                if self.base_rev_id == _mod_revision.NULL_REVISION:
597
 
                    self.base_rev_id = sorted_lca_keys[0]
598
 
                
 
450
                self._is_criss_cross = True
599
451
            if self.base_rev_id == _mod_revision.NULL_REVISION:
600
452
                raise errors.UnrelatedBranches()
601
453
            if self._is_criss_cross:
602
454
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
603
455
                              ' help criss-cross.')
604
456
                trace.mutter('Criss-cross lcas: %r' % lcas)
605
 
                if self.base_rev_id in lcas:
606
 
                    trace.mutter('Unable to find unique lca. '
607
 
                                 'Fallback %r as best option.' % self.base_rev_id)
608
 
                interesting_revision_ids = set(lcas)
609
 
                interesting_revision_ids.add(self.base_rev_id)
 
457
                interesting_revision_ids = [self.base_rev_id]
 
458
                interesting_revision_ids.extend(lcas)
610
459
                interesting_trees = dict((t.get_revision_id(), t)
611
460
                    for t in self.this_branch.repository.revision_trees(
612
461
                        interesting_revision_ids))
613
462
                self._cached_trees.update(interesting_trees)
614
 
                if self.base_rev_id in lcas:
615
 
                    self.base_tree = interesting_trees[self.base_rev_id]
616
 
                else:
617
 
                    self.base_tree = interesting_trees.pop(self.base_rev_id)
 
463
                self.base_tree = interesting_trees.pop(self.base_rev_id)
 
464
                sorted_lca_keys = self.revision_graph.find_merge_order(
 
465
                    revisions[0], lcas)
618
466
                self._lca_trees = [interesting_trees[key]
619
467
                                   for key in sorted_lca_keys]
620
468
            else:
647
495
                  'other_tree': self.other_tree,
648
496
                  'interesting_ids': self.interesting_ids,
649
497
                  'interesting_files': self.interesting_files,
650
 
                  'this_branch': self.this_branch,
 
498
                  'pp': self.pp, 'this_branch': self.this_branch,
651
499
                  'do_merge': False}
652
500
        if self.merge_type.requires_base:
653
501
            kwargs['base_tree'] = self.base_tree
671
519
        if self._is_criss_cross and getattr(self.merge_type,
672
520
                                            'supports_lca_trees', False):
673
521
            kwargs['lca_trees'] = self._lca_trees
674
 
        return self.merge_type(pb=None,
 
522
        return self.merge_type(pb=self._pb,
675
523
                               change_reporter=self.change_reporter,
676
524
                               **kwargs)
677
525
 
678
 
    def _do_merge_to(self):
679
 
        merge = self.make_merger()
 
526
    def _do_merge_to(self, merge):
680
527
        if self.other_branch is not None:
681
528
            self.other_branch.update_references(self.this_branch)
682
529
        merge.do_merge()
696
543
                    sub_tree.branch.repository.revision_tree(base_revision)
697
544
                sub_merge.base_rev_id = base_revision
698
545
                sub_merge.do_merge()
699
 
        return merge
700
546
 
701
547
    def do_merge(self):
702
 
        operation = cleanup.OperationWithCleanups(self._do_merge_to)
703
548
        self.this_tree.lock_tree_write()
704
 
        operation.add_cleanup(self.this_tree.unlock)
705
 
        if self.base_tree is not None:
706
 
            self.base_tree.lock_read()
707
 
            operation.add_cleanup(self.base_tree.unlock)
708
 
        if self.other_tree is not None:
709
 
            self.other_tree.lock_read()
710
 
            operation.add_cleanup(self.other_tree.unlock)
711
 
        merge = operation.run_simple()
 
549
        try:
 
550
            if self.base_tree is not None:
 
551
                self.base_tree.lock_read()
 
552
            try:
 
553
                if self.other_tree is not None:
 
554
                    self.other_tree.lock_read()
 
555
                try:
 
556
                    merge = self.make_merger()
 
557
                    self._do_merge_to(merge)
 
558
                finally:
 
559
                    if self.other_tree is not None:
 
560
                        self.other_tree.unlock()
 
561
            finally:
 
562
                if self.base_tree is not None:
 
563
                    self.base_tree.unlock()
 
564
        finally:
 
565
            self.this_tree.unlock()
712
566
        if len(merge.cooked_conflicts) == 0:
713
567
            if not self.ignore_zero and not trace.is_quiet():
714
568
                trace.note("All changes applied successfully.")
749
603
 
750
604
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
751
605
                 interesting_ids=None, reprocess=False, show_base=False,
752
 
                 pb=None, pp=None, change_reporter=None,
 
606
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
753
607
                 interesting_files=None, do_merge=True,
754
608
                 cherrypick=False, lca_trees=None, this_branch=None):
755
609
        """Initialize the merger object and perform the merge.
758
612
        :param this_tree: The local tree in the merge operation
759
613
        :param base_tree: The common tree in the merge operation
760
614
        :param other_tree: The other tree to merge changes from
761
 
        :param this_branch: The branch associated with this_tree.  Defaults to
762
 
            this_tree.branch if not supplied.
 
615
        :param this_branch: The branch associated with this_tree
763
616
        :param interesting_ids: The file_ids of files that should be
764
617
            participate in the merge.  May not be combined with
765
618
            interesting_files.
766
619
        :param: reprocess If True, perform conflict-reduction processing.
767
620
        :param show_base: If True, show the base revision in text conflicts.
768
621
            (incompatible with reprocess)
769
 
        :param pb: ignored
 
622
        :param pb: A Progress bar
770
623
        :param pp: A ProgressPhase object
771
624
        :param change_reporter: An object that should report changes made
772
625
        :param interesting_files: The tree-relative paths of files that should
783
636
        if interesting_files is not None and interesting_ids is not None:
784
637
            raise ValueError(
785
638
                'specify either interesting_ids or interesting_files')
786
 
        if this_branch is None:
787
 
            this_branch = this_tree.branch
788
639
        self.interesting_ids = interesting_ids
789
640
        self.interesting_files = interesting_files
790
641
        self.this_tree = working_tree
801
652
        # making sure we haven't missed any corner cases.
802
653
        # if lca_trees is None:
803
654
        #     self._lca_trees = [self.base_tree]
 
655
        self.pb = pb
 
656
        self.pp = pp
804
657
        self.change_reporter = change_reporter
805
658
        self.cherrypick = cherrypick
 
659
        if self.pp is None:
 
660
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
806
661
        if do_merge:
807
662
            self.do_merge()
808
 
        if pp is not None:
809
 
            warnings.warn("pp argument to Merge3Merger is deprecated")
810
 
        if pb is not None:
811
 
            warnings.warn("pb argument to Merge3Merger is deprecated")
812
663
 
813
664
    def do_merge(self):
814
 
        operation = cleanup.OperationWithCleanups(self._do_merge)
815
665
        self.this_tree.lock_tree_write()
816
 
        operation.add_cleanup(self.this_tree.unlock)
817
666
        self.base_tree.lock_read()
818
 
        operation.add_cleanup(self.base_tree.unlock)
819
667
        self.other_tree.lock_read()
820
 
        operation.add_cleanup(self.other_tree.unlock)
821
 
        operation.run()
822
 
 
823
 
    def _do_merge(self, operation):
824
 
        self.tt = transform.TreeTransform(self.this_tree, None)
825
 
        operation.add_cleanup(self.tt.finalize)
826
 
        self._compute_transform()
827
 
        results = self.tt.apply(no_conflicts=True)
828
 
        self.write_modified(results)
829
668
        try:
830
 
            self.this_tree.add_conflicts(self.cooked_conflicts)
831
 
        except errors.UnsupportedOperation:
832
 
            pass
 
669
            self.tt = transform.TreeTransform(self.this_tree, self.pb)
 
670
            try:
 
671
                self.pp.next_phase()
 
672
                self._compute_transform()
 
673
                self.pp.next_phase()
 
674
                results = self.tt.apply(no_conflicts=True)
 
675
                self.write_modified(results)
 
676
                try:
 
677
                    self.this_tree.add_conflicts(self.cooked_conflicts)
 
678
                except errors.UnsupportedOperation:
 
679
                    pass
 
680
            finally:
 
681
                self.tt.finalize()
 
682
        finally:
 
683
            self.other_tree.unlock()
 
684
            self.base_tree.unlock()
 
685
            self.this_tree.unlock()
 
686
            self.pb.clear()
833
687
 
834
688
    def make_preview_transform(self):
835
 
        operation = cleanup.OperationWithCleanups(self._make_preview_transform)
836
689
        self.base_tree.lock_read()
837
 
        operation.add_cleanup(self.base_tree.unlock)
838
690
        self.other_tree.lock_read()
839
 
        operation.add_cleanup(self.other_tree.unlock)
840
 
        return operation.run_simple()
841
 
 
842
 
    def _make_preview_transform(self):
843
691
        self.tt = transform.TransformPreview(self.this_tree)
844
 
        self._compute_transform()
 
692
        try:
 
693
            self.pp.next_phase()
 
694
            self._compute_transform()
 
695
            self.pp.next_phase()
 
696
        finally:
 
697
            self.other_tree.unlock()
 
698
            self.base_tree.unlock()
 
699
            self.pb.clear()
845
700
        return self.tt
846
701
 
847
702
    def _compute_transform(self):
853
708
            resolver = self._lca_multi_way
854
709
        child_pb = ui.ui_factory.nested_progress_bar()
855
710
        try:
856
 
            factories = Merger.hooks['merge_file_content']
857
 
            hooks = [factory(self) for factory in factories] + [self]
858
 
            self.active_hooks = [hook for hook in hooks if hook is not None]
859
711
            for num, (file_id, changed, parents3, names3,
860
712
                      executable3) in enumerate(entries):
861
713
                child_pb.update('Preparing file merge', num, len(entries))
862
714
                self._merge_names(file_id, parents3, names3, resolver=resolver)
863
715
                if changed:
864
 
                    file_status = self._do_merge_contents(file_id)
 
716
                    file_status = self.merge_contents(file_id)
865
717
                else:
866
718
                    file_status = 'unmodified'
867
719
                self._merge_executable(file_id,
868
720
                    executable3, file_status, resolver=resolver)
869
721
        finally:
870
722
            child_pb.finished()
871
 
        self.tt.fixup_new_roots()
872
 
        self._finish_computing_transform()
873
 
 
874
 
    def _finish_computing_transform(self):
875
 
        """Finalize the transform and report the changes.
876
 
 
877
 
        This is the second half of _compute_transform.
878
 
        """
 
723
        self.fix_root()
 
724
        self.pp.next_phase()
879
725
        child_pb = ui.ui_factory.nested_progress_bar()
880
726
        try:
881
727
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
888
734
                self.tt.iter_changes(), self.change_reporter)
889
735
        self.cook_conflicts(fs_conflicts)
890
736
        for conflict in self.cooked_conflicts:
891
 
            trace.warning(unicode(conflict))
 
737
            trace.warning(conflict)
892
738
 
893
739
    def _entries3(self):
894
740
        """Gather data about files modified between three trees.
901
747
        """
902
748
        result = []
903
749
        iterator = self.other_tree.iter_changes(self.base_tree,
904
 
                specific_files=self.interesting_files,
 
750
                include_unchanged=True, specific_files=self.interesting_files,
905
751
                extra_trees=[self.this_tree])
906
752
        this_entries = dict((e.file_id, e) for p, e in
907
753
                            self.this_tree.iter_entries_by_dir(
933
779
        it then compares with THIS and BASE.
934
780
 
935
781
        For the multi-valued entries, the format will be (BASE, [lca1, lca2])
936
 
 
937
 
        :return: [(file_id, changed, parents, names, executable)], where:
938
 
 
939
 
            * file_id: Simple file_id of the entry
940
 
            * changed: Boolean, True if the kind or contents changed else False
941
 
            * parents: ((base, [parent_id, in, lcas]), parent_id_other,
942
 
                        parent_id_this)
943
 
            * names:   ((base, [name, in, lcas]), name_in_other, name_in_this)
944
 
            * executable: ((base, [exec, in, lcas]), exec_in_other,
945
 
                        exec_in_this)
 
782
        :return: [(file_id, changed, parents, names, executable)]
 
783
            file_id     Simple file_id of the entry
 
784
            changed     Boolean, True if the kind or contents changed
 
785
                        else False
 
786
            parents     ((base, [parent_id, in, lcas]), parent_id_other,
 
787
                         parent_id_this)
 
788
            names       ((base, [name, in, lcas]), name_in_other, name_in_this)
 
789
            executable  ((base, [exec, in, lcas]), exec_in_other, exec_in_this)
946
790
        """
947
791
        if self.interesting_files is not None:
948
792
            lookup_trees = [self.this_tree, self.base_tree]
990
834
                else:
991
835
                    lca_entries.append(lca_ie)
992
836
 
993
 
            if base_inventory.has_id(file_id):
 
837
            if file_id in base_inventory:
994
838
                base_ie = base_inventory[file_id]
995
839
            else:
996
840
                base_ie = _none_entry
997
841
 
998
 
            if this_inventory.has_id(file_id):
 
842
            if file_id in this_inventory:
999
843
                this_ie = this_inventory[file_id]
1000
844
            else:
1001
845
                this_ie = _none_entry
1081
925
                        continue
1082
926
                else:
1083
927
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
928
                # XXX: We need to handle kind == 'symlink'
1084
929
 
1085
930
            # If we have gotten this far, that means something has changed
1086
931
            result.append((file_id, content_changed,
1093
938
                          ))
1094
939
        return result
1095
940
 
1096
 
    @deprecated_method(deprecated_in((2, 4, 0)))
 
941
 
1097
942
    def fix_root(self):
1098
 
        if self.tt.final_kind(self.tt.root) is None:
 
943
        try:
 
944
            self.tt.final_kind(self.tt.root)
 
945
        except errors.NoSuchFile:
1099
946
            self.tt.cancel_deletion(self.tt.root)
1100
947
        if self.tt.final_file_id(self.tt.root) is None:
1101
948
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1106
953
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1107
954
        if other_root == self.tt.root:
1108
955
            return
1109
 
        if self.this_tree.inventory.has_id(
1110
 
            self.other_tree.inventory.root.file_id):
1111
 
            # the other tree's root is a non-root in the current tree (as
1112
 
            # when a previously unrelated branch is merged into another)
1113
 
            return
1114
 
        if self.tt.final_kind(other_root) is not None:
1115
 
            other_root_is_present = True
1116
 
        else:
1117
 
            # other_root doesn't have a physical representation. We still need
1118
 
            # to move any references to the actual root of the tree.
1119
 
            other_root_is_present = False
1120
 
        # 'other_tree.inventory.root' is not present in this tree. We are
1121
 
        # calling adjust_path for children which *want* to be present with a
1122
 
        # correct place to go.
1123
 
        for _, child in self.other_tree.inventory.root.children.iteritems():
 
956
        try:
 
957
            self.tt.final_kind(other_root)
 
958
        except errors.NoSuchFile:
 
959
            return
 
960
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
 
961
            # the other tree's root is a non-root in the current tree
 
962
            return
 
963
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
964
        self.tt.cancel_creation(other_root)
 
965
        self.tt.cancel_versioning(other_root)
 
966
 
 
967
    def reparent_children(self, ie, target):
 
968
        for thing, child in ie.children.iteritems():
1124
969
            trans_id = self.tt.trans_id_file_id(child.file_id)
1125
 
            if not other_root_is_present:
1126
 
                if self.tt.final_kind(trans_id) is not None:
1127
 
                    # The item exist in the final tree and has a defined place
1128
 
                    # to go already.
1129
 
                    continue
1130
 
            # Move the item into the root
1131
 
            try:
1132
 
                final_name = self.tt.final_name(trans_id)
1133
 
            except errors.NoFinalPath:
1134
 
                # This file is not present anymore, ignore it.
1135
 
                continue
1136
 
            self.tt.adjust_path(final_name, self.tt.root, trans_id)
1137
 
        if other_root_is_present:
1138
 
            self.tt.cancel_creation(other_root)
1139
 
            self.tt.cancel_versioning(other_root)
 
970
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1140
971
 
1141
972
    def write_modified(self, results):
1142
973
        modified_hashes = {}
1167
998
    @staticmethod
1168
999
    def contents_sha1(tree, file_id):
1169
1000
        """Determine the sha1 of the file contents (used as a key method)."""
1170
 
        if not tree.has_id(file_id):
 
1001
        if file_id not in tree:
1171
1002
            return None
1172
1003
        return tree.get_file_sha1(file_id)
1173
1004
 
1189
1020
 
1190
1021
    @staticmethod
1191
1022
    def _three_way(base, other, this):
 
1023
        #if base == other, either they all agree, or only THIS has changed.
1192
1024
        if base == other:
1193
 
            # if 'base == other', either they all agree, or only 'this' has
1194
 
            # changed.
1195
1025
            return 'this'
1196
1026
        elif this not in (base, other):
1197
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1198
1027
            return 'conflict'
 
1028
        # "Ambiguous clean merge" -- both sides have made the same change.
1199
1029
        elif this == other:
1200
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1201
1030
            return "this"
 
1031
        # this == base: only other has changed.
1202
1032
        else:
1203
 
            # this == base: only other has changed.
1204
1033
            return "other"
1205
1034
 
1206
1035
    @staticmethod
1250
1079
                # only has an lca value
1251
1080
                return 'other'
1252
1081
 
1253
 
        # At this point, the lcas disagree, and the tip disagree
 
1082
        # At this point, the lcas disagree, and the tips disagree
1254
1083
        return 'conflict'
1255
1084
 
1256
1085
    @staticmethod
1257
 
    @deprecated_method(deprecated_in((2, 2, 0)))
1258
1086
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1259
1087
        """Do a three-way test on a scalar.
1260
1088
        Return "this", "other" or "conflict", depending whether a value wins.
1310
1138
                parent_id_winner = "other"
1311
1139
        if name_winner == "this" and parent_id_winner == "this":
1312
1140
            return
1313
 
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
1314
 
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1315
 
            # road if a ContentConflict needs to be created so we should not do
1316
 
            # that
1317
 
            trans_id = self.tt.trans_id_file_id(file_id)
1318
 
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1319
 
                                        this_parent, this_name,
1320
 
                                        other_parent, other_name))
1321
 
        if not self.other_tree.has_id(file_id):
 
1141
        if name_winner == "conflict":
 
1142
            trans_id = self.tt.trans_id_file_id(file_id)
 
1143
            self._raw_conflicts.append(('name conflict', trans_id,
 
1144
                                        this_name, other_name))
 
1145
        if parent_id_winner == "conflict":
 
1146
            trans_id = self.tt.trans_id_file_id(file_id)
 
1147
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1148
                                        this_parent, other_parent))
 
1149
        if other_name is None:
1322
1150
            # it doesn't matter whether the result was 'other' or
1323
 
            # 'conflict'-- if it has no file id, we leave it alone.
 
1151
            # 'conflict'-- if there's no 'other', we leave it alone.
1324
1152
            return
 
1153
        # if we get here, name_winner and parent_winner are set to safe values.
 
1154
        trans_id = self.tt.trans_id_file_id(file_id)
1325
1155
        parent_id = parents[self.winner_idx[parent_id_winner]]
1326
 
        name = names[self.winner_idx[name_winner]]
1327
 
        if parent_id is not None or name is not None:
1328
 
            # if we get here, name_winner and parent_winner are set to safe
1329
 
            # values.
1330
 
            if parent_id is None and name is not None:
1331
 
                # if parent_id is None and name is non-None, current file is
1332
 
                # the tree root.
1333
 
                if names[self.winner_idx[parent_id_winner]] != '':
1334
 
                    raise AssertionError(
1335
 
                        'File looks like a root, but named %s' %
1336
 
                        names[self.winner_idx[parent_id_winner]])
1337
 
                parent_trans_id = transform.ROOT_PARENT
1338
 
            else:
1339
 
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1340
 
            self.tt.adjust_path(name, parent_trans_id,
1341
 
                                self.tt.trans_id_file_id(file_id))
 
1156
        if parent_id is not None:
 
1157
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1158
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
 
1159
                                parent_trans_id, trans_id)
1342
1160
 
1343
 
    def _do_merge_contents(self, file_id):
 
1161
    def merge_contents(self, file_id):
1344
1162
        """Performs a merge on file_id contents."""
1345
1163
        def contents_pair(tree):
1346
 
            if not tree.has_id(file_id):
 
1164
            if file_id not in tree:
1347
1165
                return (None, None)
1348
1166
            kind = tree.kind(file_id)
1349
1167
            if kind == "file":
1380
1198
        trans_id = self.tt.trans_id_file_id(file_id)
1381
1199
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1382
1200
            other_pair[0], winner)
1383
 
        hooks = self.active_hooks
 
1201
        hooks = Merger.hooks['merge_file_content']
 
1202
        hooks = list(hooks) + [self.default_text_merge]
1384
1203
        hook_status = 'not_applicable'
1385
1204
        for hook in hooks:
1386
 
            hook_status, lines = hook.merge_contents(params)
 
1205
            hook_status, lines = hook(params)
1387
1206
            if hook_status != 'not_applicable':
1388
1207
                # Don't try any more hooks, this one applies.
1389
1208
                break
1422
1241
            self.tt.version_file(file_id, trans_id)
1423
1242
        # The merge has been performed, so the old contents should not be
1424
1243
        # retained.
1425
 
        self.tt.delete_contents(trans_id)
 
1244
        try:
 
1245
            self.tt.delete_contents(trans_id)
 
1246
        except errors.NoSuchFile:
 
1247
            pass
1426
1248
        return result
1427
1249
 
1428
1250
    def _default_other_winner_merge(self, merge_hook_params):
1457
1279
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
1458
1280
                % (file_id,))
1459
1281
 
1460
 
    def merge_contents(self, merge_hook_params):
1461
 
        """Fallback merge logic after user installed hooks."""
1462
 
        # This function is used in merge hooks as the fallback instance.
1463
 
        # Perhaps making this function and the functions it calls be a 
1464
 
        # a separate class would be better.
 
1282
    def default_text_merge(self, merge_hook_params):
1465
1283
        if merge_hook_params.winner == 'other':
1466
1284
            # OTHER is a straight winner, so replace this contents with other
1467
1285
            return self._default_other_winner_merge(merge_hook_params)
1481
1299
    def get_lines(self, tree, file_id):
1482
1300
        """Return the lines in a file, or an empty list."""
1483
1301
        if tree.has_id(file_id):
1484
 
            return tree.get_file_lines(file_id)
 
1302
            return tree.get_file(file_id).readlines()
1485
1303
        else:
1486
1304
            return []
1487
1305
 
1600
1418
        if winner == 'this' and file_status != "modified":
1601
1419
            return
1602
1420
        trans_id = self.tt.trans_id_file_id(file_id)
1603
 
        if self.tt.final_kind(trans_id) != "file":
 
1421
        try:
 
1422
            if self.tt.final_kind(trans_id) != "file":
 
1423
                return
 
1424
        except errors.NoSuchFile:
1604
1425
            return
1605
1426
        if winner == "this":
1606
1427
            executability = this_executable
1617
1438
 
1618
1439
    def cook_conflicts(self, fs_conflicts):
1619
1440
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1620
 
        content_conflict_file_ids = set()
1621
 
        cooked_conflicts = transform.cook_conflicts(fs_conflicts, self.tt)
 
1441
        name_conflicts = {}
 
1442
        self.cooked_conflicts.extend(transform.cook_conflicts(
 
1443
                fs_conflicts, self.tt))
1622
1444
        fp = transform.FinalPaths(self.tt)
1623
1445
        for conflict in self._raw_conflicts:
1624
1446
            conflict_type = conflict[0]
1625
 
            if conflict_type == 'path conflict':
1626
 
                (trans_id, file_id,
1627
 
                this_parent, this_name,
1628
 
                other_parent, other_name) = conflict[1:]
1629
 
                if this_parent is None or this_name is None:
1630
 
                    this_path = '<deleted>'
1631
 
                else:
1632
 
                    parent_path =  fp.get_path(
1633
 
                        self.tt.trans_id_file_id(this_parent))
1634
 
                    this_path = osutils.pathjoin(parent_path, this_name)
1635
 
                if other_parent is None or other_name is None:
1636
 
                    other_path = '<deleted>'
1637
 
                else:
1638
 
                    if other_parent == self.other_tree.get_root_id():
1639
 
                        # The tree transform doesn't know about the other root,
1640
 
                        # so we special case here to avoid a NoFinalPath
1641
 
                        # exception
1642
 
                        parent_path = ''
1643
 
                    else:
1644
 
                        parent_path =  fp.get_path(
1645
 
                            self.tt.trans_id_file_id(other_parent))
1646
 
                    other_path = osutils.pathjoin(parent_path, other_name)
1647
 
                c = _mod_conflicts.Conflict.factory(
1648
 
                    'path conflict', path=this_path,
1649
 
                    conflict_path=other_path,
1650
 
                    file_id=file_id)
1651
 
            elif conflict_type == 'contents conflict':
 
1447
            if conflict_type in ('name conflict', 'parent conflict'):
 
1448
                trans_id = conflict[1]
 
1449
                conflict_args = conflict[2:]
 
1450
                if trans_id not in name_conflicts:
 
1451
                    name_conflicts[trans_id] = {}
 
1452
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1453
                                     conflict_args)
 
1454
            if conflict_type == 'contents conflict':
1652
1455
                for trans_id in conflict[1]:
1653
1456
                    file_id = self.tt.final_file_id(trans_id)
1654
1457
                    if file_id is not None:
1660
1463
                        break
1661
1464
                c = _mod_conflicts.Conflict.factory(conflict_type,
1662
1465
                                                    path=path, file_id=file_id)
1663
 
                content_conflict_file_ids.add(file_id)
1664
 
            elif conflict_type == 'text conflict':
 
1466
                self.cooked_conflicts.append(c)
 
1467
            if conflict_type == 'text conflict':
1665
1468
                trans_id = conflict[1]
1666
1469
                path = fp.get_path(trans_id)
1667
1470
                file_id = self.tt.final_file_id(trans_id)
1668
1471
                c = _mod_conflicts.Conflict.factory(conflict_type,
1669
1472
                                                    path=path, file_id=file_id)
 
1473
                self.cooked_conflicts.append(c)
 
1474
 
 
1475
        for trans_id, conflicts in name_conflicts.iteritems():
 
1476
            try:
 
1477
                this_parent, other_parent = conflicts['parent conflict']
 
1478
                if this_parent == other_parent:
 
1479
                    raise AssertionError()
 
1480
            except KeyError:
 
1481
                this_parent = other_parent = \
 
1482
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1483
            try:
 
1484
                this_name, other_name = conflicts['name conflict']
 
1485
                if this_name == other_name:
 
1486
                    raise AssertionError()
 
1487
            except KeyError:
 
1488
                this_name = other_name = self.tt.final_name(trans_id)
 
1489
            other_path = fp.get_path(trans_id)
 
1490
            if this_parent is not None and this_name is not None:
 
1491
                this_parent_path = \
 
1492
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1493
                this_path = osutils.pathjoin(this_parent_path, this_name)
1670
1494
            else:
1671
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
1672
 
            cooked_conflicts.append(c)
1673
 
 
1674
 
        self.cooked_conflicts = []
1675
 
        # We want to get rid of path conflicts when a corresponding contents
1676
 
        # conflict exists. This can occur when one branch deletes a file while
1677
 
        # the other renames *and* modifies it. In this case, the content
1678
 
        # conflict is enough.
1679
 
        for c in cooked_conflicts:
1680
 
            if (c.typestring == 'path conflict'
1681
 
                and c.file_id in content_conflict_file_ids):
1682
 
                continue
 
1495
                this_path = "<deleted>"
 
1496
            file_id = self.tt.final_file_id(trans_id)
 
1497
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1498
                                                conflict_path=other_path,
 
1499
                                                file_id=file_id)
1683
1500
            self.cooked_conflicts.append(c)
1684
1501
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1685
1502
 
1791
1608
            osutils.rmtree(temp_dir)
1792
1609
 
1793
1610
 
1794
 
class PathNotInTree(errors.BzrError):
1795
 
 
1796
 
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
1797
 
 
1798
 
    def __init__(self, path, tree):
1799
 
        errors.BzrError.__init__(self, path=path, tree=tree)
1800
 
 
1801
 
 
1802
 
class MergeIntoMerger(Merger):
1803
 
    """Merger that understands other_tree will be merged into a subdir.
1804
 
 
1805
 
    This also changes the Merger api so that it uses real Branch, revision_id,
1806
 
    and RevisonTree objects, rather than using revision specs.
1807
 
    """
1808
 
 
1809
 
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1810
 
            source_subpath, other_rev_id=None):
1811
 
        """Create a new MergeIntoMerger object.
1812
 
 
1813
 
        source_subpath in other_tree will be effectively copied to
1814
 
        target_subdir in this_tree.
1815
 
 
1816
 
        :param this_tree: The tree that we will be merging into.
1817
 
        :param other_branch: The Branch we will be merging from.
1818
 
        :param other_tree: The RevisionTree object we want to merge.
1819
 
        :param target_subdir: The relative path where we want to merge
1820
 
            other_tree into this_tree
1821
 
        :param source_subpath: The relative path specifying the subtree of
1822
 
            other_tree to merge into this_tree.
1823
 
        """
1824
 
        # It is assumed that we are merging a tree that is not in our current
1825
 
        # ancestry, which means we are using the "EmptyTree" as our basis.
1826
 
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
1827
 
                                _mod_revision.NULL_REVISION)
1828
 
        super(MergeIntoMerger, self).__init__(
1829
 
            this_branch=this_tree.branch,
1830
 
            this_tree=this_tree,
1831
 
            other_tree=other_tree,
1832
 
            base_tree=null_ancestor_tree,
1833
 
            )
1834
 
        self._target_subdir = target_subdir
1835
 
        self._source_subpath = source_subpath
1836
 
        self.other_branch = other_branch
1837
 
        if other_rev_id is None:
1838
 
            other_rev_id = other_tree.get_revision_id()
1839
 
        self.other_rev_id = self.other_basis = other_rev_id
1840
 
        self.base_is_ancestor = True
1841
 
        self.backup_files = True
1842
 
        self.merge_type = Merge3Merger
1843
 
        self.show_base = False
1844
 
        self.reprocess = False
1845
 
        self.interesting_ids = None
1846
 
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1847
 
              target_subdir=self._target_subdir,
1848
 
              source_subpath=self._source_subpath)
1849
 
        if self._source_subpath != '':
1850
 
            # If this isn't a partial merge make sure the revisions will be
1851
 
            # present.
1852
 
            self._maybe_fetch(self.other_branch, self.this_branch,
1853
 
                self.other_basis)
1854
 
 
1855
 
    def set_pending(self):
1856
 
        if self._source_subpath != '':
1857
 
            return
1858
 
        Merger.set_pending(self)
1859
 
 
1860
 
 
1861
 
class _MergeTypeParameterizer(object):
1862
 
    """Wrap a merge-type class to provide extra parameters.
1863
 
    
1864
 
    This is hack used by MergeIntoMerger to pass some extra parameters to its
1865
 
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
1866
 
    the 'merge_type' member.  It is difficult override do_merge without
1867
 
    re-writing the whole thing, so instead we create a wrapper which will pass
1868
 
    the extra parameters.
1869
 
    """
1870
 
 
1871
 
    def __init__(self, merge_type, **kwargs):
1872
 
        self._extra_kwargs = kwargs
1873
 
        self._merge_type = merge_type
1874
 
 
1875
 
    def __call__(self, *args, **kwargs):
1876
 
        kwargs.update(self._extra_kwargs)
1877
 
        return self._merge_type(*args, **kwargs)
1878
 
 
1879
 
    def __getattr__(self, name):
1880
 
        return getattr(self._merge_type, name)
1881
 
 
1882
 
 
1883
 
class MergeIntoMergeType(Merge3Merger):
1884
 
    """Merger that incorporates a tree (or part of a tree) into another."""
1885
 
 
1886
 
    def __init__(self, *args, **kwargs):
1887
 
        """Initialize the merger object.
1888
 
 
1889
 
        :param args: See Merge3Merger.__init__'s args.
1890
 
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
1891
 
            source_subpath and target_subdir.
1892
 
        :keyword source_subpath: The relative path specifying the subtree of
1893
 
            other_tree to merge into this_tree.
1894
 
        :keyword target_subdir: The relative path where we want to merge
1895
 
            other_tree into this_tree
1896
 
        """
1897
 
        # All of the interesting work happens during Merge3Merger.__init__(),
1898
 
        # so we have have to hack in to get our extra parameters set.
1899
 
        self._source_subpath = kwargs.pop('source_subpath')
1900
 
        self._target_subdir = kwargs.pop('target_subdir')
1901
 
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1902
 
 
1903
 
    def _compute_transform(self):
1904
 
        child_pb = ui.ui_factory.nested_progress_bar()
1905
 
        try:
1906
 
            entries = self._entries_to_incorporate()
1907
 
            entries = list(entries)
1908
 
            for num, (entry, parent_id) in enumerate(entries):
1909
 
                child_pb.update('Preparing file merge', num, len(entries))
1910
 
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1911
 
                trans_id = transform.new_by_entry(self.tt, entry,
1912
 
                    parent_trans_id, self.other_tree)
1913
 
        finally:
1914
 
            child_pb.finished()
1915
 
        self._finish_computing_transform()
1916
 
 
1917
 
    def _entries_to_incorporate(self):
1918
 
        """Yields pairs of (inventory_entry, new_parent)."""
1919
 
        other_inv = self.other_tree.inventory
1920
 
        subdir_id = other_inv.path2id(self._source_subpath)
1921
 
        if subdir_id is None:
1922
 
            # XXX: The error would be clearer if it gave the URL of the source
1923
 
            # branch, but we don't have a reference to that here.
1924
 
            raise PathNotInTree(self._source_subpath, "Source tree")
1925
 
        subdir = other_inv[subdir_id]
1926
 
        parent_in_target = osutils.dirname(self._target_subdir)
1927
 
        target_id = self.this_tree.inventory.path2id(parent_in_target)
1928
 
        if target_id is None:
1929
 
            raise PathNotInTree(self._target_subdir, "Target tree")
1930
 
        name_in_target = osutils.basename(self._target_subdir)
1931
 
        merge_into_root = subdir.copy()
1932
 
        merge_into_root.name = name_in_target
1933
 
        if self.this_tree.inventory.has_id(merge_into_root.file_id):
1934
 
            # Give the root a new file-id.
1935
 
            # This can happen fairly easily if the directory we are
1936
 
            # incorporating is the root, and both trees have 'TREE_ROOT' as
1937
 
            # their root_id.  Users will expect this to Just Work, so we
1938
 
            # change the file-id here.
1939
 
            # Non-root file-ids could potentially conflict too.  That's really
1940
 
            # an edge case, so we don't do anything special for those.  We let
1941
 
            # them cause conflicts.
1942
 
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
1943
 
        yield (merge_into_root, target_id)
1944
 
        if subdir.kind != 'directory':
1945
 
            # No children, so we are done.
1946
 
            return
1947
 
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
1948
 
            parent_id = entry.parent_id
1949
 
            if parent_id == subdir.file_id:
1950
 
                # The root's parent ID has changed, so make sure children of
1951
 
                # the root refer to the new ID.
1952
 
                parent_id = merge_into_root.file_id
1953
 
            yield (entry, parent_id)
1954
 
 
1955
 
 
1956
1611
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1957
1612
                backup_files=False,
1958
1613
                merge_type=Merge3Merger,
1962
1617
                other_rev_id=None,
1963
1618
                interesting_files=None,
1964
1619
                this_tree=None,
1965
 
                pb=None,
 
1620
                pb=progress.DummyProgress(),
1966
1621
                change_reporter=None):
1967
1622
    """Primary interface for merging.
1968
1623
 
1969
 
    Typical use is probably::
1970
 
 
1971
 
        merge_inner(branch, branch.get_revision_tree(other_revision),
1972
 
                    branch.get_revision_tree(base_revision))
1973
 
    """
 
1624
        typical use is probably
 
1625
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
1626
                     branch.get_revision_tree(base_revision))'
 
1627
        """
1974
1628
    if this_tree is None:
1975
1629
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1976
 
                              "parameter")
 
1630
                              "parameter as of bzrlib version 0.8.")
1977
1631
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1978
1632
                    pb=pb, change_reporter=change_reporter)
1979
1633
    merger.backup_files = backup_files
2433
2087
class _PlanLCAMerge(_PlanMergeBase):
2434
2088
    """
2435
2089
    This merge algorithm differs from _PlanMerge in that:
2436
 
 
2437
2090
    1. comparisons are done against LCAs only
2438
2091
    2. cases where a contested line is new versus one LCA but old versus
2439
2092
       another are marked as conflicts, by emitting the line as conflicted-a
2480
2133
 
2481
2134
        If a line is killed and new, this indicates that the two merge
2482
2135
        revisions contain differing conflict resolutions.
2483
 
 
2484
2136
        :param revision_id: The id of the revision in which the lines are
2485
2137
            unique
2486
2138
        :param unique_line_numbers: The line numbers of unique lines.
2487
 
        :return: a tuple of (new_this, killed_other)
 
2139
        :return a tuple of (new_this, killed_other):
2488
2140
        """
2489
2141
        new = set()
2490
2142
        killed = set()