/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Andrew Bennetts
  • Date: 2010-01-12 03:53:21 UTC
  • mfrom: (4948 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4964.
  • Revision ID: andrew.bennetts@canonical.com-20100112035321-hofpz5p10224ryj3
Merge lp:bzr, resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
import errno
19
 
from itertools import chain
20
 
import os
21
 
import warnings
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
22
17
 
23
18
from bzrlib import (
 
19
    branch as _mod_branch,
 
20
    conflicts as _mod_conflicts,
24
21
    debug,
25
22
    errors,
26
23
    graph as _mod_graph,
 
24
    merge3,
27
25
    osutils,
28
26
    patiencediff,
29
 
    registry,
 
27
    progress,
30
28
    revision as _mod_revision,
 
29
    textfile,
 
30
    trace,
 
31
    transform,
31
32
    tree as _mod_tree,
32
33
    tsort,
33
 
    )
34
 
from bzrlib.branch import Branch
35
 
from bzrlib.conflicts import ConflictList, Conflict
36
 
from bzrlib.errors import (BzrCommandError,
37
 
                           BzrError,
38
 
                           NoCommonAncestor,
39
 
                           NoCommits,
40
 
                           NoSuchRevision,
41
 
                           NoSuchFile,
42
 
                           NotBranchError,
43
 
                           NotVersionedError,
44
 
                           UnrelatedBranches,
45
 
                           UnsupportedOperation,
46
 
                           WorkingTreeNotRevision,
47
 
                           BinaryFile,
48
 
                           )
49
 
from bzrlib.graph import Graph
50
 
from bzrlib.merge3 import Merge3
51
 
from bzrlib.osutils import rename, pathjoin
52
 
from progress import DummyProgress, ProgressPhase
53
 
from bzrlib.revision import (NULL_REVISION, ensure_null)
54
 
from bzrlib.textfile import check_text_lines
55
 
from bzrlib.trace import mutter, warning, note, is_quiet
56
 
from bzrlib.transform import (TransformPreview, TreeTransform,
57
 
                              resolve_conflicts, cook_conflicts,
58
 
                              conflict_pass, FinalPaths, create_from_tree,
59
 
                              unique_add, ROOT_PARENT)
60
 
from bzrlib.versionedfile import PlanWeaveMerge
61
 
from bzrlib import ui
62
 
 
 
34
    ui,
 
35
    versionedfile
 
36
    )
 
37
from bzrlib.symbol_versioning import (
 
38
    deprecated_in,
 
39
    deprecated_method,
 
40
    )
63
41
# TODO: Report back as changes are merged in
64
42
 
65
43
 
66
44
def transform_tree(from_tree, to_tree, interesting_ids=None):
67
 
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
68
 
                interesting_ids=interesting_ids, this_tree=from_tree)
 
45
    from_tree.lock_tree_write()
 
46
    try:
 
47
        merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
 
48
                    interesting_ids=interesting_ids, this_tree=from_tree)
 
49
    finally:
 
50
        from_tree.unlock()
69
51
 
70
52
 
71
53
class Merger(object):
72
54
    def __init__(self, this_branch, other_tree=None, base_tree=None,
73
 
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
 
55
                 this_tree=None, pb=None, change_reporter=None,
74
56
                 recurse='down', revision_graph=None):
75
57
        object.__init__(self)
76
58
        self.this_branch = this_branch
89
71
        self.interesting_files = None
90
72
        self.show_base = False
91
73
        self.reprocess = False
 
74
        if pb is None:
 
75
            pb = progress.DummyProgress()
92
76
        self._pb = pb
93
77
        self.pp = None
94
78
        self.recurse = recurse
100
84
        self._is_criss_cross = None
101
85
        self._lca_trees = None
102
86
 
 
87
    def cache_trees_with_revision_ids(self, trees):
 
88
        """Cache any tree in trees if it has a revision_id."""
 
89
        for maybe_tree in trees:
 
90
            if maybe_tree is None:
 
91
                continue
 
92
            try:
 
93
                rev_id = maybe_tree.get_revision_id()
 
94
            except AttributeError:
 
95
                continue
 
96
            self._cached_trees[rev_id] = maybe_tree
 
97
 
103
98
    @property
104
99
    def revision_graph(self):
105
100
        if self._revision_graph is None:
132
127
                                      _set_base_is_other_ancestor)
133
128
 
134
129
    @staticmethod
135
 
    def from_uncommitted(tree, other_tree, pb, base_tree=None):
 
130
    def from_uncommitted(tree, other_tree, pb=None, base_tree=None):
