~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: 2007-06-28 07:08:27 UTC
  • mfrom: (2553.1.3 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20070628070827-h5s313dg5tnag9vj
(robertc) Show the names of commit hooks during commit.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
import warnings
21
21
 
22
22
from bzrlib import (
23
 
    errors,
24
23
    osutils,
25
 
    patiencediff,
26
24
    registry,
27
 
    revision as _mod_revision,
28
25
    )
29
26
from bzrlib.branch import Branch
30
27
from bzrlib.conflicts import ConflictList, Conflict
48
45
from bzrlib.textfile import check_text_lines
49
46
from bzrlib.trace import mutter, warning, note
50
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
51
 
                              conflict_pass, FinalPaths, create_by_entry,
52
 
                              unique_add, ROOT_PARENT)
53
 
from bzrlib.versionedfile import PlanWeaveMerge
 
48
                              FinalPaths, create_by_entry, unique_add,
 
49
                              ROOT_PARENT)
 
50
from bzrlib.versionedfile import WeaveMerge
54
51
from bzrlib import ui
55
52
 
56
53
# TODO: Report back as changes are merged in
57
54
 
 
55
def _get_tree(treespec, local_branch=None):
 
56
    from bzrlib import workingtree
 
57
    location, revno = treespec
 
58
    if revno is None:
 
59
        tree = workingtree.WorkingTree.open_containing(location)[0]
 
60
        return tree.branch, tree
 
61
    branch = Branch.open_containing(location)[0]
 
62
    if revno == -1:
 
63
        revision_id = branch.last_revision()
 
64
    else:
 
65
        revision_id = branch.get_rev_id(revno)
 
66
    if revision_id is None:
 
67
        revision_id = NULL_REVISION
 
68
    return branch, _get_revid_tree(branch, revision_id, local_branch)
 
69
 
 
70
 
 
71
def _get_revid_tree(branch, revision_id, local_branch):
 
72
    if revision_id is None:
 
73
        base_tree = branch.bzrdir.open_workingtree()
 
74
    else:
 
75
        if local_branch is not None:
 
76
            if local_branch.base != branch.base:
 
77
                local_branch.fetch(branch, revision_id)
 
78
            base_tree = local_branch.repository.revision_tree(revision_id)
 
79
        else:
 
80
            base_tree = branch.repository.revision_tree(revision_id)
 
81
    return base_tree
 
82
 
 
83
 
 
84
def _get_revid_tree_from_tree(tree, revision_id, local_branch):
 
85
    if revision_id is None:
 
86
        return tree
 
87
    if local_branch is not None:
 
88
        if local_branch.base != tree.branch.base:
 
89
            local_branch.fetch(tree.branch, revision_id)
 
90
        return local_branch.repository.revision_tree(revision_id)
 
91
    return tree.branch.repository.revision_tree(revision_id)
 
92
 
58
93
 
59
94
def transform_tree(from_tree, to_tree, interesting_ids=None):
60
95
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
68
103
        object.__init__(self)
69
104
        assert this_tree is not None, "this_tree is required"
70
105
        self.this_branch = this_branch
71
 
        self.this_basis = _mod_revision.ensure_null(
72
 
            this_branch.last_revision())
 
106
        self.this_basis = this_branch.last_revision()
73
107
        self.this_rev_id = None
74
108
        self.this_tree = this_tree
75
109
        self.this_revision_tree = None
80
114
        self.ignore_zero = False
81
115
        self.backup_files = False
82
116
        self.interesting_ids = None
83
 
        self.interesting_files = None
84
117
        self.show_base = False
85
118
        self.reprocess = False
86
119
        self._pb = pb
87
120
        self.pp = None
88
121
        self.recurse = recurse
89
122
        self.change_reporter = change_reporter
90
 
        self._cached_trees = {}
91
 
 
92
 
    def revision_tree(self, revision_id, branch=None):
93
 
        if revision_id not in self._cached_trees:
94
 
            if branch is None:
95
 
                branch = self.this_branch
96
 
            try:
97
 
                tree = self.this_tree.revision_tree(revision_id)
98
 
            except errors.NoSuchRevisionInTree:
99
 
                tree = branch.repository.revision_tree(revision_id)
100
 
            self._cached_trees[revision_id] = tree
101
 
        return self._cached_trees[revision_id]
