~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: 2008-04-07 07:52:50 UTC
  • mfrom: (3340.1.1 208418-1.4)
  • Revision ID: pqm@pqm.ubuntu.com-20080407075250-phs53xnslo8boaeo
Return the correct knit serialisation method in _StreamAccess.
        (Andrew Bennetts, Martin Pool, Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
import warnings
21
21
 
22
22
from bzrlib import (
 
23
    debug,
 
24
    errors,
23
25
    osutils,
 
26
    patiencediff,
24
27
    registry,
 
28
    revision as _mod_revision,
25
29
    )
26
30
from bzrlib.branch import Branch
27
31
from bzrlib.conflicts import ConflictList, Conflict
41
45
from bzrlib.merge3 import Merge3
42
46
from bzrlib.osutils import rename, pathjoin
43
47
from progress import DummyProgress, ProgressPhase
44
 
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
 
48
from bzrlib.revision import (NULL_REVISION, ensure_null)
45
49
from bzrlib.textfile import check_text_lines
46
 
from bzrlib.trace import mutter, warning, note
47
 
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
 
                              FinalPaths, create_by_entry, unique_add,
49
 
                              ROOT_PARENT)
50
 
from bzrlib.versionedfile import WeaveMerge
 
50
from bzrlib.trace import mutter, warning, note, is_quiet
 
51
from bzrlib.transform import (TransformPreview, TreeTransform,
 
52
                              resolve_conflicts, cook_conflicts,
 
53
                              conflict_pass, FinalPaths, create_by_entry,
 
54
                              unique_add, ROOT_PARENT)
 
55
from bzrlib.versionedfile import PlanWeaveMerge
51
56
from bzrlib import ui
52
57
 
53
58
# TODO: Report back as changes are merged in
54
59
 
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
 
 
93
60
 
94
61
def transform_tree(from_tree, to_tree, interesting_ids=None):
95
62
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
99
66
class Merger(object):
100
67
    def __init__(self, this_branch, other_tree=None, base_tree=None,
101
68
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
102
 
                 recurse='down'):
 
69
                 recurse='down', revision_graph=None):
103
70
        object.__init__(self)
104
71
        assert this_tree is not None, "this_tree is required"
105
72
        self.this_branch = this_branch
106
 
        self.this_basis = this_branch.last_revision()
 
73
        self.this_basis = _mod_revision.ensure_null(
 
74
            this_branch.last_revision())
107
75
        self.this_rev_id = None
108
76
        self.this_tree = this_tree
109
77
        self.this_revision_tree = None
114
82
        self.ignore_zero = False
115
83
        self.backup_files = False
116
84
        self.interesting_ids = None
 
85
        self.interesting_files = None
117
86
        self.show_base = False
118
87
        self.reprocess = False
119
88
        self._pb = pb
120
89
        self.pp = None
121
90
        self.recurse = recurse
122
91
        self.change_reporter = change_reporter
123
 
 
124
 
    def revision_tree(self, revision_id):
125
 
        return self.this_branch.repository.revision_tree(revision_id)
 
92
        self._cached_trees = {}
 
93
        self._revision_graph = revision_graph
 
94
        self._base_is_ancestor = None
 
95
        self._base_is_other_ancestor = None
 
96
 
 
97
    @property
 
98
    def revision_graph(self):
 
99
        if self._revision_graph is None:
 
100
            self._revision_graph = self.this_branch.repository.get_graph()
 
101
        return self._revision_graph
 
102
 
 
103
    def _set_base_is_ancestor(self, value):
 
104
        self._base_is_ancestor = value
 
105
 
 
106
    def _get_base_is_ancestor(self):
 
107
        if self._base_is_ancestor is None:
 
108
            self._base_is_ancestor = self.revision_graph.is_ancestor(
 
109
                self.base_rev_id, self.this_basis)
 
110
        return self._base_is_ancestor
 
111
 
 
112
    base_is_ancestor = property(_get_base_is_ancestor, _set_base_is_ancestor)
 
113
 
 
114
    def _set_base_is_other_ancestor(self, value):
 
115
        self._base_is_other_ancestor = value
 
116
 
 
117
    def _get_base_is_other_ancestor(self):
 
118
        if self._base_is_other_ancestor is None:
 
119
            if self.other_basis is None:
 
120
                return True
 
121
            self._base_is_other_ancestor = self.revision_graph.is_ancestor(
 
122
                self.base_rev_id, self.other_basis)
 
123
        return self._base_is_other_ancestor
 
124
 
 
125
    base_is_other_ancestor = property(_get_base_is_other_ancestor,
 
126
                                      _set_base_is_other_ancestor)
 
127
 
 
128
    @staticmethod
 
129
    def from_uncommitted(tree, other_tree, pb):
 
130
        """Return a Merger for uncommitted changes in other_tree.
 
131
 
 
132
        :param tree: The tree to merge into
 
133
        :param other_tree: The tree to get uncommitted changes from
 
134
        :param pb: A progress indicator
 
135
        """
 
136
        merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
 
137
                        pb)
 
138
        merger.base_rev_id = merger.base_tree.get_revision_id()
 
139
        merger.other_rev_id = None
 
140
        merger.other_basis = merger.base_rev_id
 
141
        return merger
 
142
 
 
143
    @classmethod
 
144
    def from_mergeable(klass, tree, mergeable, pb):
 
145
        """Return a Merger for a bundle or merge directive.
 
146
 
 
147
        :param tree: The tree to merge changes into
 
148
        :param mergeable: A merge directive or bundle
 
149
        :param pb: A progress indicator
 
150
        """
 
151
        mergeable.install_revisions(tree.branch.repository)
 
152
        base_revision_id, other_revision_id, verified =\
 
153
            mergeable.get_merge_request(tree.branch.repository)
 
154
        revision_graph = tree.branch.repository.get_graph()
 
155
        if (base_revision_id != _mod_revision.NULL_REVISION and
 
156
            revision_graph.is_ancestor(
 
157
            base_revision_id, tree.branch.last_revision())):
 
158
            base_revision_id = None
 
159
        else:
 
160
            warning('Performing cherrypick')
 
161
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
 
162
                                         base_revision_id, revision_graph=
 
163
                                         revision_graph)
 
164
        return merger, verified
 
165
 
 
166
    @staticmethod
 
167
    def from_revision_ids(pb, tree, other, base=None, other_branch=None,
 
168
                          base_branch=None, revision_graph=None):
 
169
        """Return a Merger for revision-ids.
 
170
 
 
171
        :param tree: The tree to merge changes into
 
172
        :param other: The revision-id to use as OTHER
 
173
        :param base: The revision-id to use as BASE.  If not specified, will
 
174
            be auto-selected.
 
175
        :param other_branch: A branch containing the other revision-id.  If
 
176
            not supplied, tree.branch is used.
 
177
        :param base_branch: A branch containing the base revision-id.  If
 
178
            not supplied, other_branch or tree.branch will be used.
 
179
        :param revision_graph: If you have a revision_graph precomputed, pass
 
180
            it in, otherwise it will be created for you.
 
181
        :param pb: A progress indicator
 
182
        """
 
