~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-10-24 12:49:17 UTC
  • mfrom: (2935.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20071024124917-xb75eckyxx6vkrlg
Makefile fixes - hooks.html generation & allow python to be overridden (Ian Clatworthy)

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
import os
19
19
import errno
20
 
from tempfile import mkdtemp
21
20
import warnings
22
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    osutils,
 
25
    patiencediff,
 
26
    registry,
 
27
    revision as _mod_revision,
 
28
    )
23
29
from bzrlib.branch import Branch
24
30
from bzrlib.conflicts import ConflictList, Conflict
25
31
from bzrlib.errors import (BzrCommandError,
36
42
                           BinaryFile,
37
43
                           )
38
44
from bzrlib.merge3 import Merge3
39
 
import bzrlib.osutils
40
 
from bzrlib.osutils import rename, pathjoin, rmtree
 
45
from bzrlib.osutils import rename, pathjoin
41
46
from progress import DummyProgress, ProgressPhase
42
 
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
 
47
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
43
48
from bzrlib.textfile import check_text_lines
44
49
from bzrlib.trace import mutter, warning, note
45
50
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
46
 
                              FinalPaths, create_by_entry, unique_add)
47
 
from bzrlib.versionedfile import WeaveMerge
 
51
                              conflict_pass, FinalPaths, create_by_entry,
 
52
                              unique_add, ROOT_PARENT)
 
53
from bzrlib.versionedfile import PlanWeaveMerge
48
54
from bzrlib import ui
49
55
 
50
56
# TODO: Report back as changes are merged in
51
57
 
52
 
def _get_tree(treespec, local_branch=None):
53
 
    from bzrlib import workingtree
54
 
    location, revno = treespec
55
 
    if revno is None:
56
 
        tree = workingtree.WorkingTree.open_containing(location)[0]
57
 
        return tree.branch, tree
58
 
    branch = Branch.open_containing(location)[0]
59
 
    if revno == -1:
60
 
        revision = branch.last_revision()
61
 
    else:
62
 
        revision = branch.get_rev_id(revno)
63
 
        if revision is None:
64
 
            revision = NULL_REVISION
65
 
    return branch, _get_revid_tree(branch, revision, local_branch)
66
 
 
67
 
 
68
 
def _get_revid_tree(branch, revision, local_branch):
69
 
    if revision is None:
70
 
        base_tree = branch.bzrdir.open_workingtree()
71
 
    else:
72
 
        if local_branch is not None:
73
 
            if local_branch.base != branch.base:
74
 
                local_branch.fetch(branch, revision)
75
 
            base_tree = local_branch.repository.revision_tree(revision)
76
 
        else:
77
 
            base_tree = branch.repository.revision_tree(revision)
78
 
    return base_tree
79
 
 
80
58
 
81
59
def transform_tree(from_tree, to_tree, interesting_ids=None):
82
60
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
84
62
 
85
63
 
86
64
class Merger(object):
87
 
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
88
 
                 this_tree=None, pb=DummyProgress()):
 
65
    def __init__(self, this_branch, other_tree=None, base_tree=None,
 
66
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
 
67
                 recurse='down'):
89
68
        object.__init__(self)
90
69
        assert this_tree is not None, "this_tree is required"
91
70
        self.this_branch = this_branch
92
 
        self.this_basis = this_branch.last_revision()
 
71
        self.this_basis = _mod_revision.ensure_null(
 
72
            this_branch.last_revision())
93
73
        self.this_rev_id = None
94
74
        self.this_tree = this_tree
95
75
        self.this_revision_tree = None
96
76
        self.this_basis_tree = None
97
77
        self.other_tree = other_tree
 
78
        self.other_branch = None
98
79
        self.base_tree = base_tree
99
80
        self.ignore_zero = False
100
81
        self.backup_files = False
101
82
        self.interesting_ids = None
 
83
        self.interesting_files = None
102
84
        self.show_base = False
103
85
        self.reprocess = False
104
 
        self._pb = pb 
 
86
        self._pb = pb
105
87
        self.pp = None
106
 
 
107
 
 
108
 
    def revision_tree(self, revision_id):
109
 
        return self.this_branch.repository.revision_tree(revision_id)
 
88
        self.recurse = recurse
 
89
        self.change_reporter = change_reporter
 
90
        self._cached_trees = {}
 
91
 
 
92
    @staticmethod
 
93
    def from_uncommitted(tree, other_tree, pb):
 
94
        """Return a Merger for uncommitted changes in other_tree.
 
95
 
 
96
        :param tree: The tree to merge into
 
97
        :param other_tree: The tree to get uncommitted changes from
 
98
        :param pb: A progress indicator
 
99
        """
 
100
        merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
 
101
                        pb)
 
102
        merger.base_rev_id = merger.base_tree.get_revision_id()
 
103
        merger.other_rev_id = None
 
104
        return merger
 
105
 
 
106
    @classmethod
 
107
    def from_mergeable(klass, tree, mergeable, pb):
 
108
        """Return a Merger for a bundle or merge directive.
 
109
 
 
110
        :param tree: The tree to merge changes into
 
111
        :param mergeable: A merge directive or bundle
 
112
        :param pb: A progress indicator
 
113
        """
 
114
        mergeable.install_revisions(tree.branch.repository)
 
115
        base_revision_id, other_revision_id, verified =\
 
116
            mergeable.get_merge_request(tree.branch.repository)
 
117
        if (base_revision_id != _mod_revision.NULL_REVISION and
 
118
            tree.branch.repository.get_graph().is_ancestor(
 
119
            base_revision_id, tree.branch.last_revision())):
 