102
 
 
103
 
    def _get_tree(self, treespec, possible_transports=None):
104
 
        from bzrlib import workingtree
105
 
        location, revno = treespec
106
 
        if revno is None:
107
 
            tree = workingtree.WorkingTree.open_containing(location)[0]
108
 
            return tree.branch, tree
109
 
        branch = Branch.open_containing(location, possible_transports)[0]
110
 
        if revno == -1:
111
 
            revision_id = branch.last_revision()
112
 
        else:
113
 
            revision_id = branch.get_rev_id(revno)
114
 
        revision_id = ensure_null(revision_id)
115
 
        return branch, self.revision_tree(revision_id, branch)
 
123
 
 
124
    def revision_tree(self, revision_id):
 
125
        return self.this_branch.repository.revision_tree(revision_id)
116
126
 
117
127
    def ensure_revision_trees(self):
118
128
        if self.this_revision_tree is None:
119
 
            self.this_basis_tree = self.revision_tree(self.this_basis)
 
129
            self.this_basis_tree = self.this_branch.repository.revision_tree(
 
130
                self.this_basis)
120
131
            if self.this_basis == self.this_rev_id:
121
132
                self.this_revision_tree = self.this_basis_tree
122
133
 
152
163
                raise BzrCommandError("Working tree has uncommitted changes.")
153
164
 
154
165
    def compare_basis(self):
155
 
        try:
156
 
            basis_tree = self.revision_tree(self.this_tree.last_revision())
157
 
        except errors.RevisionNotPresent:
158
 
            basis_tree = self.this_tree.basis_tree()
159
 
        changes = self.this_tree.changes_from(basis_tree)
 
166
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
160
167
        if not changes.has_changed():
161
168
            self.this_rev_id = self.this_basis
162
169
 
163
170
    def set_interesting_files(self, file_list):
164
 
        self.interesting_files = file_list
 
171
        try:
 
172
            self._set_interesting_files(file_list)
 
173
        except NotVersionedError, e:
 
174
            raise BzrCommandError("%s is not a source file in any"
 
175
                                      " tree." % e.path)
 
176
 
 
177
    def _set_interesting_files(self, file_list):
 
178
        """Set the list of interesting ids from a list of files."""
 
179
        if file_list is None:
 
180
            self.interesting_ids = None
 
181
            return
 
182
 
 
183
        interesting_ids = set()
 
184
        for path in file_list:
 
185
            found_id = False
 
186
            # TODO: jam 20070226 The trees are not locked at this time,
 
187
            #       wouldn't it make merge faster if it locks everything in the
 
188
            #       beginning? It locks at do_merge time, but this happens
 
189
            #       before that.
 
190
            for tree in (self.this_tree, self.base_tree, self.other_tree):
 
191
                file_id = tree.path2id(path)
 
192
                if file_id is not None:
 
193
                    interesting_ids.add(file_id)
 
194
                    found_id = True
 
195
            if not found_id:
 
196
                raise NotVersionedError(path=path)
 
197
        self.interesting_ids = interesting_ids
165
198
 
166
199
    def set_pending(self):
167
 
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
168
 
            return
169
 
        self._add_parent()
170
 
 
171
 
    def _add_parent(self):
172
 
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
173
 
        new_parent_trees = []
174
 
        for revision_id in new_parents:
175
 
            try:
176
 
                tree = self.revision_tree(revision_id)
177
 
            except errors.RevisionNotPresent:
178
 
                tree = None
179
 
            else:
180
 
                tree.lock_read()
181
 
            new_parent_trees.append((revision_id, tree))
182
 
        try:
183
 
            self.this_tree.set_parent_trees(new_parent_trees,
184
 
                                            allow_leftmost_as_ghost=True)
185
 
        finally:
186
 
            for _revision_id, tree in new_parent_trees:
187
 
                if tree is not None:
188
 
                    tree.unlock()
189
 
 
190
 
    def set_other(self, other_revision, possible_transports=None):
 
200
        if not self.base_is_ancestor:
 
201
            return
 
202
        if self.other_rev_id is None:
 
203
            return
 
204
        ancestry = set(self.this_branch.repository.get_ancestry(
 
205
            self.this_basis, topo_sorted=False))
 
206
        if self.other_rev_id in ancestry:
 
207
            return
 