183
        merger = Merger(tree.branch, this_tree=tree, pb=pb,
 
184
                        revision_graph=revision_graph)
 
185
        if other_branch is None:
 
186
            other_branch = tree.branch
 
187
        merger.set_other_revision(other, other_branch)
 
188
        if base is None:
 
189
            merger.find_base()
 
190
        else:
 
191
            if base_branch is None:
 
192
                base_branch = other_branch
 
193
            merger.set_base_revision(base, base_branch)
 
194
        return merger
 
195
 
 
196
    def revision_tree(self, revision_id, branch=None):
 
197
        if revision_id not in self._cached_trees:
 
198
            if branch is None:
 
199
                branch = self.this_branch
 
200
            try:
 
201
                tree = self.this_tree.revision_tree(revision_id)
 
202
            except errors.NoSuchRevisionInTree:
 
203
                tree = branch.repository.revision_tree(revision_id)
 
204
            self._cached_trees[revision_id] = tree
 
205
        return self._cached_trees[revision_id]
 
206
 
 
207
    def _get_tree(self, treespec, possible_transports=None):
 
208
        from bzrlib import workingtree
 
209
        location, revno = treespec
 
210
        if revno is None:
 
211
            tree = workingtree.WorkingTree.open_containing(location)[0]
 
212
            return tree.branch, tree
 
213
        branch = Branch.open_containing(location, possible_transports)[0]
 
214
        if revno == -1:
 
215
            revision_id = branch.last_revision()
 
216
        else:
 
217
            revision_id = branch.get_rev_id(revno)
 
218
        revision_id = ensure_null(revision_id)
 
219
        return branch, self.revision_tree(revision_id, branch)
126
220
 
127
221
    def ensure_revision_trees(self):
128
222
        if self.this_revision_tree is None:
129
 
            self.this_basis_tree = self.this_branch.repository.revision_tree(
130
 
                self.this_basis)
 
223
            self.this_basis_tree = self.revision_tree(self.this_basis)
131
224
            if self.this_basis == self.this_rev_id:
132
225
                self.this_revision_tree = self.this_basis_tree
133
226
 
160
253
        if check_clean:
161
254
            self.compare_basis()
162
255
            if self.this_basis != self.this_rev_id:
163
 
                raise BzrCommandError("Working tree has uncommitted changes.")
 
256
                raise errors.UncommittedChanges(self.this_tree)
164
257
 
165
258
    def compare_basis(self):
166
 
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
 
259
        try:
 
260
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
261
        except errors.RevisionNotPresent:
 
262
            basis_tree = self.this_tree.basis_tree()
 
263
        changes = self.this_tree.changes_from(basis_tree)
167
264
        if not changes.has_changed():
168
265
            self.this_rev_id = self.this_basis
169
266
 
170
267
    def set_interesting_files(self, 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
 
268
        self.interesting_files = file_list
198
269
 
199
270
    def set_pending(self):
200
 
        if not self.base_is_ancestor:
201
 
            return
202
 
        if self.other_rev_id is None:
203
 
            return
204
 
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
205
 
        if self.other_rev_id in ancestry:
206
 
            return
207
 
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
208
 
 
209
 
    def set_other(self, other_revision):
 
271
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
 
272
            return
 
273
        self._add_parent()
 
274
 
 
275
    def _add_parent(self):
 
276
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
 
277
        new_parent_trees = []
 
278
        for revision_id in new_parents:
 
279
            try:
 
280
                tree = self.revision_tree(revision_id)
 
281
            except errors.RevisionNotPresent:
 
282
                tree = None
 
283
            else:
 
284
                tree.lock_read()
 
285
            new_parent_trees.append((revision_id, tree))
 
286
        try:
 
287
            self.this_tree.set_parent_trees(new_parent_trees,
 
288
                                            allow_leftmost_as_ghost=True)
 
289
        finally:
 
290
            for _revision_id, tree in new_parent_trees:
 
291
                if tree is not None:
 
292
                    tree.unlock()
 
293
 
 
294
    def set_other(self, other_revision, possible_transports=None):
210
295
        """Set the revision and tree to merge from.
211
296
 
212
297
        This sets the other_tree, other_rev_id, other_basis attributes.
213
298
 
214
299
        :param other_revision: The [path, revision] list to merge from.
215
300
        """
216
 
        self.other_branch, self.other_tree = _get_tree(other_revision,
217
 
                                                  self.this_branch)
 
301
        self.other_branch, self.other_tree = self._get_tree(other_revision,
 
302
                                                            possible_transports)
218
303
        if other_revision[1] == -1:
219
 
            self.other_rev_id = self.other_branch.last_revision()
220
 
            if self.other_rev_id is None:
 
304
            self.other_rev_id = _mod_revision.ensure_null(
 
305
                self.other_branch.last_revision())
 
306
            if _mod_revision.is_null(self.other_rev_id):
221
307
                raise NoCommits(self.other_branch)
222
308
            self.other_basis = self.other_rev_id
223
309
        elif other_revision[1] is not None:
228
314
            self.other_basis = self.other_branch.last_revision()
229
315
            if self.other_basis is None:
230
316
                raise NoCommits(self.other_branch)
231
 
        if self.other_branch.base != self.this_branch.base:
232
 
            self.this_branch.fetch(self.other_branch,
233
 
                                   last_revision=self.other_basis)
 
317
        if self.other_rev_id is not None:
 
318
            self._cached_trees[self.other_rev_id] = self.other_tree
 
319
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
234
320
 
235
321
    def set_other_revision(self, revision_id, other_branch):
236
322
        """Set 'other' based on a branch and revision id
240
326
        """
241
327
        self.other_rev_id = revision_id
242
328
        self.other_branch = other_branch
243
 
        self.this_branch.fetch(other_branch, self.other_rev_id)
 
329
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
244
330
        self.other_tree = self.revision_tree(revision_id)
245
331
        self.other_basis = revision_id
246
332
 
 
333
    def set_base_revision(self, revision_id, branch):
 
334
        """Set 'base' based on a branch and revision id
 
335
 
 
336
        :param revision_id: The revision to use for a tree
 
337
        :param branch: The branch containing this tree
 
338
        """
 
339
        self.base_rev_id = revision_id
 
340
        self.base_branch = branch
 
341
        self._maybe_fetch(branch, self.this_branch, revision_id)
 
342
        self.base_tree = self.revision_tree(revision_id)
 
343
 
 
344
    def _maybe_fetch(self, source, target, revision_id):
 
345
        if not source.repository.has_same_location(target.repository):
 
346
            target.fetch(source, revision_id)
 
347
 
247
348
    def find_base(self):
248
 
        self.set_base([None, None])
 
349
        revisions = [ensure_null(self.this_basis),
 
350
                     ensure_null(self.other_basis)]
 
351
        if NULL_REVISION in revisions:
 
352
            self.base_rev_id = NULL_REVISION
 
353
        else:
 
354
            self.base_rev_id, steps = self.revision_graph.find_unique_lca(
 
355
                revisions[0], revisions[1], count_steps=True)
 
356
            if self.base_rev_id == NULL_REVISION:
 
357
                raise UnrelatedBranches()
 
358
            if steps > 1:
 
359
                warning('Warning: criss-cross merge encountered.  See bzr'
 
360
                        ' help criss-cross.')
 
361
        self.base_tree = self.revision_tree(self.base_rev_id)
 
362
        self.base_is_ancestor = True
 
363
        self.base_is_other_ancestor = True
249
364
 
250
365
    def set_base(self, base_revision):
251
366
        """Set the base revision to use for the merge.
254
369
        """
255
370
        mutter("doing merge() with no base_revision specified")
256
371
        if base_revision == [None, None]:
257
 
            try:
258
 
                pb = ui.ui_factory.nested_progress_bar()
259
 
                try:
260
 
                    this_repo = self.this_branch.repository
261
 
                    self.base_rev_id = common_ancestor(self.this_basis, 
262
 
                                                       self.other_basis, 
263
 
                                                       this_repo, pb)
264
 
                finally:
265
 
                    pb.finished()
266
 
            except NoCommonAncestor:
267
 
                raise UnrelatedBranches()
268
 
            self.base_tree = _get_revid_tree_from_tree(self.this_tree,
269
 
                                                       self.base_rev_id,
270
 
                                                       None)
271
 
            self.base_is_ancestor = True
 
372
            self.find_base()
272
373
        else:
273
 
            base_branch, self.base_tree = _get_tree(base_revision)
 
374
            base_branch, self.base_tree = self._get_tree(base_revision)
274
375
            if base_revision[1] == -1:
275
376
                self.base_rev_id = base_branch.last_revision()
276
377
            elif base_revision[1] is None:
277
 
                self.base_rev_id = None
 
378
                self.base_rev_id = _mod_revision.NULL_REVISION
278
379
            else:
279
 
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
280
 
            if self.this_branch.base != base_branch.base:
281
 
                self.this_branch.fetch(base_branch)
282
 
            self.base_is_ancestor = is_ancestor(self.this_basis, 
283
 
                                                self.base_rev_id,
284
 
                                                self.this_branch)
 
380
                self.base_rev_id = _mod_revision.ensure_null(
 
381
                    base_branch.get_rev_id(base_revision[1]))
 
382
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
285
383
 
286
 
    def do_merge(self):
 
384
    def make_merger(self):
287
385
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
288
386
                  'other_tree': self.other_tree,
289
387
                  'interesting_ids': self.interesting_ids,
290
 
                  'pp': self.pp}
 