120
            base_revision_id = None
 
121
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
 
122
                                         base_revision_id)
 
123
        return merger, verified
 
124
 
 
125
    @staticmethod
 
126
    def from_revision_ids(pb, this, other, base=None, other_branch=None,
 
127
                          base_branch=None):
 
128
        """Return a Merger for revision-ids.
 
129
 
 
130
        :param tree: The tree to merge changes into
 
131
        :param other: The revision-id to use as OTHER
 
132
        :param base: The revision-id to use as BASE.  If not specified, will
 
133
            be auto-selected.
 
134
        :param other_branch: A branch containing the other revision-id.  If
 
135
            not supplied, this.branch is used.
 
136
        :param base_branch: A branch containing the base revision-id.  If
 
137
            not supplied, other_branch or this.branch will be used.
 
138
        :param pb: A progress indicator
 
139
        """
 
140
        merger = Merger(this.branch, this_tree=this, pb=pb)
 
141
        if other_branch is None:
 
142
            other_branch = this.branch
 
143
        merger.set_other_revision(other, other_branch)
 
144
        if base is None:
 
145
            merger.find_base()
 
146
        else:
 
147
            if base_branch is None:
 
148
                base_branch = other_branch
 
149
            merger.set_base_revision(base, base_branch)
 
150
        return merger
 
151
 
 
152
    def revision_tree(self, revision_id, branch=None):
 
153
        if revision_id not in self._cached_trees:
 
154
            if branch is None:
 
155
                branch = self.this_branch
 
156
            try:
 
157
                tree = self.this_tree.revision_tree(revision_id)
 
158
            except errors.NoSuchRevisionInTree:
 
159
                tree = branch.repository.revision_tree(revision_id)
 
160
            self._cached_trees[revision_id] = tree
 
161
        return self._cached_trees[revision_id]
 
162
 
 
163
    def _get_tree(self, treespec, possible_transports=None):
 
164
        from bzrlib import workingtree
 
165
        location, revno = treespec
 
166
        if revno is None:
 
167
            tree = workingtree.WorkingTree.open_containing(location)[0]
 
168
            return tree.branch, tree
 
169
        branch = Branch.open_containing(location, possible_transports)[0]
 
170
        if revno == -1:
 
171
            revision_id = branch.last_revision()
 
172
        else:
 
173
            revision_id = branch.get_rev_id(revno)
 
174
        revision_id = ensure_null(revision_id)
 
175
        return branch, self.revision_tree(revision_id, branch)
110
176
 
111
177
    def ensure_revision_trees(self):
112
178
        if self.this_revision_tree is None:
113
 
            self.this_basis_tree = self.this_branch.repository.revision_tree(
114
 
                self.this_basis)
 
179
            self.this_basis_tree = self.revision_tree(self.this_basis)
115
180
            if self.this_basis == self.this_rev_id:
116
181
                self.this_revision_tree = self.this_basis_tree
117
182
 
139
204
 
140
205
    def check_basis(self, check_clean, require_commits=True):
141
206
        if self.this_basis is None and require_commits is True:
142
 
            raise BzrCommandError("This branch has no commits")
 
207
            raise BzrCommandError("This branch has no commits."
 
208
                                  " (perhaps you would prefer 'bzr pull')")
143
209
        if check_clean:
144
210
            self.compare_basis()
145
211
            if self.this_basis != self.this_rev_id:
146
 
                raise BzrCommandError("Working tree has uncommitted changes.")
 
212
                raise errors.UncommittedChanges(self.this_tree)
147
213
 
148
214
    def compare_basis(self):
149
 
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
 
215
        try:
 
216
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
217
        except errors.RevisionNotPresent:
 
218
            basis_tree = self.this_tree.basis_tree()
 
219
        changes = self.this_tree.changes_from(basis_tree)
150
220
        if not changes.has_changed():
151
221
            self.this_rev_id = self.this_basis
152
222
 
153
223
    def set_interesting_files(self, file_list):
154
 
        try:
155
 
            self._set_interesting_files(file_list)
156
 
        except NotVersionedError, e:
157
 
            raise BzrCommandError("%s is not a source file in any"
158
 
                                      " tree." % e.path)
159
 
 
160
 
    def _set_interesting_files(self, file_list):
161
 
        """Set the list of interesting ids from a list of files."""
162
 
        if file_list is None:
163
 
            self.interesting_ids = None
164
 
            return
165
 
 
166
 
        interesting_ids = set()
167
 
        for path in file_list:
168
 
            found_id = False
169
 
            for tree in (self.this_tree, self.base_tree, self.other_tree):
170
 
                file_id = tree.inventory.path2id(path)
171
 
                if file_id is not None:
172
 
                    interesting_ids.add(file_id)
173
 
                    found_id = True
174
 
            if not found_id:
175
 
                raise NotVersionedError(path=path)
176
 
        self.interesting_ids = interesting_ids
 
224
        self.interesting_files = file_list
177
225
 
178
226
    def set_pending(self):
179
 
        if not self.base_is_ancestor:
180
 
            return
181
 
        if self.other_rev_id is None:
182
 
            return
183
 
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
184
 
        if self.other_rev_id in ancestry:
185
 
            return
186
 
        self.this_tree.add_pending_merge(self.other_rev_id)
187
 
 
188
 
    def set_other(self, other_revision):
189
 
        other_branch, self.other_tree = _get_tree(other_revision, 
190
 
                                                  self.this_branch)
 
227
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
 
228
            return
 
229
        self._add_parent()
 
230
 
 
231
    def _add_parent(self):
 
232
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
 