136
131
        """Return a Merger for uncommitted changes in other_tree.
137
132
 
138
133
        :param tree: The tree to merge into
167
162
                base_revision_id, tree.branch.last_revision())):
168
163
                base_revision_id = None
169
164
            else:
170
 
                warning('Performing cherrypick')
 
165
                trace.warning('Performing cherrypick')
171
166
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
172
167
                                         base_revision_id, revision_graph=
173
168
                                         revision_graph)
225
220
        if revno is None:
226
221
            tree = workingtree.WorkingTree.open_containing(location)[0]
227
222
            return tree.branch, tree
228
 
        branch = Branch.open_containing(location, possible_transports)[0]
 
223
        branch = _mod_branch.Branch.open_containing(
 
224
            location, possible_transports)[0]
229
225
        if revno == -1:
230
226
            revision_id = branch.last_revision()
231
227
        else:
232
228
            revision_id = branch.get_rev_id(revno)
233
 
        revision_id = ensure_null(revision_id)
 
229
        revision_id = _mod_revision.ensure_null(revision_id)
234
230
        return branch, self.revision_tree(revision_id, branch)
235
231
 
 
232
    @deprecated_method(deprecated_in((2, 1, 0)))
236
233
    def ensure_revision_trees(self):
237
234
        if self.this_revision_tree is None:
238
235
            self.this_basis_tree = self.revision_tree(self.this_basis)
241
238
 
242
239
        if self.other_rev_id is None:
243
240
            other_basis_tree = self.revision_tree(self.other_basis)
244
 
            changes = other_basis_tree.changes_from(self.other_tree)
245
 
            if changes.has_changed():
246
 
                raise WorkingTreeNotRevision(self.this_tree)
 
241
            if other_basis_tree.has_changes(self.other_tree):
 
242
                raise errors.WorkingTreeNotRevision(self.this_tree)
247
243
            other_rev_id = self.other_basis
248
244
            self.other_tree = other_basis_tree
249
245
 
 
246
    @deprecated_method(deprecated_in((2, 1, 0)))
250
247
    def file_revisions(self, file_id):
251
248
        self.ensure_revision_trees()
252
249
        def get_id(tree, file_id):
255
252
        if self.this_rev_id is None:
256
253
            if self.this_basis_tree.get_file_sha1(file_id) != \
257
254
                self.this_tree.get_file_sha1(file_id):
258
 
                raise WorkingTreeNotRevision(self.this_tree)
 
255
                raise errors.WorkingTreeNotRevision(self.this_tree)
259
256
 
260
257
        trees = (self.this_basis_tree, self.other_tree)
261
258
        return [get_id(tree, file_id) for tree in trees]
262
259
 
 
260
    @deprecated_method(deprecated_in((2, 1, 0)))
263
261
    def check_basis(self, check_clean, require_commits=True):
264
262
        if self.this_basis is None and require_commits is True:
265
 
            raise BzrCommandError("This branch has no commits."
266
 
                                  " (perhaps you would prefer 'bzr pull')")
 
263
            raise errors.BzrCommandError(
 
264
                "This branch has no commits."
 
265
                " (perhaps you would prefer 'bzr pull')")
267
266
        if check_clean:
268
267
            self.compare_basis()
269
268
            if self.this_basis != self.this_rev_id:
270
269
                raise errors.UncommittedChanges(self.this_tree)
271
270
 
 
271
    @deprecated_method(deprecated_in((2, 1, 0)))
272
272
    def compare_basis(self):
273
273
        try:
274
274
            basis_tree = self.revision_tree(self.this_tree.last_revision())
275
275
        except errors.NoSuchRevision:
276
276
            basis_tree = self.this_tree.basis_tree()
277
 
        changes = self.this_tree.changes_from(basis_tree)
278
 
        if not changes.has_changed():
 
277
        if not self.this_tree.has_changes(basis_tree):
279
278
            self.this_rev_id = self.this_basis
280
279
 
281
280
    def set_interesting_files(self, file_list):
282
281
        self.interesting_files = file_list
283
282
 
284
283
    def set_pending(self):
285
 
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
 
284
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
 
285
            or self.other_rev_id is None):
286
286
            return
287
287
        self._add_parent()
288
288
 
318
318
            self.other_rev_id = _mod_revision.ensure_null(
319
319
                self.other_branch.last_revision())
320
320
            if _mod_revision.is_null(self.other_rev_id):
321
 
                raise NoCommits(self.other_branch)
 
321
                raise errors.NoCommits(self.other_branch)
322
322
            self.other_basis = self.other_rev_id
323
323
        elif other_revision[1] is not None:
324
324
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
327
327
            self.other_rev_id = None
328
328
            self.other_basis = self.other_branch.last_revision()
329
329
            if self.other_basis is None:
330
 
                raise NoCommits(self.other_branch)
 
330
                raise errors.NoCommits(self.other_branch)
331
331
        if self.other_rev_id is not None:
332
332
            self._cached_trees[self.other_rev_id] = self.other_tree
333
333
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
360
360
            target.fetch(source, revision_id)
361
361
 
362
362
    def find_base(self):
363
 
        revisions = [ensure_null(self.this_basis),
364
 
                     ensure_null(self.other_basis)]
365
 
        if NULL_REVISION in revisions:
366
 
            self.base_rev_id = NULL_REVISION
 
363
        revisions = [_mod_revision.ensure_null(self.this_basis),
 
364
                     _mod_revision.ensure_null(self.other_basis)]
 
365
        if _mod_revision.NULL_REVISION in revisions:
 
366
            self.base_rev_id = _mod_revision.NULL_REVISION
367
367
            self.base_tree = self.revision_tree(self.base_rev_id)
368
368
            self._is_criss_cross = False
369
369
        else:
370
370
            lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
371
371
            self._is_criss_cross = False
372
372
            if len(lcas) == 0:
373
 
                self.base_rev_id = NULL_REVISION
 
373
                self.base_rev_id = _mod_revision.NULL_REVISION
374
374
            elif len(lcas) == 1:
375
375
                self.base_rev_id = list(lcas)[0]
376
376
            else: # len(lcas) > 1
385
385
                    self.base_rev_id = self.revision_graph.find_unique_lca(
386
386
                                            *lcas)
387
387
                self._is_criss_cross = True
388
 
            if self.base_rev_id == NULL_REVISION:
389
 
                raise UnrelatedBranches()
 
388
            if self.base_rev_id == _mod_revision.NULL_REVISION:
 
389
                raise errors.UnrelatedBranches()
390
390
            if self._is_criss_cross:
391
 
                warning('Warning: criss-cross merge encountered.  See bzr'
392
 
                        ' help criss-cross.')
393
 
                mutter('Criss-cross lcas: %r' % lcas)
 
391
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
 
392
                              ' help criss-cross.')
 
393
                trace.mutter('Criss-cross lcas: %r' % lcas)
394
394
                interesting_revision_ids = [self.base_rev_id]
395
395
                interesting_revision_ids.extend(lcas)
396
396
                interesting_trees = dict((t.get_revision_id(), t)
406
406
                self.base_tree = self.revision_tree(self.base_rev_id)
407
407
        self.base_is_ancestor = True
408
408
        self.base_is_other_ancestor = True
409
 
        mutter('Base revid: %r' % self.base_rev_id)
 
409
        trace.mutter('Base revid: %r' % self.base_rev_id)
410
410
 
411
411
    def set_base(self, base_revision):
412
412
        """Set the base revision to use for the merge.