208
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
 
209
 
 
210
    def set_other(self, other_revision):
191
211
        """Set the revision and tree to merge from.
192
212
 
193
213
        This sets the other_tree, other_rev_id, other_basis attributes.
194
214
 
195
215
        :param other_revision: The [path, revision] list to merge from.
196
216
        """
197
 
        self.other_branch, self.other_tree = self._get_tree(other_revision,
198
 
                                                            possible_transports)
 
217
        self.other_branch, self.other_tree = _get_tree(other_revision,
 
218
                                                  self.this_branch)
199
219
        if other_revision[1] == -1:
200
 
            self.other_rev_id = _mod_revision.ensure_null(
201
 
                self.other_branch.last_revision())
202
 
            if _mod_revision.is_null(self.other_rev_id):
 
220
            self.other_rev_id = self.other_branch.last_revision()
 
221
            if self.other_rev_id is None:
203
222
                raise NoCommits(self.other_branch)
204
223
            self.other_basis = self.other_rev_id
205
224
        elif other_revision[1] is not None:
210
229
            self.other_basis = self.other_branch.last_revision()
211
230
            if self.other_basis is None:
212
231
                raise NoCommits(self.other_branch)
213
 
        if self.other_rev_id is not None:
214
 
            self._cached_trees[self.other_rev_id] = self.other_tree
215
 
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
 
232
        if self.other_branch.base != self.this_branch.base:
 
233
            self.this_branch.fetch(self.other_branch,
 
234
                                   last_revision=self.other_basis)
216
235
 
217
236
    def set_other_revision(self, revision_id, other_branch):
218
237
        """Set 'other' based on a branch and revision id
222
241
        """
223
242
        self.other_rev_id = revision_id
224
243
        self.other_branch = other_branch
225
 
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
 
244
        self.this_branch.fetch(other_branch, self.other_rev_id)
226
245
        self.other_tree = self.revision_tree(revision_id)
227
246
        self.other_basis = revision_id
228
247
 
229
 
    def set_base_revision(self, revision_id, branch):
230
 
        """Set 'base' based on a branch and revision id
231
 
 
232
 
        :param revision_id: The revision to use for a tree
233
 
        :param branch: The branch containing this tree
234
 
        """
235
 
        self.base_rev_id = revision_id
236
 
        self.base_branch = branch
237
 
        self._maybe_fetch(branch, self.this_branch, revision_id)
238
 
        self.base_tree = self.revision_tree(revision_id)
239
 
        self.base_is_ancestor = is_ancestor(self.this_basis,
240
 
                                            self.base_rev_id,
241
 
                                            self.this_branch)
242
 
        self.base_is_other_ancestor = is_ancestor(self.other_basis,
243
 
                                                  self.base_rev_id,
244
 
                                                  self.this_branch)
245
 
 
246
 
    def _maybe_fetch(self, source, target, revision_id):
247
 
        if (source.repository.bzrdir.root_transport.base !=
248
 
            target.repository.bzrdir.root_transport.base):
249
 
            target.fetch(source, revision_id)
250
 
 
251
248
    def find_base(self):
252
 
        this_repo = self.this_branch.repository
253
 
        graph = this_repo.get_graph()
254
 
        revisions = [ensure_null(self.this_basis),
255
 
                     ensure_null(self.other_basis)]
256
 
        if NULL_REVISION in revisions:
257
 
            self.base_rev_id = NULL_REVISION
258
 
        else:
259
 
            self.base_rev_id = graph.find_unique_lca(*revisions)
260
 
            if self.base_rev_id == NULL_REVISION:
261
 
                raise UnrelatedBranches()
262
 
        self.base_tree = self.revision_tree(self.base_rev_id)
263
 
        self.base_is_ancestor = True
264
 
        self.base_is_other_ancestor = True
 
249
        self.set_base([None, None])
265
250
 
266
251
    def set_base(self, base_revision):
267
252
        """Set the base revision to use for the merge.
270
255
        """
271
256
        mutter("doing merge() with no base_revision specified")
272
257
        if base_revision == [None, None]:
273
 
            self.find_base()
 
258
            try:
 
259
                pb = ui.ui_factory.nested_progress_bar()
 
260
                try:
 
261
                    this_repo = self.this_branch.repository
 
262
                    graph = this_repo.get_graph()
 