388
                  'interesting_files': self.interesting_files,
 
389
                  'pp': self.pp,
 
390
                  'do_merge': False}
291
391
        if self.merge_type.requires_base:
292
392
            kwargs['base_tree'] = self.base_tree
293
393
        if self.merge_type.supports_reprocess:
299
399
            kwargs['show_base'] = self.show_base
300
400
        elif self.show_base:
301
401
            raise BzrError("Showing base is not supported for this"
302
 
                                  " merge type. %s" % self.merge_type)
 
402
                           " merge type. %s" % self.merge_type)
 
403
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
 
404
            and not self.base_is_other_ancestor):
 
405
            raise errors.CannotReverseCherrypick()
 
406
        if self.merge_type.supports_cherrypick:
 
407
            kwargs['cherrypick'] = (not self.base_is_ancestor or
 
408
                                    not self.base_is_other_ancestor)
 
409
        return self.merge_type(pb=self._pb,
 
410
                               change_reporter=self.change_reporter,
 
411
                               **kwargs)
 
412
 
 
413
    def do_merge(self):
303
414
        self.this_tree.lock_tree_write()
304
415
        if self.base_tree is not None:
305
416
            self.base_tree.lock_read()
306
417
        if self.other_tree is not None:
307
418
            self.other_tree.lock_read()
308
419
        try:
309
 
            merge = self.merge_type(pb=self._pb,
310
 
                                    change_reporter=self.change_reporter,
311
 
                                    **kwargs)
 
420
            merge = self.make_merger()
 
421
            merge.do_merge()
312
422
            if self.recurse == 'down':
313
423
                for path, file_id in self.this_tree.iter_references():
314
424
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
324
434
                    base_revision = self.base_tree.get_reference_revision(file_id)
325
435
                    sub_merge.base_tree = \
326
436
                        sub_tree.branch.repository.revision_tree(base_revision)
 
437
                    sub_merge.base_rev_id = base_revision
327
438
                    sub_merge.do_merge()
328
439
 
329
440
        finally:
333
444
                self.base_tree.unlock()
334
445
            self.this_tree.unlock()
335
446
        if len(merge.cooked_conflicts) == 0:
336
 
            if not self.ignore_zero:
 
447
            if not self.ignore_zero and not is_quiet():
337
448
                note("All changes applied successfully.")
338
449
        else:
339
450
            note("%d conflicts encountered." % len(merge.cooked_conflicts))
340
451
 
341
452
        return len(merge.cooked_conflicts)
342
453
 
343
 
    def regen_inventory(self, new_entries):
344
 
        old_entries = self.this_tree.read_working_inventory()
345
 
        new_inventory = {}
346
 
        by_path = {}
347
 
        new_entries_map = {} 
348
 
        for path, file_id in new_entries:
349
 
            if path is None:
350
 
                continue
351
 
            new_entries_map[file_id] = path
352
 
 
353
 
        def id2path(file_id):
354
 
            path = new_entries_map.get(file_id)
355
 
            if path is not None:
356
 
                return path
357
 
            entry = old_entries[file_id]
358
 
            if entry.parent_id is None:
359
 
                return entry.name
360
 
            return pathjoin(id2path(entry.parent_id), entry.name)
361
 
            
362
 
        for file_id in old_entries:
363
 
            entry = old_entries[file_id]
364
 
            path = id2path(file_id)
365
 
            if file_id in self.base_tree.inventory:
366
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
367
 
            else:
368
 
                executable = getattr(entry, 'executable', False)
369
 
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
370
 
                                      entry.kind, executable)
371
 
                                      
372
 
            by_path[path] = file_id
373
 
        
374
 
        deletions = 0
375
 
        insertions = 0
376
 
        new_path_list = []
377
 
        for path, file_id in new_entries:
378
 
            if path is None:
379
 
                del new_inventory[file_id]
380
 
                deletions += 1
381
 
            else:
382
 
                new_path_list.append((path, file_id))
383
 
                if file_id not in old_entries:
384
 
                    insertions += 1
385
 
        # Ensure no file is added before its parent
386
 
        new_path_list.sort()
387
 
        for path, file_id in new_path_list:
388
 
            if path == '':
389
 
                parent = None
390
 
            else:
391
 
                parent = by_path[os.path.dirname(path)]