413
413
 
414
414
        :param base_revision: A 2-list containing a path and revision number.
415
415
        """
416
 
        mutter("doing merge() with no base_revision specified")
 
416
        trace.mutter("doing merge() with no base_revision specified")
417
417
        if base_revision == [None, None]:
418
418
            self.find_base()
419
419
        else:
439
439
        if self.merge_type.supports_reprocess:
440
440
            kwargs['reprocess'] = self.reprocess
441
441
        elif self.reprocess:
442
 
            raise BzrError("Conflict reduction is not supported for merge"
443
 
                                  " type %s." % self.merge_type)
 
442
            raise errors.BzrError(
 
443
                "Conflict reduction is not supported for merge"
 
444
                " type %s." % self.merge_type)
444
445
        if self.merge_type.supports_show_base:
445
446
            kwargs['show_base'] = self.show_base
446
447
        elif self.show_base:
447
 
            raise BzrError("Showing base is not supported for this"
448
 
                           " merge type. %s" % self.merge_type)
 
448
            raise errors.BzrError("Showing base is not supported for this"
 
449
                                  " merge type. %s" % self.merge_type)
449
450
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
450
451
            and not self.base_is_other_ancestor):
451
452
            raise errors.CannotReverseCherrypick()
460
461
                               **kwargs)
461
462
 
462
463
    def _do_merge_to(self, merge):
 
464
        if self.other_branch is not None:
 
465
            self.other_branch.update_references(self.this_branch)
463
466
        merge.do_merge()
464
467
        if self.recurse == 'down':
465
468
            for relpath, file_id in self.this_tree.iter_references():
477
480
                    sub_tree.branch.repository.revision_tree(base_revision)
478
481
                sub_merge.base_rev_id = base_revision
479
482
                sub_merge.do_merge()
480
 
        
 
483
 
481
484
    def do_merge(self):
482
485
        self.this_tree.lock_tree_write()
483
486
        try:
498
501
        finally:
499
502
            self.this_tree.unlock()
500
503
        if len(merge.cooked_conflicts) == 0:
501
 
            if not self.ignore_zero and not is_quiet():
502
 
                note("All changes applied successfully.")
 
504
            if not self.ignore_zero and not trace.is_quiet():
 
505
                trace.note("All changes applied successfully.")
503
506
        else:
504
 
            note("%d conflicts encountered." % len(merge.cooked_conflicts))
 
507
            trace.note("%d conflicts encountered."
 
508
                       % len(merge.cooked_conflicts))
505
509
 
506
510
        return len(merge.cooked_conflicts)
507
511
 
534
538
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
535
539
    supports_lca_trees = True
536
540
 
537
 
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
 
541
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
538
542
                 interesting_ids=None, reprocess=False, show_base=False,
539
 
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
543
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
540
544
                 interesting_files=None, do_merge=True,
541
545
                 cherrypick=False, lca_trees=None):
542
546
        """Initialize the merger object and perform the merge.
544
548
        :param working_tree: The working tree to apply the merge to
545
549
        :param this_tree: The local tree in the merge operation
546
550
        :param base_tree: The common tree in the merge operation
547
 
        :param other_tree: The other other tree to merge changes from
 
551
        :param other_tree: The other tree to merge changes from
548
552
        :param interesting_ids: The file_ids of files that should be
549
553
            participate in the merge.  May not be combined with