233
        new_parent_trees = []
 
234
        for revision_id in new_parents:
 
235
            try:
 
236
                tree = self.revision_tree(revision_id)
 
237
            except errors.RevisionNotPresent:
 
238
                tree = None
 
239
            else:
 
240
                tree.lock_read()
 
241
            new_parent_trees.append((revision_id, tree))
 
242
        try:
 
243
            self.this_tree.set_parent_trees(new_parent_trees,
 
244
                                            allow_leftmost_as_ghost=True)
 
245
        finally:
 
246
            for _revision_id, tree in new_parent_trees:
 
247
                if tree is not None:
 
248
                    tree.unlock()
 
249
 
 
250
    def set_other(self, other_revision, possible_transports=None):
 
251
        """Set the revision and tree to merge from.
 
252
 
 
253
        This sets the other_tree, other_rev_id, other_basis attributes.
 
254
 
 
255
        :param other_revision: The [path, revision] list to merge from.
 
256
        """
 
257
        self.other_branch, self.other_tree = self._get_tree(other_revision,
 
258
                                                            possible_transports)
191
259
        if other_revision[1] == -1:
192
 
            self.other_rev_id = other_branch.last_revision()
193
 
            if self.other_rev_id is None:
194
 
                raise NoCommits(other_branch)
 
260
            self.other_rev_id = _mod_revision.ensure_null(
 
261
                self.other_branch.last_revision())
 
262
            if _mod_revision.is_null(self.other_rev_id):
 
263
                raise NoCommits(self.other_branch)
195
264
            self.other_basis = self.other_rev_id
196
265
        elif other_revision[1] is not None:
197
 
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
266
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
198
267
            self.other_basis = self.other_rev_id
199
268
        else:
200
269
            self.other_rev_id = None
201
 
            self.other_basis = other_branch.last_revision()
 
270
            self.other_basis = self.other_branch.last_revision()
202
271
            if self.other_basis is None:
203
 
                raise NoCommits(other_branch)
204
 
        if other_branch.base != self.this_branch.base:
205
 
            self.this_branch.fetch(other_branch, last_revision=self.other_basis)
 
272
                raise NoCommits(self.other_branch)
 
273
        if self.other_rev_id is not None:
 
274
            self._cached_trees[self.other_rev_id] = self.other_tree
 
275
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
 
276
 
 
277
    def set_other_revision(self, revision_id, other_branch):
 
278
        """Set 'other' based on a branch and revision id
 
279
 
 
280
        :param revision_id: The revision to use for a tree
 
281
        :param other_branch: The branch containing this tree
 
282
        """
 
283
        self.other_rev_id = revision_id
 
284
        self.other_branch = other_branch
 
285
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
 
286
        self.other_tree = self.revision_tree(revision_id)
 
287
        self.other_basis = revision_id
 
288
 
 
289
    def set_base_revision(self, revision_id, branch):
 
290
        """Set 'base' based on a branch and revision id
 
291
 
 
292
        :param revision_id: The revision to use for a tree
 
293
        :param branch: The branch containing this tree
 
294
        """
 
295
        self.base_rev_id = revision_id
 
296
        self.base_branch = branch
 
297
        self._maybe_fetch(branch, self.this_branch, revision_id)
 
298
        self.base_tree = self.revision_tree(revision_id)
 
299
        self.base_is_ancestor = is_ancestor(self.this_basis,
 
300
                                            self.base_rev_id,
 
301
                                            self.this_branch)
 
302
        self.base_is_other_ancestor = is_ancestor(self.other_basis,
 
303
                                                  self.base_rev_id,
 
304
                                                  self.this_branch)
 
305
 
 
306
    def _maybe_fetch(self, source, target, revision_id):
 
307
        if not source.repository.has_same_location(target.repository):
 
308
            target.fetch(source, revision_id)
206
309
 
207
310
    def find_base(self):
208
 
        self.set_base([None, None])
 
311
        this_repo = self.this_branch.repository
 
312
        graph = this_repo.get_graph()
 
313
        revisions = [ensure_null(self.this_basis),
 
314
                     ensure_null(self.other_basis)]
 
315
        if NULL_REVISION in revisions:
 
316
            self.base_rev_id = NULL_REVISION
 
317
        else:
 
318
            self.base_rev_id = graph.find_unique_lca(*revisions)
 
319
            if self.base_rev_id == NULL_REVISION:
 
320
                raise UnrelatedBranches()
 
321
        self.base_tree = self.revision_tree(self.base_rev_id)
 
322
        self.base_is_ancestor = True
 
323
        self.base_is_other_ancestor = True
209
324
 
210
325
    def set_base(self, base_revision):
 
326
        """Set the base revision to use for the merge.
 
327
 
 
328
        :param base_revision: A 2-list containing a path and revision number.
 
329
        """
211
330
        mutter("doing merge() with no base_revision specified")
212
331
        if base_revision == [None, None]:
213
 
            try:
214
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
215
 
                try:
216
 
                    this_repo = self.this_branch.repository
217
 
                    self.base_rev_id = common_ancestor(self.this_basis, 
218
 
                                                       self.other_basis, 
219
 
                                                       this_repo, pb)
220
 
                finally:
221
 
                    pb.finished()
222
 
            except NoCommonAncestor:
223
 
                raise UnrelatedBranches()
224
 
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
225
 
                                            None)
226
 
            self.base_is_ancestor = True
 
332
            self.find_base()
227
333
        else:
228
 
            base_branch, self.base_tree = _get_tree(base_revision)
 
334
            base_branch, self.base_tree = self._get_tree(base_revision)
229
335
            if base_revision[1] == -1:
230
336
                self.base_rev_id = base_branch.last_revision()
231
337
            elif base_revision[1] is None:
232
 
                self.base_rev_id = None
 
338
                self.base_rev_id = _mod_revision.NULL_REVISION
233
339
            else:
234
 
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
235
 
            if self.this_branch.base != base_branch.base:
236
 
                self.this_branch.fetch(base_branch)
 
340
                self.base_rev_id = _mod_revision.ensure_null(
 
341
                    base_branch.get_rev_id(base_revision[1]))
 
342
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
237
343
            self.base_is_ancestor = is_ancestor(self.this_basis, 
238
344
                                                self.base_rev_id,
239
345
                                                self.this_branch)
 
346
            self.base_is_other_ancestor = is_ancestor(self.other_basis,
 
347
                                                      self.base_rev_id,
 
348
                                                      self.this_branch)
240
349
 
241
350
    def do_merge(self):
242
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
243
 
                  'other_tree': self.other_tree, 
 
351
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
352
                  'other_tree': self.other_tree,
244
353
                  'interesting_ids': self.interesting_ids,
 
354
                  'interesting_files': self.interesting_files,
245
355
                  'pp': self.pp}
246
356
        if self.merge_type.requires_base:
247
357
            kwargs['base_tree'] = self.base_tree
255
365
        elif self.show_base:
256
366
            raise BzrError("Showing base is not supported for this"
257
367
                                  " merge type. %s" % self.merge_type)
258
 
        merge = self.merge_type(pb=self._pb, **kwargs)
 
368
        self.this_tree.lock_tree_write()
 
369
        if self.base_tree is not None:
 
370
            self.base_tree.lock_read()
 
371
        if self.other_tree is not None:
 
372
            self.other_tree.lock_read()
 
373
        try:
 
374
            merge = self.merge_type(pb=self._pb,
 
375
                                    change_reporter=self.change_reporter,
 
376
                                    **kwargs)
 
377
            if self.recurse == 'down':
 
378
                for path, file_id in self.this_tree.iter_references():
 
379
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
380
                    other_revision = self.other_tree.get_reference_revision(
 
381
                        file_id, path)
 
382
                    if  other_revision == sub_tree.last_revision():
 
383
                        continue
 
384
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
385
                    sub_merge.merge_type = self.merge_type
 
386
                    relpath = self.this_tree.relpath(path)
 
387
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
 
388
                    sub_merge.set_other_revision(other_revision, other_branch)
 
389
                    base_revision = self.base_tree.get_reference_revision(file_id)
 
390
                    sub_merge.base_tree = \
 
391
                        sub_tree.branch.repository.revision_tree(base_revision)
 
392
                    sub_merge.do_merge()
 
393
 
 
394
        finally:
 
395
            if self.other_tree is not None:
 
396
                self.other_tree.unlock()
 
397
            if self.base_tree is not None:
 
398
                self.base_tree.unlock()
 
399
            self.this_tree.unlock()
259
400
        if len(merge.cooked_conflicts) == 0:
260
401
            if not self.ignore_zero:
261
402
                note("All changes applied successfully.")
264
405
 
265
406
        return len(merge.cooked_conflicts)
266
407
 
267
 
    def regen_inventory(self, new_entries):
268
 
        old_entries = self.this_tree.read_working_inventory()
269
 
        new_inventory = {}
270
 
        by_path = {}
271
 
        new_entries_map = {} 
272
 
        for path, file_id in new_entries:
273
 
            if path is None:
274
 
                continue
275
 
            new_entries_map[file_id] = path
276
 
 
277
 
        def id2path(file_id):
278
 
            path = new_entries_map.get(file_id)
279
 
            if path is not None:
280
 
                return path
281
 
            entry = old_entries[file_id]
282
 
            if entry.parent_id is None:
283
 
                return entry.name
284
 
            return pathjoin(id2path(entry.parent_id), entry.name)
285
 
            
286
 
        for file_id in old_entries:
287
 
            entry = old_entries[file_id]
288
 
            path = id2path(file_id)
289
 
            if file_id in self.base_tree.inventory:
290
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
291
 
            else:
292
 
                executable = getattr(entry, 'executable', False)
293
 
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
294
 
                                      entry.kind, executable)
295
 
                                      
296
 
            by_path[path] = file_id
297
 
        
298
 
        deletions = 0
299
 
        insertions = 0
300
 
        new_path_list = []
301
 
        for path, file_id in new_entries:
302
 
            if path is None:
303
 
                del new_inventory[file_id]
304
 
                deletions += 1
305
 
            else:
306
 
                new_path_list.append((path, file_id))
307
 
                if file_id not in old_entries:
308
 
                    insertions += 1
309
 
        # Ensure no file is added before its parent
310
 
        new_path_list.sort()
311
 
        for path, file_id in new_path_list:
312
 
            if path == '':
313
 
                parent = None
314
 
            else:
315
 
                parent = by_path[os.path.dirname(path)]
316
 
            abspath = pathjoin(self.this_tree.basedir, path)
317
 
            kind = bzrlib.osutils.file_kind(abspath)
318
 
            if file_id in self.base_tree.inventory:
319
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
320
 
            else:
321
 
                executable = False
322
 
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
323
 
            by_path[path] = file_id 
324
 
 
325
 
        # Get a list in insertion order
326
 
        new_inventory_list = new_inventory.values()
327
 
        mutter ("""Inventory regeneration:
328
 
    old length: %i insertions: %i deletions: %i new_length: %i"""\
329
 
            % (len(old_entries), insertions, deletions, 
330
 
               len(new_inventory_list)))