263
                    revisions = [ensure_null(self.this_basis),
 
264
                                 ensure_null(self.other_basis)]
 
265
                    if NULL_REVISION in revisions:
 
266
                        self.base_rev_id = NULL_REVISION
 
267
                    else:
 
268
                        self.base_rev_id = graph.find_unique_lca(*revisions)
 
269
                        if self.base_rev_id == NULL_REVISION:
 
270
                            raise UnrelatedBranches()
 
271
                finally:
 
272
                    pb.finished()
 
273
            except NoCommonAncestor:
 
274
                raise UnrelatedBranches()
 
275
            self.base_tree = _get_revid_tree_from_tree(self.this_tree,
 
276
                                                       self.base_rev_id,
 
277
                                                       None)
 
278
            self.base_is_ancestor = True
274
279
        else:
275
 
            base_branch, self.base_tree = self._get_tree(base_revision)
 
280
            base_branch, self.base_tree = _get_tree(base_revision)
276
281
            if base_revision[1] == -1:
277
282
                self.base_rev_id = base_branch.last_revision()
278
283
            elif base_revision[1] is None:
279
 
                self.base_rev_id = _mod_revision.NULL_REVISION
 
284
                self.base_rev_id = None
280
285
            else:
281
 
                self.base_rev_id = _mod_revision.ensure_null(
282
 
                    base_branch.get_rev_id(base_revision[1]))
283
 
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
 
286
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
 
287
            if self.this_branch.base != base_branch.base:
 
288
                self.this_branch.fetch(base_branch)
284
289
            self.base_is_ancestor = is_ancestor(self.this_basis, 
285
290
                                                self.base_rev_id,
286
291
                                                self.this_branch)
287
 
            self.base_is_other_ancestor = is_ancestor(self.other_basis,
288
 
                                                      self.base_rev_id,
289
 
                                                      self.this_branch)
290
292
 
291
293
    def do_merge(self):
292
294
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
293
295
                  'other_tree': self.other_tree,
294
296
                  'interesting_ids': self.interesting_ids,
295
 
                  'interesting_files': self.interesting_files,
296
297
                  'pp': self.pp}
297
298
        if self.merge_type.requires_base:
298
299
            kwargs['base_tree'] = self.base_tree
346
347
 
347
348
        return len(merge.cooked_conflicts)
348
349
 
 
350
    def regen_inventory(self, new_entries):
 
351
        old_entries = self.this_tree.read_working_inventory()
 
352
        new_inventory = {}
 
353
        by_path = {}
 
354
        new_entries_map = {} 
 
355
        for path, file_id in new_entries:
 
356
            if path is None:
 
357
                continue
 
358
            new_entries_map[file_id] = path
 
359
 
 
360
        def id2path(file_id):
 
361
            path = new_entries_map.get(file_id)
 
362
            if path is not None:
 
363
                return path
 
364
            entry = old_entries[file_id]
 
365
            if entry.parent_id is None:
 
366
                return entry.name
 
367
            return pathjoin(id2path(entry.parent_id), entry.name)
 
368
            
 
369
        for file_id in old_entries:
 
370
            entry = old_entries[file_id]
 
371
            path = id2path(file_id)
 
372
            if file_id in self.base_tree.inventory:
 
373
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
 
374
            else:
 
375
                executable = getattr(entry, 'executable', False)
 
376
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
 
377
                                      entry.kind, executable)
 
378
                                      
 
379
            by_path[path] = file_id
 
380
        
 
381
        deletions = 0
 
382
        insertions = 0
 
383
        new_path_list = []
 
384
        for path, file_id in new_entries:
 
385
            if path is None:
 
386
                del new_inventory[file_id]
 
387
                deletions += 1
 
388
            else:
 
389
                new_path_list.append((path, file_id))
 
390
                if file_id not in old_entries:
 
391
                    insertions += 1
 
392
        # Ensure no file is added before its parent
 
393
        new_path_list.sort()
 
394
        for path, file_id in new_path_list:
 
395
            if path == '':
 
396
                parent = None
 
397
            else:
 
398
                parent = by_path[os.path.dirname(path)]
 
399
            abspath = pathjoin(self.this_tree.basedir, path)
 
400
            kind = osutils.file_kind(abspath)
 