392
 
            abspath = pathjoin(self.this_tree.basedir, path)
393
 
            kind = osutils.file_kind(abspath)
394
 
            if file_id in self.base_tree.inventory:
395
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
396
 
            else:
397
 
                executable = False
398
 
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
399
 
            by_path[path] = file_id 
400
 
 
401
 
        # Get a list in insertion order
402
 
        new_inventory_list = new_inventory.values()
403
 
        mutter ("""Inventory regeneration:
404
 
    old length: %i insertions: %i deletions: %i new_length: %i"""\
405
 
            % (len(old_entries), insertions, deletions, 
406
 
               len(new_inventory_list)))
407
 
        assert len(new_inventory_list) == len(old_entries) + insertions\
408
 
            - deletions
409
 
        new_inventory_list.sort()
410
 
        return new_inventory_list
411
 
 
412
454
 
413
455
class Merge3Merger(object):
414
456
    """Three-way merger that uses the merge3 text merger"""
416
458
    supports_reprocess = True
417
459
    supports_show_base = True
418
460
    history_based = False
 
461
    supports_cherrypick = True
 
462
    supports_reverse_cherrypick = True
 
463
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
419
464
 
420
465
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
421
466
                 interesting_ids=None, reprocess=False, show_base=False,
422
 
                 pb=DummyProgress(), pp=None, change_reporter=None):
423
 
        """Initialize the merger object and perform the merge."""
 
467
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
468
                 interesting_files=None, do_merge=True,
 
469
                 cherrypick=False):
 
470
        """Initialize the merger object and perform the merge.
 
471
 
 
472
        :param working_tree: The working tree to apply the merge to
 
473
        :param this_tree: The local tree in the merge operation
 
474
        :param base_tree: The common tree in the merge operation
 
475
        :param other_tree: The other other tree to merge changes from
 
476
        :param interesting_ids: The file_ids of files that should be
 
477
            participate in the merge.  May not be combined with
 
478
            interesting_files.
 
479
        :param: reprocess If True, perform conflict-reduction processing.
 
480
        :param show_base: If True, show the base revision in text conflicts.
 
481
            (incompatible with reprocess)
 
482
        :param pb: A Progress bar
 
483
        :param pp: A ProgressPhase object
 
484
        :param change_reporter: An object that should report changes made
 
485
        :param interesting_files: The tree-relative paths of files that should
 
486
            participate in the merge.  If these paths refer to directories,
 
487
            the contents of those directories will also be included.  May not
 
488
            be combined with interesting_ids.  If neither interesting_files nor
 
489
            interesting_ids is specified, all files may participate in the
 
490
            merge.
 
491
        """
424
492
        object.__init__(self)
 
493
        if interesting_files is not None:
 
494
            assert interesting_ids is None
 
495
        self.interesting_ids = interesting_ids
 
496
        self.interesting_files = interesting_files
425
497
        self.this_tree = working_tree
426
 
        self.this_tree.lock_tree_write()
427
498
        self.base_tree = base_tree
428
 
        self.base_tree.lock_read()
429
499
        self.other_tree = other_tree
430
 
        self.other_tree.lock_read()
431
500
        self._raw_conflicts = []
432
501
        self.cooked_conflicts = []
433
502
        self.reprocess = reprocess
435
504
        self.pb = pb
436
505
        self.pp = pp
437
506
        self.change_reporter = change_reporter
 
507
        self.cherrypick = cherrypick
438
508
        if self.pp is None:
439
509
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
 
510
        if do_merge:
 
511
            self.do_merge()
440
512
 
441
 
        if interesting_ids is not None:
442
 
            all_ids = interesting_ids
443
 
        else:
444
 
            all_ids = set(base_tree)
445
 
            all_ids.update(other_tree)
446
 
        self.tt = TreeTransform(working_tree, self.pb)
 
513
    def do_merge(self):
 
514
        self.this_tree.lock_tree_write()
 
515
        self.base_tree.lock_read()
 
516
        self.other_tree.lock_read()
 
517
        self.tt = TreeTransform(self.this_tree, self.pb)
447
518
        try:
448
519
            self.pp.next_phase()
449
 
            child_pb = ui.ui_factory.nested_progress_bar()
450
 
            try:
451
 
                for num, file_id in enumerate(all_ids):
452
 
                    child_pb.update('Preparing file merge', num, len(all_ids))
453
 
                    self.merge_names(file_id)
454
 
                    file_status = self.merge_contents(file_id)
455
 
                    self.merge_executable(file_id, file_status)
456
 
            finally:
457
 
                child_pb.finished()
458
 
            self.fix_root()
459
 
            self.pp.next_phase()
460
 
            child_pb = ui.ui_factory.nested_progress_bar()
461
 
            try:
462
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
463
 
            finally:
464
 
                child_pb.finished()
465
 
            if change_reporter is not None:
466
 
                from bzrlib import delta
467
 
                delta.report_changes(self.tt._iter_changes(), change_reporter)
468
 
            self.cook_conflicts(fs_conflicts)
469
 
            for conflict in self.cooked_conflicts:
470
 
                warning(conflict)
471
 
            self.pp.next_phase()
472
 
            results = self.tt.apply()
 
520
            self._compute_transform()
 
521
            self.pp.next_phase()
 
522
            results = self.tt.apply(no_conflicts=True)
473
523
            self.write_modified(results)
474
524
            try:
475
 
                working_tree.add_conflicts(self.cooked_conflicts)
 
525
                self.this_tree.add_conflicts(self.cooked_conflicts)
476
526
            except UnsupportedOperation:
477
527
                pass
478
528
        finally:
482
532
            self.this_tree.unlock()
483
533
            self.pb.clear()
484
534
 
 
535
    def make_preview_transform(self):
 
536
        self.base_tree.lock_read()
 
537
        self.other_tree.lock_read()
 
538
        self.tt = TransformPreview(self.this_tree)
 
539
        try:
 
540
            self.pp.next_phase()
 
541
            self._compute_transform()
 
542
            self.pp.next_phase()
 
543
        finally:
 
544
            self.other_tree.unlock()
 
545
            self.base_tree.unlock()
 
546
            self.pb.clear()
 
547
        return self.tt
 
548
 
 
549
    def _compute_transform(self):
 
550
        entries = self._entries3()
 
551
        child_pb = ui.ui_factory.nested_progress_bar()
 
552
        try:
 
553
            for num, (file_id, changed, parents3, names3,
 
554
                      executable3) in enumerate(entries):
 
555
                child_pb.update('Preparing file merge', num, len(entries))
 
556
                self._merge_names(file_id, parents3, names3)
 
557
                if changed:
 
558
                    file_status = self.merge_contents(file_id)
 
559
                else:
 
560
                    file_status = 'unmodified'
 
561
                self._merge_executable(file_id,
 
562
                    executable3, file_status)
 
563
        finally:
 
564
            child_pb.finished()
 
565
        self.fix_root()
 
566
        self.pp.next_phase()
 