331
 
        assert len(new_inventory_list) == len(old_entries) + insertions\
332
 
            - deletions
333
 
        new_inventory_list.sort()
334
 
        return new_inventory_list
335
 
 
336
408
 
337
409
class Merge3Merger(object):
338
410
    """Three-way merger that uses the merge3 text merger"""
340
412
    supports_reprocess = True
341
413
    supports_show_base = True
342
414
    history_based = False
 
415
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
343
416
 
344
417
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
345
418
                 interesting_ids=None, reprocess=False, show_base=False,
346
 
                 pb=DummyProgress(), pp=None):
347
 
        """Initialize the merger object and perform the merge."""
 
419
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
420
                 interesting_files=None):
 
421
        """Initialize the merger object and perform the merge.
 
422
 
 
423
        :param working_tree: The working tree to apply the merge to
 
424
        :param this_tree: The local tree in the merge operation
 
425
        :param base_tree: The common tree in the merge operation
 
426
        :param other_tree: The other other tree to merge changes from
 
427
        :param interesting_ids: The file_ids of files that should be
 
428
            participate in the merge.  May not be combined with
 
429
            interesting_files.
 
430
        :param: reprocess If True, perform conflict-reduction processing.
 
431
        :param show_base: If True, show the base revision in text conflicts.
 
432
            (incompatible with reprocess)
 
433
        :param pb: A Progress bar
 
434
        :param pp: A ProgressPhase object
 
435
        :param change_reporter: An object that should report changes made
 
436
        :param interesting_files: The tree-relative paths of files that should
 
437
            participate in the merge.  If these paths refer to directories,
 
438
            the contents of those directories will also be included.  May not
 
439
            be combined with interesting_ids.  If neither interesting_files nor
 
440
            interesting_ids is specified, all files may participate in the
 
441
            merge.
 
442
        """
348
443
        object.__init__(self)
 
444
        if interesting_files is not None:
 
445
            assert interesting_ids is None
 
446
        self.interesting_ids = interesting_ids
 
447
        self.interesting_files = interesting_files
349
448
        self.this_tree = working_tree
 
449
        self.this_tree.lock_tree_write()
350
450
        self.base_tree = base_tree
 
451
        self.base_tree.lock_read()
351
452
        self.other_tree = other_tree
 
453
        self.other_tree.lock_read()
352
454
        self._raw_conflicts = []
353
455
        self.cooked_conflicts = []
354
456
        self.reprocess = reprocess
355
457
        self.show_base = show_base
356
458
        self.pb = pb
357
459
        self.pp = pp
 
460
        self.change_reporter = change_reporter
358
461
        if self.pp is None:
359
462
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
360
463
 
361
 
        if interesting_ids is not None:
362
 
            all_ids = interesting_ids
363
 
        else:
364
 
            all_ids = set(base_tree)
365
 
            all_ids.update(other_tree)
366
 
        working_tree.lock_write()
367
464
        self.tt = TreeTransform(working_tree, self.pb)
368
465
        try:
369
466
            self.pp.next_phase()
 
467
            entries = self._entries3()
370
468
            child_pb = ui.ui_factory.nested_progress_bar()
371
469
            try:
372
 
                for num, file_id in enumerate(all_ids):
373
 
                    child_pb.update('Preparing file merge', num, len(all_ids))
374
 
                    self.merge_names(file_id)
375
 
                    file_status = self.merge_contents(file_id)
376
 
                    self.merge_executable(file_id, file_status)
 
470
                for num, (file_id, changed, parents3, names3,
 
471
                          executable3) in enumerate(entries):
 
472
                    child_pb.update('Preparing file merge', num, len(entries))
 
473
                    self._merge_names(file_id, parents3, names3)
 
474
                    if changed:
 
475
                        file_status = self.merge_contents(file_id)
 
476
                    else:
 
477
                        file_status = 'unmodified'
 
478
                    self._merge_executable(file_id,
 
479
                        executable3, file_status)
377
480
            finally:
378
481
                child_pb.finished()
379
 
                
 
482
            self.fix_root()
380
483
            self.pp.next_phase()
381
484
            child_pb = ui.ui_factory.nested_progress_bar()
382
485
            try:
383
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
 
486
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
 
487
                    lambda t, c: conflict_pass(t, c, self.other_tree))
384
488
            finally:
385
489
                child_pb.finished()
 
490
            if change_reporter is not None:
 
491
                from bzrlib import delta
 
492
                delta.report_changes(self.tt._iter_changes(), change_reporter)
386
493
            self.cook_conflicts(fs_conflicts)
387
494
            for conflict in self.cooked_conflicts:
388
495
                warning(conflict)
389
496
            self.pp.next_phase()
390
 
            results = self.tt.apply()
 
497
            results = self.tt.apply(no_conflicts=True)
391
498
            self.write_modified(results)
392
499
            try:
393
500
                working_tree.add_conflicts(self.cooked_conflicts)
395
502
                pass
396
503
        finally:
397
504
            self.tt.finalize()
398
 
            working_tree.unlock()
 
505
            self.other_tree.unlock()
 
506
            self.base_tree.unlock()
 
507
            self.this_tree.unlock()
399
508
            self.pb.clear()
400
509
 
 
510
    def _entries3(self):
 
511
        """Gather data about files modified between three trees.
 
512
 
 
513
        Return a list of tuples of file_id, changed, parents3, names3,
 
514
        executable3.  changed is a boolean indicating whether the file contents
 
515
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
516
        other and this.  names3 is a tuple of names for base, other and this.
 
517
        executable3 is a tuple of execute-bit values for base, other and this.
 
518
        """
 