550
554
            interesting_files.
588
592
        self.change_reporter = change_reporter
589
593
        self.cherrypick = cherrypick
590
594
        if self.pp is None:
591
 
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
 
595
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
592
596
        if do_merge:
593
597
            self.do_merge()
594
598
 
596
600
        self.this_tree.lock_tree_write()
597
601
        self.base_tree.lock_read()
598
602
        self.other_tree.lock_read()
599
 
        self.tt = TreeTransform(self.this_tree, self.pb)
600
603
        try:
601
 
            self.pp.next_phase()
602
 
            self._compute_transform()
603
 
            self.pp.next_phase()
604
 
            results = self.tt.apply(no_conflicts=True)
605
 
            self.write_modified(results)
 
604
            self.tt = transform.TreeTransform(self.this_tree, self.pb)
606
605
            try:
607
 
                self.this_tree.add_conflicts(self.cooked_conflicts)
608
 
            except UnsupportedOperation:
609
 
                pass
 
606
                self.pp.next_phase()
 
607
                self._compute_transform()
 
608
                self.pp.next_phase()
 
609
                results = self.tt.apply(no_conflicts=True)
 
610
                self.write_modified(results)
 
611
                try:
 
612
                    self.this_tree.add_conflicts(self.cooked_conflicts)
 
613
                except errors.UnsupportedOperation:
 
614
                    pass
 
615
            finally:
 
616
                self.tt.finalize()
610
617
        finally:
611
 
            self.tt.finalize()
612
618
            self.other_tree.unlock()
613
619
            self.base_tree.unlock()
614
620
            self.this_tree.unlock()
617
623
    def make_preview_transform(self):
618
624
        self.base_tree.lock_read()
619
625
        self.other_tree.lock_read()
620
 
        self.tt = TransformPreview(self.this_tree)
 
626
        self.tt = transform.TransformPreview(self.this_tree)
621
627
        try:
622
628
            self.pp.next_phase()
623
629
            self._compute_transform()
653
659
        self.pp.next_phase()
654
660
        child_pb = ui.ui_factory.nested_progress_bar()
655
661
        try:
656
 
            fs_conflicts = resolve_conflicts(self.tt, child_pb,
657
 
                lambda t, c: conflict_pass(t, c, self.other_tree))
 
662
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
 
663
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
658
664
        finally:
659
665
            child_pb.finished()
660
666
        if self.change_reporter is not None:
663
669
                self.tt.iter_changes(), self.change_reporter)
664
670
        self.cook_conflicts(fs_conflicts)
665
671
        for conflict in self.cooked_conflicts:
666
 
            warning(conflict)
 
672
            trace.warning(conflict)
667
673
 
668
674
    def _entries3(self):