567
        child_pb = ui.ui_factory.nested_progress_bar()
 
568
        try:
 
569
            fs_conflicts = resolve_conflicts(self.tt, child_pb,
 
570
                lambda t, c: conflict_pass(t, c, self.other_tree))
 
571
        finally:
 
572
            child_pb.finished()
 
573
        if self.change_reporter is not None:
 
574
            from bzrlib import delta
 
575
            delta.report_changes(
 
576
                self.tt.iter_changes(), self.change_reporter)
 
577
        self.cook_conflicts(fs_conflicts)
 
578
        for conflict in self.cooked_conflicts:
 
579
            warning(conflict)
 
580
 
 
581
    def _entries3(self):
 
582
        """Gather data about files modified between three trees.
 
583
 
 
584
        Return a list of tuples of file_id, changed, parents3, names3,
 
585
        executable3.  changed is a boolean indicating whether the file contents
 
586
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
587
        other and this.  names3 is a tuple of names for base, other and this.
 
588
        executable3 is a tuple of execute-bit values for base, other and this.
 
589
        """
 
590
        result = []
 
591
        iterator = self.other_tree.iter_changes(self.base_tree,
 
592
                include_unchanged=True, specific_files=self.interesting_files,
 
593
                extra_trees=[self.this_tree])
 
594
        for (file_id, paths, changed, versioned, parents, names, kind,
 
595
             executable) in iterator:
 
596
            if (self.interesting_ids is not None and
 
597
                file_id not in self.interesting_ids):
 
598
                continue
 
599
            if file_id in self.this_tree.inventory:
 
600
                entry = self.this_tree.inventory[file_id]
 
601
                this_name = entry.name
 
602
                this_parent = entry.parent_id
 
603
                this_executable = entry.executable
 
604
            else:
 
605
                this_name = None
 
606
                this_parent = None
 
607
                this_executable = None
 
608
            parents3 = parents + (this_parent,)
 
609
            names3 = names + (this_name,)
 
610
            executable3 = executable + (this_executable,)
 
611
            result.append((file_id, changed, parents3, names3, executable3))
 
612
        return result
 
613
 
485
614
    def fix_root(self):
486
615
        try:
487
616
            self.tt.final_kind(self.tt.root)
492
621
                                 self.tt.root)
493
622
        if self.other_tree.inventory.root is None:
494
623
            return
495
 
        other_root_file_id = self.other_tree.inventory.root.file_id
 
624
        other_root_file_id = self.other_tree.get_root_id()
496
625
        other_root = self.tt.trans_id_file_id(other_root_file_id)
497
626
        if other_root == self.tt.root:
498
627
            return
559
688
        return tree.kind(file_id)
560
689
 
561
690
    @staticmethod
 
691
    def _three_way(base, other, this):
 
692
        #if base == other, either they all agree, or only THIS has changed.
 
693
        if base == other:
 
694
            return 'this'
 
695
        elif this not in (base, other):
 
696
            return 'conflict'
 
697
        # "Ambiguous clean merge" -- both sides have made the same change.
 
698
        elif this == other:
 
699
            return "this"
 
700
        # this == base: only other has changed.
 
701
        else:
 
702
            return "other"
 
703
 
 
704
    @staticmethod
562
705
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
563
706
        """Do a three-way test on a scalar.
564
707
        Return "this", "other" or "conflict", depending whether a value wins.
579
722
            return "other"
580
723
 
581
724
    def merge_names(self, file_id):
582
 
        """Perform a merge on file_id names and parents"""
583
725
        def get_entry(tree):
584
726
            if file_id in tree.inventory:
585
727
                return tree.inventory[file_id]
588
730
        this_entry = get_entry(self.this_tree)
589
731
        other_entry = get_entry(self.other_tree)
590
732
        base_entry = get_entry(self.base_tree)
591
 
        name_winner = self.scalar_three_way(this_entry, base_entry, 
592
 
                                            other_entry, file_id, self.name)
593
 
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
594
 
                                                 other_entry, file_id, 
595
 
                                                 self.parent)
596
 
        if this_entry is None:
 
733
        entries = (base_entry, other_entry, this_entry)
 
734
        names = []
 
735
        parents = []
 
736
        for entry in entries:
 
737
            if entry is None:
 
738
                names.append(None)
 
739
                parents.append(None)
 
740
            else:
 
741
                names.append(entry.name)
 
742
                parents.append(entry.parent_id)
 
743
        return self._merge_names(file_id, parents, names)
 
744
 
 
745
    def _merge_names(self, file_id, parents, names):
 
746
        """Perform a merge on file_id names and parents"""
 
747
        base_name, other_name, this_name = names
 
748
        base_parent, other_parent, this_parent = parents
 
749
 
 
750
        name_winner = self._three_way(*names)
 
751
 
 
752
        parent_id_winner = self._three_way(*parents)
 
753
        if this_name is None:
597
754
            if name_winner == "this":
598
755
                name_winner = "other"
599
756
            if parent_id_winner == "this":
603
760
        if name_winner == "conflict":
604
761
            trans_id = self.tt.trans_id_file_id(file_id)
605
762
            self._raw_conflicts.append(('name conflict', trans_id, 
606
 
                                        self.name(this_entry, file_id), 
607
 
                                        self.name(other_entry, file_id)))
 
763
                                        this_name, other_name))
608
764
        if parent_id_winner == "conflict":
609
765
            trans_id = self.tt.trans_id_file_id(file_id)
610
766
            self._raw_conflicts.append(('parent conflict', trans_id, 
611
 
                                        self.parent(this_entry, file_id), 
612
 
                                        self.parent(other_entry, file_id)))
613
 
        if other_entry is None:
 
767
                                        this_parent, other_parent))
 
768
        if other_name is None:
614
769
            # it doesn't matter whether the result was 'other' or 
615
770
            # 'conflict'-- if there's no 'other', we leave it alone.
616
771
            return
617
772
        # if we get here, name_winner and parent_winner are set to safe values.
618
 
        winner_entry = {"this": this_entry, "other": other_entry, 
619
 
                        "conflict": other_entry}
620
773
        trans_id = self.tt.trans_id_file_id(file_id)
621
 
        parent_id = winner_entry[parent_id_winner].parent_id
 
774
        parent_id = parents[self.winner_idx[parent_id_winner]]
622
775
        if parent_id is not None:
623
776
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
624
 
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
777
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
625
778
                                parent_trans_id, trans_id)
626
779
 
627
780
    def merge_contents(self, file_id):
720
873
            base_lines = []
721
874
        other_lines = self.get_lines(self.other_tree, file_id)
722
875
        this_lines = self.get_lines(self.this_tree, file_id)
723
 
        m3 = Merge3(base_lines, this_lines, other_lines)
 
876
        m3 = Merge3(base_lines, this_lines, other_lines,
 
877
                    is_cherrypick=self.cherrypick)
724
878
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
725
879
        if self.show_base is True:
726
880
            base_marker = '|' * 7
787
941
 
788
942
    def merge_executable(self, file_id, file_status):
789
943
        """Perform a merge on the execute bit."""
 
944
        executable = [self.executable(t, file_id) for t in (self.base_tree,
 
945
                      self.other_tree, self.this_tree)]
 
946
        self._merge_executable(file_id, executable, file_status)
 
947
 
 
948
    def _merge_executable(self, file_id, executable, file_status):
 
949
        """Perform a merge on the execute bit."""
 
950
        base_executable, other_executable, this_executable = executable
790
951
        if file_status == "deleted":
791
952
            return
792
 
        trans_id = self.tt.trans_id_file_id(file_id)
793
 
        try:
794
 
            if self.tt.final_kind(trans_id) != "file":
795
 
                return
796
 
        except NoSuchFile:
797
 
            return
798
 
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
799
 
                                       self.other_tree, file_id, 
800
 
                                       self.executable)
 
953
        winner = self._three_way(*executable)
801
954
        if winner == "conflict":
802
955
        # There must be a None in here, if we have a conflict, but we
803
956
        # need executability since file status was not deleted.
805
958
                winner = "this"
806
959
            else:
807
960
                winner = "other"
 
961
        if winner == 'this' and file_status != "modified":
 
962
            return
 
963
        trans_id = self.tt.trans_id_file_id(file_id)
 
964
        try:
 
965
            if self.tt.final_kind(trans_id) != "file":
 
966
                return
 
967
        except NoSuchFile:
 
968
            return
808
969
        if winner == "this":
809
 
            if file_status == "modified":
810
 
                executability = self.this_tree.is_executable(file_id)
811
 
                if executability is not None:
812
 
                    trans_id = self.tt.trans_id_file_id(file_id)
813
 
                    self.tt.set_executability(executability, trans_id)
 
970
            executability = this_executable
814
971
        else:
815
972
            assert winner == "other"
816
973
            if file_id in self.other_tree:
817
 
                executability = self.other_tree.is_executable(file_id)
 
974
                executability = other_executable
818
975
            elif file_id in self.this_tree:
819
 
                executability = self.this_tree.is_executable(file_id)
 
976
                executability = this_executable
820
977
            elif file_id in self.base_tree:
821
 
                executability = self.base_tree.is_executable(file_id)
822
 
            if executability is not None:
823
 
                trans_id = self.tt.trans_id_file_id(file_id)
824
 
                self.tt.set_executability(executability, trans_id)
 
978
                executability = base_executable
 
979
        if executability is not None:
 
980
            trans_id = self.tt.trans_id_file_id(file_id)
 
981
            self.tt.set_executability(executability, trans_id)
825
982
 
826
983
    def cook_conflicts(self, fs_conflicts):
827
984
        """Convert all conflicts into a form that doesn't depend on trans_id"""