519
        result = []
 
520
        iterator = self.other_tree._iter_changes(self.base_tree,
 
521
                include_unchanged=True, specific_files=self.interesting_files,
 
522
                extra_trees=[self.this_tree])
 
523
        for (file_id, paths, changed, versioned, parents, names, kind,
 
524
             executable) in iterator:
 
525
            if (self.interesting_ids is not None and
 
526
                file_id not in self.interesting_ids):
 
527
                continue
 
528
            if file_id in self.this_tree.inventory:
 
529
                entry = self.this_tree.inventory[file_id]
 
530
                this_name = entry.name
 
531
                this_parent = entry.parent_id
 
532
                this_executable = entry.executable
 
533
            else:
 
534
                this_name = None
 
535
                this_parent = None
 
536
                this_executable = None
 
537
            parents3 = parents + (this_parent,)
 
538
            names3 = names + (this_name,)
 
539
            executable3 = executable + (this_executable,)
 
540
            result.append((file_id, changed, parents3, names3, executable3))
 
541
        return result
 
542
 
 
543
    def fix_root(self):
 
544
        try:
 
545
            self.tt.final_kind(self.tt.root)
 
546
        except NoSuchFile:
 
547
            self.tt.cancel_deletion(self.tt.root)
 
548
        if self.tt.final_file_id(self.tt.root) is None:
 
549
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
550
                                 self.tt.root)
 
551
        if self.other_tree.inventory.root is None:
 
552
            return
 
553
        other_root_file_id = self.other_tree.inventory.root.file_id
 
554
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
555
        if other_root == self.tt.root:
 
556
            return
 
557
        try:
 
558
            self.tt.final_kind(other_root)
 
559
        except NoSuchFile:
 
560
            return
 
561
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
562
        self.tt.cancel_creation(other_root)
 
563
        self.tt.cancel_versioning(other_root)
 
564
 
 
565
    def reparent_children(self, ie, target):
 
566
        for thing, child in ie.children.iteritems():
 
567
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
568
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
569
 
401
570
    def write_modified(self, results):
402
571
        modified_hashes = {}
403
572
        for path in results.modified_paths:
448
617
        return tree.kind(file_id)
449
618
 
450
619
    @staticmethod
 
620
    def _three_way(base, other, this):
 
621
        #if base == other, either they all agree, or only THIS has changed.
 
622
        if base == other:
 
623
            return 'this'
 
624
        elif this not in (base, other):
 
625
            return 'conflict'
 
626
        # "Ambiguous clean merge" -- both sides have made the same change.
 
627
        elif this == other:
 
628
            return "this"
 
629
        # this == base: only other has changed.
 
630
        else:
 
631
            return "other"
 
632
 
 
633
    @staticmethod