401
            if file_id in self.base_tree.inventory:
 
402
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
 
403
            else:
 
404
                executable = False
 
405
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
 
406
            by_path[path] = file_id 
 
407
 
 
408
        # Get a list in insertion order
 
409
        new_inventory_list = new_inventory.values()
 
410
        mutter ("""Inventory regeneration:
 
411
    old length: %i insertions: %i deletions: %i new_length: %i"""\
 
412
            % (len(old_entries), insertions, deletions, 
 
413
               len(new_inventory_list)))
 
414
        assert len(new_inventory_list) == len(old_entries) + insertions\
 
415
            - deletions
 
416
        new_inventory_list.sort()
 
417
        return new_inventory_list
 
418
 
349
419
 
350
420
class Merge3Merger(object):
351
421
    """Three-way merger that uses the merge3 text merger"""
353
423
    supports_reprocess = True
354
424
    supports_show_base = True
355
425
    history_based = False
356
 
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
357
426
 
358
427
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
359
428
                 interesting_ids=None, reprocess=False, show_base=False,
360
 
                 pb=DummyProgress(), pp=None, change_reporter=None,
361
 
                 interesting_files=None):
362
 
        """Initialize the merger object and perform the merge.
363
 
 
364
 
        :param working_tree: The working tree to apply the merge to
365
 
        :param this_tree: The local tree in the merge operation
366
 
        :param base_tree: The common tree in the merge operation
367
 
        :param other_tree: The other other tree to merge changes from
368
 
        :param interesting_ids: The file_ids of files that should be
369
 
            participate in the merge.  May not be combined with
370
 
            interesting_files.
371
 
        :param: reprocess If True, perform conflict-reduction processing.
372
 
        :param show_base: If True, show the base revision in text conflicts.
373
 
            (incompatible with reprocess)
374
 
        :param pb: A Progress bar
375
 
        :param pp: A ProgressPhase object
376
 
        :param change_reporter: An object that should report changes made
377
 
        :param interesting_files: The tree-relative paths of files that should
378
 
            participate in the merge.  If these paths refer to directories,
379
 
            the contents of those directories will also be included.  May not
380
 
            be combined with interesting_ids.  If neither interesting_files nor
381
 
            interesting_ids is specified, all files may participate in the
382
 
            merge.
383
 
        """
 
429
                 pb=DummyProgress(), pp=None, change_reporter=None):
 
430
        """Initialize the merger object and perform the merge."""
384
431
        object.__init__(self)
385
 
        if interesting_files is not None:
386
 
            assert interesting_ids is None
387
 
        self.interesting_ids = interesting_ids
388
 
        self.interesting_files = interesting_files
389
432
        self.this_tree = working_tree
390
433
        self.this_tree.lock_tree_write()
391
434
        self.base_tree = base_tree
402
445
        if self.pp is None:
403
446
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
404
447
 
 
448
        if interesting_ids is not None:
 
449
            all_ids = interesting_ids
 
450
        else:
 
451
            all_ids = set(base_tree)
 
452
            all_ids.update(other_tree)
405
453
        self.tt = TreeTransform(working_tree, self.pb)
406
454
        try:
407
455
            self.pp.next_phase()
408
 
            entries = self._entries3()
409
456
            child_pb = ui.ui_factory.nested_progress_bar()
410
457
            try:
411
 
                for num, (file_id, changed, parents3, names3,
412
 
                          executable3) in enumerate(entries):
413
 
                    child_pb.update('Preparing file merge', num, len(entries))
414
 
                    self._merge_names(file_id, parents3, names3)
415
 
                    if changed:
416
 
                        file_status = self.merge_contents(file_id)
417
 
                    else:
418
 
                        file_status = 'unmodified'
419
 
                    self._merge_executable(file_id,
420
 
                        executable3, file_status)
 
458
                for num, file_id in enumerate(all_ids):
 
459
                    child_pb.update('Preparing file merge', num, len(all_ids))
 
460
                    self.merge_names(file_id)
 
461
                    file_status = self.merge_contents(file_id)
 
462
                    self.merge_executable(file_id, file_status)
421
463
            finally:
422
464
                child_pb.finished()
423
465
            self.fix_root()
424
466
            self.pp.next_phase()
425
467
            child_pb = ui.ui_factory.nested_progress_bar()
426
468
            try:
427
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
428
 
                    lambda t, c: conflict_pass(t, c, self.other_tree))
 
469
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
429
470
            finally:
430
471
                child_pb.finished()
431
472
            if change_reporter is not None:
435
476
            for conflict in self.cooked_conflicts:
436
477
                warning(conflict)
437
478
            self.pp.next_phase()
438
 
            results = self.tt.apply(no_conflicts=True)
 
479
            results = self.tt.apply()
439
480
            self.write_modified(results)
440
481
            try:
441
482
                working_tree.add_conflicts(self.cooked_conflicts)
448
489
            self.this_tree.unlock()
449
490
            self.pb.clear()
450
491
 
451
 
    def _entries3(self):
452
 
        """Gather data about files modified between three trees.
453
 
 
454
 
        Return a list of tuples of file_id, changed, parents3, names3,
455
 
        executable3.  changed is a boolean indicating whether the file contents
456
 
        or kind were changed.  parents3 is a tuple of parent ids for base,
457
 
        other and this.  names3 is a tuple of names for base, other and this.
458
 
        executable3 is a tuple of execute-bit values for base, other and this.
459
 
        """
460
 
        result = []
461
 
        iterator = self.other_tree._iter_changes(self.base_tree,
462
 
                include_unchanged=True, specific_files=self.interesting_files,
463
 
                extra_trees=[self.this_tree])
464
 
        for (file_id, paths, changed, versioned, parents, names, kind,
465
 
             executable) in iterator:
466
 
            if (self.interesting_ids is not None and
467
 
                file_id not in self.interesting_ids):
468
 
                continue
469
 
            if file_id in self.this_tree.inventory:
470
 
                entry = self.this_tree.inventory[file_id]
471
 
                this_name = entry.name
472
 
                this_parent = entry.parent_id
473
 
                this_executable = entry.executable
474
 
            else:
475
 
                this_name = None
476
 
                this_parent = None
477
 
                this_executable = None
478
 
            parents3 = parents + (this_parent,)
479
 
            names3 = names + (this_name,)
480
 
            executable3 = executable + (this_executable,)
481
 
            result.append((file_id, changed, parents3, names3, executable3))
482
 
        return result
483
 
 
484
492
    def fix_root(self):
485
493
        try:
486
494
            self.tt.final_kind(self.tt.root)
558
566
        return tree.kind(file_id)
559
567
 
560
568
    @staticmethod
561
 
    def _three_way(base, other, this):
562
 
        #if base == other, either they all agree, or only THIS has changed.
563
 
        if base == other:
564
 
            return 'this'
565
 
        elif this not in (base, other):
566
 
            return 'conflict'
567
 
        # "Ambiguous clean merge" -- both sides have made the same change.
568
 
        elif this == other:
569
 
            return "this"
570
 
        # this == base: only other has changed.
571
 
        else:
572
 
            return "other"
573
 
 
574
 
    @staticmethod