870
1027
            except KeyError:
871
1028
                this_name = other_name = self.tt.final_name(trans_id)
872
1029
            other_path = fp.get_path(trans_id)
873
 
            if this_parent is not None:
 
1030
            if this_parent is not None and this_name is not None:
874
1031
                this_parent_path = \
875
1032
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
876
1033
                this_path = pathjoin(this_parent_path, this_name)
887
1044
    """Three-way tree merger, text weave merger."""
888
1045
    supports_reprocess = True
889
1046
    supports_show_base = False
890
 
 
891
 
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
892
 
                 interesting_ids=None, pb=DummyProgress(), pp=None,
893
 
                 reprocess=False, change_reporter=None):
894
 
        self.this_revision_tree = self._get_revision_tree(this_tree)
895
 
        self.other_revision_tree = self._get_revision_tree(other_tree)
896
 
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
897
 
                                          base_tree, other_tree, 
898
 
                                          interesting_ids=interesting_ids, 
899
 
                                          pb=pb, pp=pp, reprocess=reprocess,
900
 
                                          change_reporter=change_reporter)
901
 
 
902
 
    def _get_revision_tree(self, tree):
903
 
        """Return a revision tree related to this tree.
904
 
        If the tree is a WorkingTree, the basis will be returned.
905
 
        """
906
 
        if getattr(tree, 'get_weave', False) is False:
907
 
            # If we have a WorkingTree, try using the basis
908
 
            return tree.branch.basis_tree()
909
 
        else:
910
 
            return tree
911
 
 
912
 
    def _check_file(self, file_id):
913
 
        """Check that the revision tree's version of the file matches."""
914
 
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
915
 
                         (self.other_tree, self.other_revision_tree)):
916
 
            if rt is tree:
917
 
                continue
918
 
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
919
 
                raise WorkingTreeNotRevision(self.this_tree)
 
1047
    supports_reverse_cherrypick = False
 
1048
    history_based = True
920
1049
 
921
1050
    def _merged_lines(self, file_id):
922
1051
        """Generate the merged lines.
923
1052
        There is no distinction between lines that are meant to contain <<<<<<<
924
1053
        and conflicts.
925
1054
        """
926
 
        weave = self.this_revision_tree.get_weave(file_id)
927
 
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
928
 
        other_revision_id = \
929
 
            self.other_revision_tree.inventory[file_id].revision
930
 
        wm = WeaveMerge(weave, this_revision_id, other_revision_id, 
931
 
                        '<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
932
 
        return wm.merge_lines(self.reprocess)
 
1055
        if self.cherrypick:
 
1056
            base = self.base_tree
 
1057
        else:
 
1058
            base = None
 
1059
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
 
1060
                                              base=base)
 
1061
        if 'merge' in debug.debug_flags:
 
1062
            plan = list(plan)
 
1063
            trans_id = self.tt.trans_id_file_id(file_id)
 
1064
            name = self.tt.final_name(trans_id) + '.plan'
 
1065
            contents = ('%10s|%s' % l for l in plan)
 
1066
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
 
1067
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
1068
            '>>>>>>> MERGE-SOURCE\n')
 
1069
        return textmerge.merge_lines(self.reprocess)
933
1070
 
934
1071
    def text_merge(self, file_id, trans_id):
935
1072
        """Perform a (weave) text merge for a given file and file-id.
936
1073
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
937
1074
        and a conflict will be noted.
938
1075
        """
939
 
        self._check_file(file_id)
940
1076
        lines, conflicts = self._merged_lines(file_id)
941
1077
        lines = list(lines)
942
1078
        # Note we're checking whether the OUTPUT is binary in this case, 
952
1088
            file_group.append(trans_id)
953
1089
 
954
1090
 
 
1091
class LCAMerger(WeaveMerger):
 
1092
 
 
1093
    def _merged_lines(self, file_id):
 
1094
        """Generate the merged lines.
 
1095
        There is no distinction between lines that are meant to contain <<<<<<<
 
1096
        and conflicts.
 
1097
        """
 
1098
        if self.cherrypick:
 
1099
            base = self.base_tree
 
1100
        else:
 
1101
            base = None
 
1102
        plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
 
1103
                                                  base=base)
 