451
634
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
452
635
        """Do a three-way test on a scalar.
453
636
        Return "this", "other" or "conflict", depending whether a value wins.
468
651
            return "other"
469
652
 
470
653
    def merge_names(self, file_id):
471
 
        """Perform a merge on file_id names and parents"""
472
654
        def get_entry(tree):
473
655
            if file_id in tree.inventory:
474
656
                return tree.inventory[file_id]
477
659
        this_entry = get_entry(self.this_tree)
478
660
        other_entry = get_entry(self.other_tree)
479
661
        base_entry = get_entry(self.base_tree)
480
 
        name_winner = self.scalar_three_way(this_entry, base_entry, 
481
 
                                            other_entry, file_id, self.name)
482
 
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
483
 
                                                 other_entry, file_id, 
484
 
                                                 self.parent)
485
 
        if this_entry is None:
 
662
        entries = (base_entry, other_entry, this_entry)
 
663
        names = []
 
664
        parents = []
 
665
        for entry in entries:
 
666
            if entry is None:
 
667
                names.append(None)
 
668
                parents.append(None)
 
669
            else:
 
670
                names.append(entry.name)
 
671
                parents.append(entry.parent_id)
 
672
        return self._merge_names(file_id, parents, names)
 
673
 
 
674
    def _merge_names(self, file_id, parents, names):
 
675
        """Perform a merge on file_id names and parents"""
 
676
        base_name, other_name, this_name = names
 
677
        base_parent, other_parent, this_parent = parents
 
678
 
 
679
        name_winner = self._three_way(*names)
 
680
 
 
681
        parent_id_winner = self._three_way(*parents)
 
682
        if this_name is None:
486
683
            if name_winner == "this":
487
684
                name_winner = "other"
488
685
            if parent_id_winner == "this":
492
689
        if name_winner == "conflict":
493
690
            trans_id = self.tt.trans_id_file_id(file_id)
494
691
            self._raw_conflicts.append(('name conflict', trans_id, 
495
 
                                        self.name(this_entry, file_id), 
496
 
                                        self.name(other_entry, file_id)))
 
692
                                        this_name, other_name))
497
693
        if parent_id_winner == "conflict":
498
694
            trans_id = self.tt.trans_id_file_id(file_id)
499
695
            self._raw_conflicts.append(('parent conflict', trans_id, 
500
 
                                        self.parent(this_entry, file_id), 
501
 
                                        self.parent(other_entry, file_id)))
502
 
        if other_entry is None:
 
696
                                        this_parent, other_parent))
 
697
        if other_name is None:
503
698
            # it doesn't matter whether the result was 'other' or 
504
699
            # 'conflict'-- if there's no 'other', we leave it alone.
505
700
            return
506
701
        # if we get here, name_winner and parent_winner are set to safe values.
507
 
        winner_entry = {"this": this_entry, "other": other_entry, 
508
 
                        "conflict": other_entry}
509
702
        trans_id = self.tt.trans_id_file_id(file_id)
510
 
        parent_id = winner_entry[parent_id_winner].parent_id
511
 
        parent_trans_id = self.tt.trans_id_file_id(parent_id)
512
 
        self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
513
 
                            trans_id)
 
703
        parent_id = parents[self.winner_idx[parent_id_winner]]
 
704
        if parent_id is not None:
 
705
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
706
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
 
707
                                parent_trans_id, trans_id)
514
708
 
515
709
    def merge_contents(self, file_id):
516
710
        """Performa a merge on file_id contents."""
532
726
            parent_id = self.tt.final_parent(trans_id)
533
727
            if file_id in self.this_tree.inventory:
534
728
                self.tt.unversion_file(trans_id)
535
 
                self.tt.delete_contents(trans_id)
 
729
                if file_id in self.this_tree:
 
730
                    self.tt.delete_contents(trans_id)
536
731
            file_group = self._dump_conflicts(name, parent_id, file_id, 
537
732
                                              set_version=True)
538
733
            self._raw_conflicts.append(('contents conflict', file_group))
674
869
 
675
870
    def merge_executable(self, file_id, file_status):
676
871
        """Perform a merge on the execute bit."""
 
872
        executable = [self.executable(t, file_id) for t in (self.base_tree,
 
873
                      self.other_tree, self.this_tree)]
 
874
        self._merge_executable(file_id, executable, file_status)
 
875
 
 
876
    def _merge_executable(self, file_id, executable, file_status):
 
877
        """Perform a merge on the execute bit."""
 
878
        base_executable, other_executable, this_executable = executable
677
879
        if file_status == "deleted":
678
880
            return
679
881
        trans_id = self.tt.trans_id_file_id(file_id)
682
884
                return
683
885
        except NoSuchFile:
684
886
            return
685
 
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
686
 
                                       self.other_tree, file_id, 
687
 
                                       self.executable)
 
887
        winner = self._three_way(*executable)
688
888
        if winner == "conflict":
689
889
        # There must be a None in here, if we have a conflict, but we
690
890
        # need executability since file status was not deleted.
694
894
                winner = "other"
695
895
        if winner == "this":
696
896
            if file_status == "modified":
697
 
                executability = self.this_tree.is_executable(file_id)
 
897
                executability = this_executable
698
898
                if executability is not None:
699
899
                    trans_id = self.tt.trans_id_file_id(file_id)
700
900
                    self.tt.set_executability(executability, trans_id)
701
901
        else:
702
902
            assert winner == "other"
703
903
            if file_id in self.other_tree:
704
 
                executability = self.other_tree.is_executable(file_id)
 
904
                executability = other_executable
705
905
            elif file_id in self.this_tree:
706
 
                executability = self.this_tree.is_executable(file_id)
 
906
                executability = this_executable
707
907
            elif file_id in self.base_tree:
708
 
                executability = self.base_tree.is_executable(file_id)
 
908
                executability = base_executable
709
909
            if executability is not None:
710
910
                trans_id = self.tt.trans_id_file_id(file_id)
711
911
                self.tt.set_executability(executability, trans_id)
757
957
            except KeyError:
758
958
                this_name = other_name = self.tt.final_name(trans_id)
759
959
            other_path = fp.get_path(trans_id)
760
 
            if this_parent is not None:
 
960
            if this_parent is not None and this_name is not None:
761
961
                this_parent_path = \
762
962
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
763
963
                this_path = pathjoin(this_parent_path, this_name)
777
977
 
778
978
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
779
979
                 interesting_ids=None, pb=DummyProgress(), pp=None,
780
 
                 reprocess=False):
781
 
        self.this_revision_tree = self._get_revision_tree(this_tree)
782
 
        self.other_revision_tree = self._get_revision_tree(other_tree)
 
980
                 reprocess=False, change_reporter=None,
 
981
                 interesting_files=None):
783
982
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
784
983
                                          base_tree, other_tree, 
785
984
                                          interesting_ids=interesting_ids, 
786
 
                                          pb=pb, pp=pp, reprocess=reprocess)
787
 
 
788
 
    def _get_revision_tree(self, tree):
789
 
        """Return a revision tree related to this tree.
790
 
        If the tree is a WorkingTree, the basis will be returned.
791
 
        """
792
 
        if getattr(tree, 'get_weave', False) is False:
793
 
            # If we have a WorkingTree, try using the basis
794
 
            return tree.branch.basis_tree()
795
 
        else:
796
 
            return tree
797
 
 
798
 
    def _check_file(self, file_id):
799
 
        """Check that the revision tree's version of the file matches."""
800
 
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
801
 
                         (self.other_tree, self.other_revision_tree)):
802
 
            if rt is tree:
803
 
                continue
804
 
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
805
 
                raise WorkingTreeNotRevision(self.this_tree)
 
985
                                          pb=pb, pp=pp, reprocess=reprocess,
 
986
                                          change_reporter=change_reporter)
806
987
 
807
988
    def _merged_lines(self, file_id):
808
989
        """Generate the merged lines.
809
990
        There is no distinction between lines that are meant to contain <<<<<<<
810
991
        and conflicts.
811
992
        """
812
 
        weave = self.this_revision_tree.get_weave(file_id)
813
 
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
814
 
        other_revision_id = \
815
 
            self.other_revision_tree.inventory[file_id].revision
816
 
        wm = WeaveMerge(weave, this_revision_id, other_revision_id, 
817
 
                        '<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
818
 
        return wm.merge_lines(self.reprocess)
 
993
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
 
994
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
995
            '>>>>>>> MERGE-SOURCE\n')
 