575
569
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
576
570
        """Do a three-way test on a scalar.
577
571
        Return "this", "other" or "conflict", depending whether a value wins.
592
586
            return "other"
593
587
 
594
588
    def merge_names(self, file_id):
 
589
        """Perform a merge on file_id names and parents"""
595
590
        def get_entry(tree):
596
591
            if file_id in tree.inventory:
597
592
                return tree.inventory[file_id]
600
595
        this_entry = get_entry(self.this_tree)
601
596
        other_entry = get_entry(self.other_tree)
602
597
        base_entry = get_entry(self.base_tree)
603
 
        entries = (base_entry, other_entry, this_entry)
604
 
        names = []
605
 
        parents = []
606
 
        for entry in entries:
607
 
            if entry is None:
608
 
                names.append(None)
609
 
                parents.append(None)
610
 
            else:
611
 
                names.append(entry.name)
612
 
                parents.append(entry.parent_id)
613
 
        return self._merge_names(file_id, parents, names)
614
 
 
615
 
    def _merge_names(self, file_id, parents, names):
616
 
        """Perform a merge on file_id names and parents"""
617
 
        base_name, other_name, this_name = names
618
 
        base_parent, other_parent, this_parent = parents
619
 
 
620
 
        name_winner = self._three_way(*names)
621
 
 
622
 
        parent_id_winner = self._three_way(*parents)
623
 
        if this_name is None:
 
598
        name_winner = self.scalar_three_way(this_entry, base_entry, 
 
599
                                            other_entry, file_id, self.name)
 
600
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
 
601
                                                 other_entry, file_id, 
 
602
                                                 self.parent)
 
603
        if this_entry is None:
624
604
            if name_winner == "this":
625
605
                name_winner = "other"
626
606
            if parent_id_winner == "this":
630
610
        if name_winner == "conflict":
631
611
            trans_id = self.tt.trans_id_file_id(file_id)
632
612
            self._raw_conflicts.append(('name conflict', trans_id, 
633
 
                                        this_name, other_name))
 
613
                                        self.name(this_entry, file_id), 
 
614
                                        self.name(other_entry, file_id)))
634
615
        if parent_id_winner == "conflict":
635
616
            trans_id = self.tt.trans_id_file_id(file_id)
636
617
            self._raw_conflicts.append(('parent conflict', trans_id, 
637
 
                                        this_parent, other_parent))
638
 
        if other_name is None:
 
618
                                        self.parent(this_entry, file_id), 
 
619
                                        self.parent(other_entry, file_id)))
 
620
        if other_entry is None:
639
621
            # it doesn't matter whether the result was 'other' or 
640
622
            # 'conflict'-- if there's no 'other', we leave it alone.
641
623
            return
642
624
        # if we get here, name_winner and parent_winner are set to safe values.
 
625
        winner_entry = {"this": this_entry, "other": other_entry, 
 
626
                        "conflict": other_entry}
643
627
        trans_id = self.tt.trans_id_file_id(file_id)
644
 
        parent_id = parents[self.winner_idx[parent_id_winner]]
 
628
        parent_id = winner_entry[parent_id_winner].parent_id
645
629
        if parent_id is not None:
646
630
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
647
 
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
 
631
            self.tt.adjust_path(winner_entry[name_winner].name, 
648
632
                                parent_trans_id, trans_id)
649
633
 
650
634
    def merge_contents(self, file_id):
810
794
 
811
795
    def merge_executable(self, file_id, file_status):
812
796
        """Perform a merge on the execute bit."""
813
 
        executable = [self.executable(t, file_id) for t in (self.base_tree,
814
 
                      self.other_tree, self.this_tree)]
815
 
        self._merge_executable(file_id, executable, file_status)
816
 
 
817
 
    def _merge_executable(self, file_id, executable, file_status):
818
 
        """Perform a merge on the execute bit."""
819
 
        base_executable, other_executable, this_executable = executable
820
797
        if file_status == "deleted":
821
798
            return
822
799
        trans_id = self.tt.trans_id_file_id(file_id)
825
802
                return
826
803
        except NoSuchFile:
827
804
            return
828
 
        winner = self._three_way(*executable)
 
805
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
 
806
                                       self.other_tree, file_id, 
 
807
                                       self.executable)
829
808
        if winner == "conflict":
830
809
        # There must be a None in here, if we have a conflict, but we
831
810
        # need executability since file status was not deleted.
835
814
                winner = "other"
836
815
        if winner == "this":
837
816
            if file_status == "modified":
838
 
                executability = this_executable
 
817
                executability = self.this_tree.is_executable(file_id)
839
818
                if executability is not None:
840
819
                    trans_id = self.tt.trans_id_file_id(file_id)
841
820
                    self.tt.set_executability(executability, trans_id)
842
821
        else:
843
822
            assert winner == "other"
844
823
            if file_id in self.other_tree:
845
 
                executability = other_executable
 
824
                executability = self.other_tree.is_executable(file_id)
846
825
            elif file_id in self.this_tree:
847
 
                executability = this_executable
 
826
                executability = self.this_tree.is_executable(file_id)
848
827
            elif file_id in self.base_tree:
849
 
                executability = base_executable
 
828
                executability = self.base_tree.is_executable(file_id)
850
829
            if executability is not None:
851
830
                trans_id = self.tt.trans_id_file_id(file_id)
852
831
                self.tt.set_executability(executability, trans_id)
918
897
 
919
898
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
920
899
                 interesting_ids=None, pb=DummyProgress(), pp=None,
921
 
                 reprocess=False, change_reporter=None,
922
 
                 interesting_files=None):
 
900
                 reprocess=False, change_reporter=None):
 
901
        self.this_revision_tree = self._get_revision_tree(this_tree)
 
902
        self.other_revision_tree = self._get_revision_tree(other_tree)
923
903
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
924
904
                                          base_tree, other_tree, 
925
905
                                          interesting_ids=interesting_ids, 
926
906
                                          pb=pb, pp=pp, reprocess=reprocess,
927
907
                                          change_reporter=change_reporter)
928
908
 
 
909
    def _get_revision_tree(self, tree):
 
910
        """Return a revision tree related to this tree.
 