1104
        if 'merge' in debug.debug_flags:
 
1105
            plan = list(plan)
 
1106
            trans_id = self.tt.trans_id_file_id(file_id)
 
1107
            name = self.tt.final_name(trans_id) + '.plan'
 
1108
            contents = ('%10s|%s' % l for l in plan)
 
1109
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
 
1110
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
1111
            '>>>>>>> MERGE-SOURCE\n')
 
1112
        return textmerge.merge_lines(self.reprocess)
 
1113
 
 
1114
 
955
1115
class Diff3Merger(Merge3Merger):
956
1116
    """Three-way merger using external diff3 for text merging"""
957
1117
 
1024
1184
    if interesting_files:
1025
1185
        assert not interesting_ids, ('Only supply interesting_ids'
1026
1186
                                     ' or interesting_files')
1027
 
        merger._set_interesting_files(interesting_files)
 
1187
        merger.interesting_files = interesting_files
1028
1188
    merger.show_base = show_base
1029
1189
    merger.reprocess = reprocess
1030
1190
    merger.other_rev_id = other_rev_id
1031
1191
    merger.other_basis = other_rev_id
 
1192
    get_revision_id = getattr(base_tree, 'get_revision_id', None)
 
1193
    if get_revision_id is None:
 
1194
        get_revision_id = base_tree.last_revision
 
1195
    merger.set_base_revision(get_revision_id(), this_branch)
1032
1196
    return merger.do_merge()
1033
1197
 
1034
1198
def get_merge_type_registry():
1038
1202
    """
1039
1203
    from bzrlib import option
1040
1204
    return option._merge_type_registry
 
1205
 
 
1206
 
 
1207
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
 
1208
    def status_a(revision, text):
 
1209
        if revision in ancestors_b:
 
1210
            return 'killed-b', text
 
1211
        else:
 
1212
            return 'new-a', text
 
1213
 
 
1214
    def status_b(revision, text):
 
1215
        if revision in ancestors_a:
 
1216
            return 'killed-a', text
 
1217
        else:
 
1218
            return 'new-b', text
 
1219
 
 
1220
    plain_a = [t for (a, t) in annotated_a]
 
1221
    plain_b = [t for (a, t) in annotated_b]
 
1222
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
 
1223
    blocks = matcher.get_matching_blocks()
 
1224
    a_cur = 0
 
1225
    b_cur = 0
 
1226
    for ai, bi, l in blocks:
 
1227
        # process all mismatched sections
 
1228
        # (last mismatched section is handled because blocks always
 
1229
        # includes a 0-length last block)
 
1230
        for revision, text in annotated_a[a_cur:ai]:
 
1231
            yield status_a(revision, text)
 
1232
        for revision, text in annotated_b[b_cur:bi]:
 
1233
            yield status_b(revision, text)
 
1234
 
 
1235
        # and now the matched section
 
1236
        a_cur = ai + l
 
1237
        b_cur = bi + l
 
1238
        for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
 
1239
            assert text_a == text_b
 
1240
            yield "unchanged", text_a
 
1241
 
 
1242
 
 
1243
class _PlanMergeBase(object):
 
1244
 
 
1245
    def __init__(self, a_rev, b_rev, vf):
 
1246
        """Contructor.
 
1247
 
 
1248
        :param a_rev: Revision-id of one revision to merge
 
1249
        :param b_rev: Revision-id of the other revision to merge
 
1250
        :param vf: A versionedfile containing both revisions
 
1251
        """
 
1252
        self.a_rev = a_rev
 
1253
        self.b_rev = b_rev
 
1254
        self.lines_a = vf.get_lines(a_rev)
 
1255
        self.lines_b = vf.get_lines(b_rev)
 
1256
        self.vf = vf
 
1257
        self._last_lines = None
 
1258
        self._last_lines_revision_id = None
 
1259
        self._cached_matching_blocks = {}
 
1260
 
 
1261
    def plan_merge(self):
 
1262
        """Generate a 'plan' for merging the two revisions.
 
1263
 
 
1264
        This involves comparing their texts and determining the cause of
 
1265
        differences.  If text A has a line and text B does not, then either the
 
1266
        line was added to text A, or it was deleted from B.  Once the causes
 
1267
        are combined, they are written out in the format described in
 
1268
        VersionedFile.plan_merge
 
1269
        """
 
1270
        blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
 
1271
        unique_a, unique_b = self._unique_lines(blocks)
 
1272
        new_a, killed_b = self._determine_status(self.a_rev, unique_a)
 
1273
        new_b, killed_a = self._determine_status(self.b_rev, unique_b)
 
1274
        return self._iter_plan(blocks, new_a, killed_b, new_b, killed_a)
 
1275
 
 
1276
    def _iter_plan(self, blocks, new_a, killed_b, new_b, killed_a):
 
1277
        last_i = 0
 
1278
        last_j = 0
 
1279
        for i, j, n in blocks:
 
1280
            for a_index in range(last_i, i):
 
1281
                if a_index in new_a:
 
1282
                    if a_index in killed_b:
 
1283
                        yield 'conflicted-a', self.lines_a[a_index]
 
1284
                    else:
 
1285
                        yield 'new-a', self.lines_a[a_index]
 
1286
                else:
 
1287
                    yield 'killed-b', self.lines_a[a_index]
 
1288
            for b_index in range(last_j, j):
 
1289
                if b_index in new_b:
 
1290
                    if b_index in killed_a:
 
1291
                        yield 'conflicted-b', self.lines_b[b_index]
 
1292
                    else:
 
1293
                        yield 'new-b', self.lines_b[b_index]
 
1294
                else:
 
1295
                    yield 'killed-a', self.lines_b[b_index]
 
1296
            # handle common lines
 
1297
            for a_index in range(i, i+n):
 
1298
                yield 'unchanged', self.lines_a[a_index]
 
1299
            last_i = i+n
 
1300
            last_j = j+n
 
1301
 
 
1302
    def _get_matching_blocks(self, left_revision, right_revision):
 
1303
        """Return a description of which sections of two revisions match.
 
1304
 
 
1305
        See SequenceMatcher.get_matching_blocks
 
1306
        """
 
1307
        cached = self._cached_matching_blocks.get((left_revision,
 
1308
                                                   right_revision))
 
1309
        if cached is not None:
 
1310
            return cached
 
1311
        if self._last_lines_revision_id == left_revision:
 
1312
            left_lines = self._last_lines
 
1313
        else:
 
1314
            left_lines = self.vf.get_lines(left_revision)
 
1315
        right_lines = self.vf.get_lines(right_revision)
 
1316
        self._last_lines = right_lines
 
1317
        self._last_lines_revision_id = right_revision
 
1318
        matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
 
1319
                                                       right_lines)
 
1320
        return matcher.get_matching_blocks()
 
1321
 
 
1322
    def _unique_lines(self, matching_blocks):
 
1323
        """Analyse matching_blocks to determine which lines are unique
 