996
        return textmerge.merge_lines(self.reprocess)
819
997
 
820
998
    def text_merge(self, file_id, trans_id):
821
999
        """Perform a (weave) text merge for a given file and file-id.
822
1000
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
823
1001
        and a conflict will be noted.
824
1002
        """
825
 
        self._check_file(file_id)
826
1003
        lines, conflicts = self._merged_lines(file_id)
827
1004
        lines = list(lines)
828
1005
        # Note we're checking whether the OUTPUT is binary in this case, 
858
1035
        will be dumped, and a will be conflict noted.
859
1036
        """
860
1037
        import bzrlib.patch
861
 
        temp_dir = mkdtemp(prefix="bzr-")
 
1038
        temp_dir = osutils.mkdtemp(prefix="bzr-")
862
1039
        try:
863
1040
            new_file = pathjoin(temp_dir, "new")
864
1041
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
876
1053
                name = self.tt.final_name(trans_id)
877
1054
                parent_id = self.tt.final_parent(trans_id)
878
1055
                self._dump_conflicts(name, parent_id, file_id)
879
 
            self._raw_conflicts.append(('text conflict', trans_id))
 
1056
                self._raw_conflicts.append(('text conflict', trans_id))
880
1057
        finally:
881
 
            rmtree(temp_dir)
 
1058
            osutils.rmtree(temp_dir)
882
1059
 
883
1060
 
884
1061
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
885
 
                backup_files=False, 
886
 
                merge_type=Merge3Merger, 
887
 
                interesting_ids=None, 
888
 
                show_base=False, 
889
 
                reprocess=False, 
 
1062
                backup_files=False,
 
1063
                merge_type=Merge3Merger,
 
1064
                interesting_ids=None,
 
1065
                show_base=False,
 
1066
                reprocess=False,
890
1067
                other_rev_id=None,
891
1068
                interesting_files=None,
892
1069
                this_tree=None,
893
 
                pb=DummyProgress()):
 
1070
                pb=DummyProgress(),
 
1071
                change_reporter=None):
894
1072
    """Primary interface for merging. 
895
1073
 
896
1074
        typical use is probably 
898
1076
                     branch.get_revision_tree(base_revision))'
899
1077
        """
900
1078
    if this_tree is None:
901
 
        warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
902
 
             "bzrlib version 0.8.",
903
 
             DeprecationWarning,
904
 
             stacklevel=2)
905
 
        this_tree = this_branch.bzrdir.open_workingtree()
906
 
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree, 
907
 
                    pb=pb)
 
1079
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1080
            "parameter as of bzrlib version 0.8.")
 
1081
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1082
                    pb=pb, change_reporter=change_reporter)
908
1083
    merger.backup_files = backup_files
909
1084
    merger.merge_type = merge_type
910
1085
    merger.interesting_ids = interesting_ids
912
1087
    if interesting_files:
913
1088
        assert not interesting_ids, ('Only supply interesting_ids'
914
1089
                                     ' or interesting_files')
915
 
        merger._set_interesting_files(interesting_files)
916
 
    merger.show_base = show_base 
 
1090
        merger.interesting_files = interesting_files
 
1091
    merger.show_base = show_base
917
1092
    merger.reprocess = reprocess
918
1093
    merger.other_rev_id = other_rev_id
919
1094
    merger.other_basis = other_rev_id
920
1095
    return merger.do_merge()
921
1096
 
922
 
 
923
 
merge_types = {     "merge3": (Merge3Merger, "Native diff3-style merge"), 
924
 
                     "diff3": (Diff3Merger,  "Merge using external diff3"),
925
 
                     'weave': (WeaveMerger, "Weave-based merge")
926
 
              }
927
 
 
928
 
 
929
 
def merge_type_help():
930
 
    templ = '%s%%7s: %%s' % (' '*12)
931
 
    lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
932
 
    return '\n'.join(lines)
 
1097
def get_merge_type_registry():
 
1098
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1099
 
 
1100
    This method provides a sanctioned way to retrieve it.
 
1101
    """
 
1102
    from bzrlib import option
 
1103
    return option._merge_type_registry
 
1104
 
 
1105
 
 
1106
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
 
1107
    def status_a(revision, text):
 
1108
        if revision in ancestors_b:
 
1109
            return 'killed-b', text
 
1110
        else:
 
1111
            return 'new-a', text
 
1112
 
 
1113
    def status_b(revision, text):
 
1114
        if revision in ancestors_a:
 
1115
            return 'killed-a', text
 
1116
        else:
 
1117
            return 'new-b', text
 
1118
 
 
1119
    plain_a = [t for (a, t) in annotated_a]
 
1120
    plain_b = [t for (a, t) in annotated_b]
 
1121
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
 
1122
    blocks = matcher.get_matching_blocks()
 
1123
    a_cur = 0
 
1124
    b_cur = 0
 
1125
    for ai, bi, l in blocks:
 
1126
        # process all mismatched sections
 
1127
        # (last mismatched section is handled because blocks always
 
1128
        # includes a 0-length last block)
 
1129
        for revision, text in annotated_a[a_cur:ai]:
 
1130
            yield status_a(revision, text)
 
1131
        for revision, text in annotated_b[b_cur:bi]:
 
1132
            yield status_b(revision, text)
 
1133
 
 
1134
        # and now the matched section
 
1135
        a_cur = ai + l
 
1136
        b_cur = bi + l
 
1137
        for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
 
1138
            assert text_a == text_b
 
1139
            yield "unchanged", text_a