669
675
        """Gather data about files modified between three trees.
871
877
    def fix_root(self):
872
878
        try:
873
879
            self.tt.final_kind(self.tt.root)
874
 
        except NoSuchFile:
 
880
        except errors.NoSuchFile:
875
881
            self.tt.cancel_deletion(self.tt.root)
876
882
        if self.tt.final_file_id(self.tt.root) is None:
877
 
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
883
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
878
884
                                 self.tt.root)
879
885
        other_root_file_id = self.other_tree.get_root_id()
880
886
        if other_root_file_id is None:
884
890
            return
885
891
        try:
886
892
            self.tt.final_kind(other_root)
887
 
        except NoSuchFile:
 
893
        except errors.NoSuchFile:
888
894
            return
889
895
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
890
896
            # the other tree's root is a non-root in the current tree
923
929
        if entry is None:
924
930
            return None
925
931
        return entry.name
926
 
    
 
932
 
927
933
    @staticmethod
928
934
    def contents_sha1(tree, file_id):
929
935
        """Determine the sha1 of the file contents (used as a key method)."""
1069
1075
            return
1070
1076
        if name_winner == "conflict":
1071
1077
            trans_id = self.tt.trans_id_file_id(file_id)
1072
 
            self._raw_conflicts.append(('name conflict', trans_id, 
 
1078
            self._raw_conflicts.append(('name conflict', trans_id,
1073
1079
                                        this_name, other_name))
1074
1080
        if parent_id_winner == "conflict":
1075
1081
            trans_id = self.tt.trans_id_file_id(file_id)
1076
 
            self._raw_conflicts.append(('parent conflict', trans_id, 
 
1082
            self._raw_conflicts.append(('parent conflict', trans_id,
1077
1083
                                        this_parent, other_parent))
1078
1084
        if other_name is None:
1079
 
            # it doesn't matter whether the result was 'other' or 
 
1085
            # it doesn't matter whether the result was 'other' or
1080
1086
            # 'conflict'-- if there's no 'other', we leave it alone.
1081
1087
            return
1082
1088
        # if we get here, name_winner and parent_winner are set to safe values.
1109
1115
                self.tt.unversion_file(trans_id)
1110
1116
                if file_id in self.this_tree:
1111
1117
                    self.tt.delete_contents(trans_id)
1112
 
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
1118
            file_group = self._dump_conflicts(name, parent_id, file_id,
1113
1119
                                              set_version=True)
1114
1120
            self._raw_conflicts.append(('contents conflict', file_group))
1115
1121
 
1143
1149
                self.tt.delete_contents(trans_id)
1144
1150
            if file_id in self.other_tree:
1145
1151
                # OTHER changed the file
1146
 
                create_from_tree(self.tt, trans_id,
1147
 
                                 self.other_tree, file_id)
 
1152
                wt = self.this_tree
 
1153
                if wt.supports_content_filtering():
 
1154
                    # We get the path from the working tree if it exists.
 
1155
                    # That fails though when OTHER is adding a file, so
 
1156
                    # we fall back to the other tree to find the path if
 
1157
                    # it doesn't exist locally.
 
1158
                    try:
 
1159
                        filter_tree_path = wt.id2path(file_id)
 
1160
                    except errors.NoSuchId:
 
1161
                        filter_tree_path = self.other_tree.id2path(file_id)
 
1162
                else:
 
1163
                    # Skip the id2path lookup for older formats
 
1164
                    filter_tree_path = None
 
1165
                transform.create_from_tree(self.tt, trans_id,
 
1166
                                 self.other_tree, file_id,
 
1167
                                 filter_tree_path=filter_tree_path)
1148
1168
                if not file_in_this:
1149
1169
                    self.tt.version_file(file_id, trans_id)
1150
1170
                return "modified"
1161
1181
                # have agreement that output should be a file.
1162
1182
                try:
1163
1183
                    self.text_merge(file_id, trans_id)
1164
 
                except BinaryFile:
 
1184
                except errors.BinaryFile:
1165
1185
                    return contents_conflict()
1166
1186
                if file_id not in self.this_tree:
1167
1187
                    self.tt.version_file(file_id, trans_id)
1168
1188
                try:
1169
1189
                    self.tt.tree_kind(trans_id)
1170
1190
                    self.tt.delete_contents(trans_id)
1171
 
                except NoSuchFile:
 
1191
                except errors.NoSuchFile:
1172
1192
                    pass
1173
1193
                return "modified"
1174
1194
            else:
1192
1212
            base_lines = []
1193
1213
        other_lines = self.get_lines(self.other_tree, file_id)
1194
1214
        this_lines = self.get_lines(self.this_tree, file_id)
1195
 
        m3 = Merge3(base_lines, this_lines, other_lines,
1196
 
                    is_cherrypick=self.cherrypick)
 
1215
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
 
1216
                           is_cherrypick=self.cherrypick)
1197
1217
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1198
1218
        if self.show_base is True:
1199
1219
            base_marker = '|' * 7
1202
1222
 
1203
1223
        def iter_merge3(retval):
1204
1224
            retval["text_conflicts"] = False
1205
 
            for line in m3.merge_lines(name_a = "TREE", 
1206
 
                                       name_b = "MERGE-SOURCE", 
 
1225
            for line in m3.merge_lines(name_a = "TREE",
 
1226
                                       name_b = "MERGE-SOURCE",
1207
1227
                                       name_base = "BASE-REVISION",
1208
 
                                       start_marker=start_marker, 
 
1228
                                       start_marker=start_marker,
1209
1229
                                       base_marker=base_marker,
1210
1230
                                       reprocess=self.reprocess):
1211
1231
                if line.startswith(start_marker):
1220
1240
            self._raw_conflicts.append(('text conflict', trans_id))
1221
1241
            name = self.tt.final_name(trans_id)
1222
1242
            parent_id = self.tt.final_parent(trans_id)
1223
 
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
1243
            file_group = self._dump_conflicts(name, parent_id, file_id,
1224
1244
                                              this_lines, base_lines,
1225
1245
                                              other_lines)
1226
1246
            file_group.append(trans_id)
1227
1247
 
1228
 
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None, 
 
1248
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
1229
1249
                        base_lines=None, other_lines=None, set_version=False,
1230
1250
                        no_base=False):
1231
1251
        """Emit conflict files.
1233
1253
        determined automatically.  If set_version is true, the .OTHER, .THIS
1234
1254
        or .BASE (in that order) will be created as versioned files.