911
        If the tree is a WorkingTree, the basis will be returned.
 
912
        """
 
913
        if getattr(tree, 'get_weave', False) is False:
 
914
            # If we have a WorkingTree, try using the basis
 
915
            return tree.branch.basis_tree()
 
916
        else:
 
917
            return tree
 
918
 
 
919
    def _check_file(self, file_id):
 
920
        """Check that the revision tree's version of the file matches."""
 
921
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
 
922
                         (self.other_tree, self.other_revision_tree)):
 
923
            if rt is tree:
 
924
                continue
 
925
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
 
926
                raise WorkingTreeNotRevision(self.this_tree)
 
927
 
929
928
    def _merged_lines(self, file_id):
930
929
        """Generate the merged lines.
931
930
        There is no distinction between lines that are meant to contain <<<<<<<
932
931
        and conflicts.
933
932
        """
934
 
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
935
 
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
936
 
            '>>>>>>> MERGE-SOURCE\n')
937
 
        return textmerge.merge_lines(self.reprocess)
 
933
        weave = self.this_revision_tree.get_weave(file_id)
 
934
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
 
935
        other_revision_id = \
 
936
            self.other_revision_tree.inventory[file_id].revision
 
937
        wm = WeaveMerge(weave, this_revision_id, other_revision_id, 
 
938
                        '<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
 
939
        return wm.merge_lines(self.reprocess)
938
940
 
939
941
    def text_merge(self, file_id, trans_id):
940
942
        """Perform a (weave) text merge for a given file and file-id.
941
943
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
942
944
        and a conflict will be noted.
943
945
        """
 
946
        self._check_file(file_id)
944
947
        lines, conflicts = self._merged_lines(file_id)
945
948
        lines = list(lines)
946
949
        # Note we're checking whether the OUTPUT is binary in this case, 
1028
1031
    if interesting_files:
1029
1032
        assert not interesting_ids, ('Only supply interesting_ids'
1030
1033
                                     ' or interesting_files')
1031
 
        merger.interesting_files = interesting_files
 
1034
        merger._set_interesting_files(interesting_files)
1032
1035
    merger.show_base = show_base
1033
1036
    merger.reprocess = reprocess
1034
1037
    merger.other_rev_id = other_rev_id
1042
1045
    """
1043
1046
    from bzrlib import option
1044
1047
    return option._merge_type_registry
1045
 
 
1046
 
 
1047
 
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1048
 
    def status_a(revision, text):
1049
 
        if revision in ancestors_b:
1050
 
            return 'killed-b', text
1051
 
        else:
1052
 
            return 'new-a', text
1053
 
 
1054
 
    def status_b(revision, text):
1055
 
        if revision in ancestors_a:
1056
 
            return 'killed-a', text
1057
 
        else:
1058
 
            return 'new-b', text
1059
 
 
1060
 
    plain_a = [t for (a, t) in annotated_a]
1061
 
    plain_b = [t for (a, t) in annotated_b]
1062
 
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1063
 
    blocks = matcher.get_matching_blocks()
1064
 
    a_cur = 0
1065
 
    b_cur = 0
1066
 
    for ai, bi, l in blocks:
1067
 
        # process all mismatched sections
1068
 
        # (last mismatched section is handled because blocks always
1069
 
        # includes a 0-length last block)
1070
 
        for revision, text in annotated_a[a_cur:ai]:
1071
 
            yield status_a(revision, text)
1072
 
        for revision, text in annotated_b[b_cur:bi]:
1073
 
            yield status_b(revision, text)
1074
 
 
1075
 
        # and now the matched section
1076
 
        a_cur = ai + l
1077
 
        b_cur = bi + l
1078
 
        for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1079
 
            assert text_a == text_b
1080
 
            yield "unchanged", text_a