~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Vincent Ladeuil
  • Date: 2007-07-15 11:24:18 UTC
  • mfrom: (2617 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070715112418-9nn4n6esxv60ny4b
merge bzr.dev@1617

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
import warnings
21
21
 
22
22
from bzrlib import (
 
23
    errors,
23
24
    osutils,
24
25
    registry,
 
26
    revision as _mod_revision,
25
27
    )
26
28
from bzrlib.branch import Branch
27
29
from bzrlib.conflicts import ConflictList, Conflict
52
54
 
53
55
# TODO: Report back as changes are merged in
54
56
 
55
 
def _get_tree(treespec, local_branch=None, possible_transports=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, possible_transports)[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
57
 
94
58
def transform_tree(from_tree, to_tree, interesting_ids=None):
95
59
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
103
67
        object.__init__(self)
104
68
        assert this_tree is not None, "this_tree is required"
105
69
        self.this_branch = this_branch
106
 
        self.this_basis = this_branch.last_revision()
 
70
        self.this_basis = _mod_revision.ensure_null(
 
71
            this_branch.last_revision())
107
72
        self.this_rev_id = None
108
73
        self.this_tree = this_tree
109
74
        self.this_revision_tree = None
121
86
        self.pp = None
122
87
        self.recurse = recurse
123
88
        self.change_reporter = change_reporter
124
 
 
125
 
    def revision_tree(self, revision_id):
126
 
        return self.this_branch.repository.revision_tree(revision_id)
 
89
        self._cached_trees = {}
 
90
 
 
91
    def revision_tree(self, revision_id, branch=None):
 
92
        if revision_id not in self._cached_trees:
 
93
            if branch is None:
 
94
                branch = self.this_branch
 
95
            try:
 
96
                tree = self.this_tree.revision_tree(revision_id)
 
97
            except errors.NoSuchRevisionInTree:
 
98
                tree = branch.repository.revision_tree(revision_id)
 
99
            self._cached_trees[revision_id] = tree
 
100
        return self._cached_trees[revision_id]
 
101
 
 
102
    def _get_tree(self, treespec, possible_transports=None):
 
103
        from bzrlib import workingtree
 
104
        location, revno = treespec
 
105
        if revno is None:
 
106
            tree = workingtree.WorkingTree.open_containing(location)[0]
 
107
            return tree.branch, tree
 
108
        branch = Branch.open_containing(location, possible_transports)[0]
 
109
        if revno == -1:
 
110
            revision_id = branch.last_revision()
 
111
        else:
 
112
            revision_id = branch.get_rev_id(revno)
 
113
        revision_id = ensure_null(revision_id)
 
114
        return branch, self.revision_tree(revision_id, branch)
127
115
 
128
116
    def ensure_revision_trees(self):
129
117
        if self.this_revision_tree is None:
130
 
            self.this_basis_tree = self.this_branch.repository.revision_tree(
131
 
                self.this_basis)
 
118
            self.this_basis_tree = self.revision_tree(self.this_basis)
132
119
            if self.this_basis == self.this_rev_id:
133
120
                self.this_revision_tree = self.this_basis_tree
134
121
 
164
151
                raise BzrCommandError("Working tree has uncommitted changes.")
165
152
 
166
153
    def compare_basis(self):
167
 
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
 
154
        try:
 
155
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
156
        except errors.RevisionNotPresent:
 
157
            basis_tree = self.this_tree.basis_tree()
 
158
        changes = self.this_tree.changes_from(basis_tree)
168
159
        if not changes.has_changed():
169
160
            self.this_rev_id = self.this_basis
170
161
 
171
162
    def set_interesting_files(self, file_list):
172
163
        self.interesting_files = file_list
173
164
 
174
 
    def _set_interesting_files(self, file_list):
175
 
        """Set the list of interesting ids from a list of files."""
176
 
        if file_list is None:
177
 
            self.interesting_ids = None
178
 
            return
179
 
 
180
 
        interesting_ids = set()
181
 
        for path in file_list:
182
 
            found_id = False
183
 
            # TODO: jam 20070226 The trees are not locked at this time,
184
 
            #       wouldn't it make merge faster if it locks everything in the
185
 
            #       beginning? It locks at do_merge time, but this happens
186
 
            #       before that.
187
 
            for tree in (self.this_tree, self.base_tree, self.other_tree):
188
 
                file_id = tree.path2id(path)
189
 
                if file_id is not None:
190
 
                    interesting_ids.add(file_id)
191
 
                    found_id = True
192
 
            if not found_id:
193
 
                raise NotVersionedError(path=path)
194
 
        self.interesting_ids = interesting_ids
195
 
 
196
165
    def set_pending(self):
197
 
        if not self.base_is_ancestor:
198
 
            return
199
 
        if self.other_rev_id is None:
200
 
            return
201
 
        ancestry = set(self.this_branch.repository.get_ancestry(
202
 
            self.this_basis, topo_sorted=False))
203
 
        if self.other_rev_id in ancestry:
204
 
            return
205
 
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
 
166
        if not self.base_is_ancestor or not self.base_is_other_ancestor:
 
167
            return
 
168
        self._add_parent()
 
169
 
 
170
    def _add_parent(self):
 
171
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
 
172
        new_parent_trees = []
 
173
        for revision_id in new_parents:
 
174
            try:
 
175
                tree = self.revision_tree(revision_id)
 
176
            except errors.RevisionNotPresent:
 
177
                tree = None
 
178
            else:
 
179
                tree.lock_read()
 
180
            new_parent_trees.append((revision_id, tree))
 
181
        try:
 
182
            self.this_tree.set_parent_trees(new_parent_trees,
 
183
                                            allow_leftmost_as_ghost=True)
 
184
        finally:
 
185
            for _revision_id, tree in new_parent_trees:
 
186
                if tree is not None:
 
187
                    tree.unlock()
206
188
 
207
189
    def set_other(self, other_revision, possible_transports=None):
208
190
        """Set the revision and tree to merge from.
211
193
 
212
194
        :param other_revision: The [path, revision] list to merge from.
213
195
        """
214
 
        self.other_branch, self.other_tree = _get_tree(other_revision,
215
 
                                                       self.this_branch,
216
 
                                                       possible_transports)
 
196
        self.other_branch, self.other_tree = self._get_tree(other_revision,
 
197
                                                            possible_transports)
217
198
        if other_revision[1] == -1:
218
 
            self.other_rev_id = self.other_branch.last_revision()
219
 
            if self.other_rev_id is None:
 
199
            self.other_rev_id = _mod_revision.ensure_null(
 
200
                self.other_branch.last_revision())
 
201
            if _mod_revision.is_null(self.other_rev_id):
220
202
                raise NoCommits(self.other_branch)
221
203
            self.other_basis = self.other_rev_id
222
204
        elif other_revision[1] is not None:
227
209
            self.other_basis = self.other_branch.last_revision()
228
210
            if self.other_basis is None:
229
211
                raise NoCommits(self.other_branch)
230
 
        if self.other_branch.base != self.this_branch.base:
231
 
            self.this_branch.fetch(self.other_branch,
232
 
                                   last_revision=self.other_basis)
 
212
        if self.other_rev_id is not None:
 
213
            self._cached_trees[self.other_rev_id] = self.other_tree
 
214
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
233
215
 
234
216
    def set_other_revision(self, revision_id, other_branch):
235
217
        """Set 'other' based on a branch and revision id
239
221
        """
240
222
        self.other_rev_id = revision_id
241
223
        self.other_branch = other_branch
242
 
        self.this_branch.fetch(other_branch, self.other_rev_id)
 
224
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
243
225
        self.other_tree = self.revision_tree(revision_id)
244
226
        self.other_basis = revision_id
245
227
 
 
228
    def _maybe_fetch(self, source, target, revision_id):
 
229
        if (source.repository.bzrdir.root_transport.base !=
 
230
            target.repository.bzrdir.root_transport.base):
 
231
            target.fetch(source, revision_id)
 
232
 
246
233
    def find_base(self):
247
 
        self.set_base([None, None])
 
234
        this_repo = self.this_branch.repository
 
235
        graph = this_repo.get_graph()
 
236
        revisions = [ensure_null(self.this_basis),
 
237
                     ensure_null(self.other_basis)]
 
238
        if NULL_REVISION in revisions:
 
239
            self.base_rev_id = NULL_REVISION
 
240
        else:
 
241
            self.base_rev_id = graph.find_unique_lca(*revisions)
 
242
            if self.base_rev_id == NULL_REVISION:
 
243
                raise UnrelatedBranches()
 
244
        self.base_tree = self.revision_tree(self.base_rev_id)
 
245
        self.base_is_ancestor = True
 
246
        self.base_is_other_ancestor = True
248
247
 
249
248
    def set_base(self, base_revision):
250
249
        """Set the base revision to use for the merge.
253
252
        """
254
253
        mutter("doing merge() with no base_revision specified")
255
254
        if base_revision == [None, None]:
256
 
            try:
257
 
                pb = ui.ui_factory.nested_progress_bar()
258
 
                try:
259
 
                    this_repo = self.this_branch.repository
260
 
                    graph = this_repo.get_graph()
261
 
                    revisions = [ensure_null(self.this_basis),
262
 
                                 ensure_null(self.other_basis)]
263
 
                    if NULL_REVISION in revisions:
264
 
                        self.base_rev_id = NULL_REVISION
265
 
                    else:
266
 
                        self.base_rev_id = graph.find_unique_lca(*revisions)
267
 
                        if self.base_rev_id == NULL_REVISION:
268
 
                            raise UnrelatedBranches()
269
 
                finally:
270
 
                    pb.finished()
271
 
            except NoCommonAncestor:
272
 
                raise UnrelatedBranches()
273
 
            self.base_tree = _get_revid_tree_from_tree(self.this_tree,
274
 
                                                       self.base_rev_id,
275
 
                                                       None)
276
 
            self.base_is_ancestor = True
 
255
            self.find_base()
277
256
        else:
278
 
            base_branch, self.base_tree = _get_tree(base_revision)
 
257
            base_branch, self.base_tree = self._get_tree(base_revision)
279
258
            if base_revision[1] == -1:
280
259
                self.base_rev_id = base_branch.last_revision()
281
260
            elif base_revision[1] is None:
282
 
                self.base_rev_id = None
 
261
                self.base_rev_id = _mod_revision.NULL_REVISION
283
262
            else:
284
 
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
285
 
            if self.this_branch.base != base_branch.base:
286
 
                self.this_branch.fetch(base_branch)
 
263
                self.base_rev_id = _mod_revision.ensure_null(
 
264
                    base_branch.get_rev_id(base_revision[1]))
 
265
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
287
266
            self.base_is_ancestor = is_ancestor(self.this_basis, 
288
267
                                                self.base_rev_id,
289
268
                                                self.this_branch)
 
269
            self.base_is_other_ancestor = is_ancestor(self.other_basis,
 
270
                                                      self.base_rev_id,
 
271
                                                      self.this_branch)
290
272
 
291
273
    def do_merge(self):
292
274
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
346
328
 
347
329
        return len(merge.cooked_conflicts)
348
330
 
349
 
    def regen_inventory(self, new_entries):
350
 
        old_entries = self.this_tree.read_working_inventory()
351
 
        new_inventory = {}
352
 
        by_path = {}
353
 
        new_entries_map = {} 
354
 
        for path, file_id in new_entries:
355
 
            if path is None:
356
 
                continue
357
 
            new_entries_map[file_id] = path
358
 
 
359
 
        def id2path(file_id):
360
 
            path = new_entries_map.get(file_id)
361
 
            if path is not None:
362
 
                return path
363
 
            entry = old_entries[file_id]
364
 
            if entry.parent_id is None:
365
 
                return entry.name
366
 
            return pathjoin(id2path(entry.parent_id), entry.name)
367
 
            
368
 
        for file_id in old_entries:
369
 
            entry = old_entries[file_id]
370
 
            path = id2path(file_id)
371
 
            if file_id in self.base_tree.inventory:
372
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
373
 
            else:
374
 
                executable = getattr(entry, 'executable', False)
375
 
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
376
 
                                      entry.kind, executable)
377
 
                                      
378
 
            by_path[path] = file_id
379
 
        
380
 
        deletions = 0
381
 
        insertions = 0
382
 
        new_path_list = []
383
 
        for path, file_id in new_entries:
384
 
            if path is None:
385
 
                del new_inventory[file_id]
386
 
                deletions += 1
387
 
            else:
388
 
                new_path_list.append((path, file_id))
389
 
                if file_id not in old_entries:
390
 
                    insertions += 1
391
 
        # Ensure no file is added before its parent
392
 
        new_path_list.sort()
393
 
        for path, file_id in new_path_list:
394
 
            if path == '':
395
 
                parent = None
396
 
            else:
397
 
                parent = by_path[os.path.dirname(path)]
398
 
            abspath = pathjoin(self.this_tree.basedir, path)
399
 
            kind = osutils.file_kind(abspath)
400
 
            if file_id in self.base_tree.inventory:
401
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
402
 
            else:
403
 
                executable = False
404
 
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
405
 
            by_path[path] = file_id 
406
 
 
407
 
        # Get a list in insertion order
408
 
        new_inventory_list = new_inventory.values()
409
 
        mutter ("""Inventory regeneration:
410
 
    old length: %i insertions: %i deletions: %i new_length: %i"""\
411
 
            % (len(old_entries), insertions, deletions, 
412
 
               len(new_inventory_list)))
413
 
        assert len(new_inventory_list) == len(old_entries) + insertions\
414
 
            - deletions
415
 
        new_inventory_list.sort()
416
 
        return new_inventory_list
417
 
 
418
331
 
419
332
class Merge3Merger(object):
420
333
    """Three-way merger that uses the merge3 text merger"""
504
417
            for conflict in self.cooked_conflicts:
505
418
                warning(conflict)
506
419
            self.pp.next_phase()
507
 
            results = self.tt.apply()
 
420
            results = self.tt.apply(no_conflicts=True)
508
421
            self.write_modified(results)
509
422
            try:
510
423
                working_tree.add_conflicts(self.cooked_conflicts)