1235
1255
        """
1236
 
        data = [('OTHER', self.other_tree, other_lines), 
 
1256
        data = [('OTHER', self.other_tree, other_lines),
1237
1257
                ('THIS', self.this_tree, this_lines)]
1238
1258
        if not no_base:
1239
1259
            data.append(('BASE', self.base_tree, base_lines))
 
1260
 
 
1261
        # We need to use the actual path in the working tree of the file here,
 
1262
        # ignoring the conflict suffixes
 
1263
        wt = self.this_tree
 
1264
        if wt.supports_content_filtering():
 
1265
            try:
 
1266
                filter_tree_path = wt.id2path(file_id)
 
1267
            except errors.NoSuchId:
 
1268
                # file has been deleted
 
1269
                filter_tree_path = None
 
1270
        else:
 
1271
            # Skip the id2path lookup for older formats
 
1272
            filter_tree_path = None
 
1273
 
1240
1274
        versioned = False
1241
1275
        file_group = []
1242
1276
        for suffix, tree, lines in data:
1243
1277
            if file_id in tree:
1244
1278
                trans_id = self._conflict_file(name, parent_id, tree, file_id,
1245
 
                                               suffix, lines)
 
1279
                                               suffix, lines, filter_tree_path)
1246
1280
                file_group.append(trans_id)
1247
1281
                if set_version and not versioned:
1248
1282
                    self.tt.version_file(file_id, trans_id)
1249
1283
                    versioned = True
1250
1284
        return file_group
1251
 
           
 
1285
 
1252
1286
    def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1253
 
                       lines=None):
 
1287
                       lines=None, filter_tree_path=None):
1254
1288
        """Emit a single conflict file."""
1255
1289
        name = name + '.' + suffix
1256
1290
        trans_id = self.tt.create_path(name, parent_id)
1257
 
        create_from_tree(self.tt, trans_id, tree, file_id, lines)
 
1291
        transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
 
1292
            filter_tree_path)
1258
1293
        return trans_id
1259
1294
 
1260
1295
    def merge_executable(self, file_id, file_status):
1284
1319
        try:
1285
1320
            if self.tt.final_kind(trans_id) != "file":
1286
1321
                return
1287
 
        except NoSuchFile:
 
1322
        except errors.NoSuchFile:
1288
1323
            return
1289
1324
        if winner == "this":
1290
1325
            executability = this_executable
1301
1336
 
1302
1337
    def cook_conflicts(self, fs_conflicts):
1303
1338
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1304
 
        from conflicts import Conflict
1305
1339
        name_conflicts = {}
1306
 
        self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1307
 
        fp = FinalPaths(self.tt)
 
1340
        self.cooked_conflicts.extend(transform.cook_conflicts(
 
1341
                fs_conflicts, self.tt))
 
1342
        fp = transform.FinalPaths(self.tt)
1308
1343
        for conflict in self._raw_conflicts:
1309
1344
            conflict_type = conflict[0]
1310
1345
            if conflict_type in ('name conflict', 'parent conflict'):
1312
1347
                conflict_args = conflict[2:]
1313
1348
                if trans_id not in name_conflicts:
1314
1349
                    name_conflicts[trans_id] = {}
1315
 
                unique_add(name_conflicts[trans_id], conflict_type, 
1316
 
                           conflict_args)
 
1350
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1351
                                     conflict_args)
1317
1352
            if conflict_type == 'contents conflict':
1318
1353
                for trans_id in conflict[1]:
1319
1354
                    file_id = self.tt.final_file_id(trans_id)
1324
1359
                    if path.endswith(suffix):
1325
1360
                        path = path[:-len(suffix)]
1326
1361
                        break
1327
 
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
1362
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1363
                                                    path=path, file_id=file_id)
1328
1364
                self.cooked_conflicts.append(c)
1329
1365
            if conflict_type == 'text conflict':
1330
1366
                trans_id = conflict[1]
1331
1367
                path = fp.get_path(trans_id)
1332
1368
                file_id = self.tt.final_file_id(trans_id)
1333
 
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
1369
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1370
                                                    path=path, file_id=file_id)
1334
1371
                self.cooked_conflicts.append(c)
1335
1372
 
1336
1373
        for trans_id, conflicts in name_conflicts.iteritems():
1351
1388
            if this_parent is not None and this_name is not None:
1352
1389
                this_parent_path = \
1353
1390
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
1354
 
                this_path = pathjoin(this_parent_path, this_name)
 
1391
                this_path = osutils.pathjoin(this_parent_path, this_name)
1355
1392
            else:
1356
1393
                this_path = "<deleted>"
1357
1394
            file_id = self.tt.final_file_id(trans_id)
1358
 
            c = Conflict.factory('path conflict', path=this_path,
1359
 
                                 conflict_path=other_path, file_id=file_id)
 
1395
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1396
                                                conflict_path=other_path,
 
1397
                                                file_id=file_id)
1360
1398
            self.cooked_conflicts.append(c)
1361
 
        self.cooked_conflicts.sort(key=Conflict.sort_key)
 
1399
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1362
1400
 
1363
1401
 
1364
1402
class WeaveMerger(Merge3Merger):
1368
1406
    supports_reverse_cherrypick = False
1369
1407
    history_based = True
1370
1408
 
1371
 
    def _merged_lines(self, file_id):
1372
 
        """Generate the merged lines.
1373
 
        There is no distinction between lines that are meant to contain <<<<<<<
1374
 
        and conflicts.
1375
 
        """
1376
 
        if self.cherrypick:
1377
 
            base = self.base_tree
1378
 
        else:
1379
 
            base = None
1380
 
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
 
1409
    def _generate_merge_plan(self, file_id, base):
 
1410
        return self.this_tree.plan_file_merge(file_id, self.other_tree,
1381
1411
                                              base=base)
 
1412
 
 
1413
    def _merged_lines(self, file_id):
 
1414
        """Generate the merged lines.
 
1415
        There is no distinction between lines that are meant to contain <<<<<<<
 
1416
        and conflicts.
 