1324
 
 
1325
        :return: a tuple of (unique_left, unique_right), where the values are
 
1326
            sets of line numbers of unique lines.
 
1327
        """
 
1328
        last_i = 0
 
1329
        last_j = 0
 
1330
        unique_left = []
 
1331
        unique_right = []
 
1332
        for i, j, n in matching_blocks:
 
1333
            unique_left.extend(range(last_i, i))
 
1334
            unique_right.extend(range(last_j, j))
 
1335
            last_i = i + n
 
1336
            last_j = j + n
 
1337
        return unique_left, unique_right
 
1338
 
 
1339
    @staticmethod
 
1340
    def _subtract_plans(old_plan, new_plan):
 
1341
        """Remove changes from new_plan that came from old_plan.
 
1342
 
 
1343
        It is assumed that the difference between the old_plan and new_plan
 
1344
        is their choice of 'b' text.
 
1345
 
 
1346
        All lines from new_plan that differ from old_plan are emitted
 
1347
        verbatim.  All lines from new_plan that match old_plan but are
 
1348
        not about the 'b' revision are emitted verbatim.
 
1349
 
 
1350
        Lines that match and are about the 'b' revision are the lines we
 
1351
        don't want, so we convert 'killed-b' -> 'unchanged', and 'new-b'
 
1352
        is skipped entirely.
 
1353
        """
 
1354
        matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
 
1355
                                                       new_plan)
 
1356
        last_j = 0
 
1357
        for i, j, n in matcher.get_matching_blocks():
 
1358
            for jj in range(last_j, j):
 
1359
                yield new_plan[jj]
 
1360
            for jj in range(j, j+n):
 
1361
                plan_line = new_plan[jj]
 
1362
                if plan_line[0] == 'new-b':
 
1363
                    pass
 
1364
                elif plan_line[0] == 'killed-b':
 
1365
                    yield 'unchanged', plan_line[1]
 
1366
                else:
 
1367
                    yield plan_line
 
1368
            last_j = j + n
 
1369
 
 
1370
 
 
1371
class _PlanMerge(_PlanMergeBase):
 
1372
    """Plan an annotate merge using on-the-fly annotation"""
 
1373
 
 
1374
    def __init__(self, a_rev, b_rev, vf):
 
1375
       _PlanMergeBase.__init__(self, a_rev, b_rev, vf)
 
1376
       a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
 
1377
       b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
 
1378
       self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
 
1379
 
 
1380
    def _determine_status(self, revision_id, unique_line_numbers):
 
1381
        """Determines the status unique lines versus all lcas.
 
1382
 
 
1383
        Basically, determines why the line is unique to this revision.
 
1384
 
 
1385
        A line may be determined new or killed, but not both.
 
1386
 
 
1387
        :param revision_id: The id of the revision in which the lines are
 
1388
            unique
 
1389
        :param unique_line_numbers: The line numbers of unique lines.
 
1390
        :return a tuple of (new_this, killed_other):
 
1391
        """
 
1392
        new = self._find_new(revision_id)
 
1393
        killed = set(unique_line_numbers).difference(new)
 
1394
        return new, killed
 
1395
 
 
1396
    def _find_new(self, version_id):
 
1397
        """Determine which lines are new in the ancestry of this version.
 
1398
 
 
1399
        If a lines is present in this version, and not present in any
 
1400
        common ancestor, it is considered new.
 
1401
        """
 
1402
        if version_id not in self.uncommon:
 
1403
            return set()
 
1404
        parents = self.vf.get_parent_map([version_id])[version_id]
 
1405
        if len(parents) == 0:
 
1406
            return set(range(len(self.vf.get_lines(version_id))))
 
1407
        new = None
 
1408
        for parent in parents:
 
1409
            blocks = self._get_matching_blocks(version_id, parent)
 
1410
            result, unused = self._unique_lines(blocks)
 
1411
            parent_new = self._find_new(parent)
 
1412
            for i, j, n in blocks:
 
1413
                for ii, jj in [(i+r, j+r) for r in range(n)]:
 
1414
                    if jj in parent_new:
 
1415
                        result.append(ii)
 
1416
            if new is None:
 
1417
                new = set(result)
 
1418
            else:
 
1419
                new.intersection_update(result)
 
1420
        return new
 
1421
 
 
1422
 
 
1423
class _PlanLCAMerge(_PlanMergeBase):
 
1424
    """
 
1425
    This merge algorithm differs from _PlanMerge in that:
 
1426
    1. comparisons are done against LCAs only
 
1427
    2. cases where a contested line is new versus one LCA but old versus
 
1428
       another are marked as conflicts, by emitting the line as conflicted-a
 
1429
       or conflicted-b.
 
1430
 
 
1431
    This is faster, and hopefully produces more useful output.
 
1432
    """
 
1433
 
 
1434
    def __init__(self, a_rev, b_rev, vf, graph):
 
1435
        _PlanMergeBase.__init__(self, a_rev, b_rev, vf)
 
1436
        self.lcas = graph.find_lca(a_rev, b_rev)
 
1437
        for lca in self.lcas:
 
1438
            lca_lines = self.vf.get_lines(lca)
 
1439
            matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_a,
 
1440
                                                           lca_lines)
 
1441
            blocks = list(matcher.get_matching_blocks())
 
1442
            self._cached_matching_blocks[(a_rev, lca)] = blocks
 
1443
            matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_b,
 
1444
                                                           lca_lines)
 
1445
            blocks = list(matcher.get_matching_blocks())
 
1446
            self._cached_matching_blocks[(b_rev, lca)] = blocks
 
1447
 
 
1448
    def _determine_status(self, revision_id, unique_line_numbers):
 
1449
        """Determines the status unique lines versus all lcas.
 
1450
 
 
1451
        Basically, determines why the line is unique to this revision.
 
1452
 
 
1453
        A line may be determined new, killed, or both.
 
1454
 
 
1455
        If a line is determined new, that means it was not present in at least
 
1456
        one LCA, and is not present in the other merge revision.
 
1457
 
 
1458
        If a line is determined killed, that means the line was present in
 
1459
        at least one LCA.
 
1460
 
 
1461
        If a line is killed and new, this indicates that the two merge
 
1462
        revisions contain differing conflict resolutions.
 
1463
        :param revision_id: The id of the revision in which the lines are
 
1464
            unique
 
1465
        :param unique_line_numbers: The line numbers of unique lines.
 
1466
        :return a tuple of (new_this, killed_other):
 
1467
        """
 
1468
        new = set()
 
1469
        killed = set()
 
1470
        unique_line_numbers = set(unique_line_numbers)
 
1471
        for lca in self.lcas:
 
1472
            blocks = self._get_matching_blocks(revision_id, lca)
 
1473
            unique_vs_lca, _ignored = self._unique_lines(blocks)
 
1474
            new.update(unique_line_numbers.intersection(unique_vs_lca))
 
1475
            killed.update(unique_line_numbers.difference(unique_vs_lca))
 
1476
        return new, killed