1417
        """
 
1418
        if self.cherrypick:
 
1419
            base = self.base_tree
 
1420
        else:
 
1421
            base = None
 
1422
        plan = self._generate_merge_plan(file_id, base)
1382
1423
        if 'merge' in debug.debug_flags:
1383
1424
            plan = list(plan)
1384
1425
            trans_id = self.tt.trans_id_file_id(file_id)
1385
1426
            name = self.tt.final_name(trans_id) + '.plan'
1386
 
            contents = ('%10s|%s' % l for l in plan)
 
1427
            contents = ('%11s|%s' % l for l in plan)
1387
1428
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1388
 
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1389
 
            '>>>>>>> MERGE-SOURCE\n')
1390
 
        return textmerge.merge_lines(self.reprocess)
 
1429
        textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
1430
                                                 '>>>>>>> MERGE-SOURCE\n')
 
1431
        lines, conflicts = textmerge.merge_lines(self.reprocess)
 
1432
        if conflicts:
 
1433
            base_lines = textmerge.base_from_plan()
 
1434
        else:
 
1435
            base_lines = None
 
1436
        return lines, base_lines
1391
1437
 
1392
1438
    def text_merge(self, file_id, trans_id):
1393
1439
        """Perform a (weave) text merge for a given file and file-id.
1394
1440
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1395
1441
        and a conflict will be noted.
1396
1442
        """
1397
 
        lines, conflicts = self._merged_lines(file_id)
 
1443
        lines, base_lines = self._merged_lines(file_id)
1398
1444
        lines = list(lines)
1399
 
        # Note we're checking whether the OUTPUT is binary in this case, 
 
1445
        # Note we're checking whether the OUTPUT is binary in this case,
1400
1446
        # because we don't want to get into weave merge guts.
1401
 
        check_text_lines(lines)
 
1447
        textfile.check_text_lines(lines)
1402
1448
        self.tt.create_file(lines, trans_id)
1403
 
        if conflicts:
 
1449
        if base_lines is not None:
 
1450
            # Conflict
1404
1451
            self._raw_conflicts.append(('text conflict', trans_id))
1405
1452
            name = self.tt.final_name(trans_id)
1406
1453
            parent_id = self.tt.final_parent(trans_id)
1407
 
            file_group = self._dump_conflicts(name, parent_id, file_id, 
1408
 
                                              no_base=True)
 
1454
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1455
                                              no_base=False,
 
1456
                                              base_lines=base_lines)
1409
1457
            file_group.append(trans_id)
1410
1458
 
1411
1459
 
1412
1460
class LCAMerger(WeaveMerger):
1413
1461
 
1414
 
    def _merged_lines(self, file_id):
1415
 
        """Generate the merged lines.
1416
 
        There is no distinction between lines that are meant to contain <<<<<<<
1417
 
        and conflicts.
1418
 
        """
1419
 
        if self.cherrypick:
1420
 
            base = self.base_tree
1421
 
        else:
1422
 
            base = None
1423
 
        plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
 
1462
    def _generate_merge_plan(self, file_id, base):
 
1463
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1424
1464
                                                  base=base)
1425
 
        if 'merge' in debug.debug_flags:
1426
 
            plan = list(plan)
1427
 
            trans_id = self.tt.trans_id_file_id(file_id)
1428
 
            name = self.tt.final_name(trans_id) + '.plan'
1429
 
            contents = ('%10s|%s' % l for l in plan)
1430
 
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1431
 
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1432
 
            '>>>>>>> MERGE-SOURCE\n')
1433
 
        return textmerge.merge_lines(self.reprocess)
1434
 
 
1435
1465
 
1436
1466
class Diff3Merger(Merge3Merger):
1437
1467
    """Three-way merger using external diff3 for text merging"""
1438
1468
 
1439
1469
    def dump_file(self, temp_dir, name, tree, file_id):
1440
 
        out_path = pathjoin(temp_dir, name)
 
1470
        out_path = osutils.pathjoin(temp_dir, name)
1441
1471
        out_file = open(out_path, "wb")
1442
1472
        try:
1443
1473
            in_file = tree.get_file(file_id)
1455
1485
        import bzrlib.patch
1456
1486
        temp_dir = osutils.mkdtemp(prefix="bzr-")
1457
1487
        try:
1458
 
            new_file = pathjoin(temp_dir, "new")
 
1488
            new_file = osutils.pathjoin(temp_dir, "new")
1459
1489
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1460
1490
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1461
1491
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1462
1492
            status = bzrlib.patch.diff3(new_file, this, base, other)
1463
1493
            if status not in (0, 1):
1464
 
                raise BzrError("Unhandled diff3 exit code")
 
1494
                raise errors.BzrError("Unhandled diff3 exit code")
1465
1495
            f = open(new_file, 'rb')
1466
1496
            try:
1467
1497
                self.tt.create_file(f, trans_id)
1485
1515
                other_rev_id=None,
1486
1516
                interesting_files=None,
1487
1517
                this_tree=None,
1488
 
                pb=DummyProgress(),
 
1518
                pb=progress.DummyProgress(),
1489
1519
                change_reporter=None):
1490
 
    """Primary interface for merging. 
 
1520
    """Primary interface for merging.
1491
1521
 
1492
 
        typical use is probably 
 
1522
        typical use is probably
1493
1523
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1494
1524
                     branch.get_revision_tree(base_revision))'
1495
1525
        """
1496
1526
    if this_tree is None:
1497
 
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1498
 
            "parameter as of bzrlib version 0.8.")
 
1527
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1528
                              "parameter as of bzrlib version 0.8.")
1499
1529
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1500
1530
                    pb=pb, change_reporter=change_reporter)
1501
1531
    merger.backup_files = backup_files
1514
1544
    get_revision_id = getattr(base_tree, 'get_revision_id', None)
1515
1545
    if get_revision_id is None:
1516
1546
        get_revision_id = base_tree.last_revision
 
1547
    merger.cache_trees_with_revision_ids([other_tree, base_tree, this_tree])
1517
1548
    merger.set_base_revision(get_revision_id(), this_branch)
1518
1549
    return merger.do_merge()
1519
1550
 
1718
1749
        super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
1719
1750
        self.a_key = self._key_prefix + (self.a_rev,)
1720
1751
        self.b_key = self._key_prefix + (self.b_rev,)
1721
 
        self.graph = Graph(self.vf)
 
1752
        self.graph = _mod_graph.Graph(self.vf)
1722
1753
        heads = self.graph.heads((self.a_key, self.b_key))
1723
1754
        if len(heads) == 1:
1724
1755
            # one side dominates, so we can just return its values, yay for
1729
1760
                other = b_rev
1730
1761
            else:
1731
1762
                other = a_rev
1732
 
            mutter('found dominating revision for %s\n%s > %s', self.vf,
1733
 
                   self._head_key[-1], other)
 
1763
            trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
 
1764
                         self._head_key[-1], other)
1734
1765
            self._weave = None
1735
1766
        else:
1736
1767
            self._head_key = None
1750
1781
        while True:
1751
1782
            next_lcas = self.graph.find_lca(*cur_ancestors)
1752
1783
            # Map a plain NULL_REVISION to a simple no-ancestors
1753
 
            if next_lcas == set([NULL_REVISION]):
 
1784
            if next_lcas == set([_mod_revision.NULL_REVISION]):
1754
1785
                next_lcas = ()
1755
1786
            # Order the lca's based on when they were merged into the tip
1756
1787
            # While the actual merge portion of weave merge uses a set() of
1768
1799
            elif len(next_lcas) > 2:
1769
1800
                # More than 2 lca's, fall back to grabbing all nodes between
1770
1801
                # this and the unique lca.
1771
 
                mutter('More than 2 LCAs, falling back to all nodes for:'
1772
 
                       ' %s, %s\n=> %s', self.a_key, self.b_key, cur_ancestors)
 
1802
                trace.mutter('More than 2 LCAs, falling back to all nodes for:'
 
1803
                             ' %s, %s\n=> %s',
 
1804
                             self.a_key, self.b_key, cur_ancestors)
1773
1805
                cur_lcas = next_lcas
1774
1806
                while len(cur_lcas) > 1:
1775
1807
                    cur_lcas = self.graph.find_lca(*cur_lcas)
1778
1810
                    unique_lca = None
1779
1811
                else:
1780
1812
                    unique_lca = list(cur_lcas)[0]
1781
 
                    if unique_lca == NULL_REVISION:
 
1813
                    if unique_lca == _mod_revision.NULL_REVISION:
1782
1814
                        # find_lca will return a plain 'NULL_REVISION' rather
1783
1815
                        # than a key tuple when there is no common ancestor, we
1784
1816
                        # prefer to just use None, because it doesn't confuse
1792
1824
 
1793
1825
    def _find_unique_parents(self, tip_keys, base_key):
1794
1826
        """Find ancestors of tip that aren't ancestors of base.
1795
 
        
 
1827
 
1796
1828
        :param tip_keys: Nodes that are interesting
1797
1829
        :param base_key: Cull all ancestors of this node
1798
1830
        :return: The parent map for all revisions between tip_keys and
1807
1839
            # We remove NULL_REVISION because it isn't a proper tuple key, and
1808
1840
            # thus confuses things like _get_interesting_texts, and our logic
1809
1841
            # to add the texts into the memory weave.
1810
 
            if NULL_REVISION in parent_map:
1811
 
                parent_map.pop(NULL_REVISION)
 
1842
            if _mod_revision.NULL_REVISION in parent_map:
 
1843
                parent_map.pop(_mod_revision.NULL_REVISION)
1812
1844
        else:
1813
1845
            interesting = set()
1814
1846
            for tip in tip_keys:
1858
1890
    @staticmethod
1859
1891
    def _prune_tails(parent_map, child_map, tails_to_remove):
1860
1892
        """Remove tails from the parent map.
1861
 
        
 
1893
 
1862
1894
        This will remove the supplied revisions until no more children have 0
1863
1895
        parents.
1864
1896
 
1966
1998
        lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
1967
1999
        self.lcas = set()
1968
2000
        for lca in lcas:
1969
 
            if lca == NULL_REVISION:
 
2001
            if lca == _mod_revision.NULL_REVISION:
1970
2002
                self.lcas.add(lca)
1971
2003
            else:
1972
2004
                self.lcas.add(lca[-1])