~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Robert Collins
  • Date: 2006-02-15 08:11:37 UTC
  • mto: (1534.1.24 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060215081137-4c27377517e96dd1
Make format 4/5/6 branches share a single LockableFiles instance across wt/branch/repository.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
 
18
18
import os
19
 
import tempfile
20
 
import shutil
21
19
import errno
22
20
 
23
 
import bzrlib.osutils
24
 
import bzrlib.revision
25
 
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
26
 
from bzrlib.merge_core import WeaveMerge
27
 
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
28
 
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
 
21
import bzrlib
 
22
from bzrlib._changeset import generate_changeset, ExceptionConflictHandler
 
23
from bzrlib._changeset import Inventory, Diff3Merge, ReplaceContents
 
24
from bzrlib._merge_core import WeaveMerge
 
25
from bzrlib._merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
29
26
from bzrlib.branch import Branch
 
27
from bzrlib.delta import compare_trees
30
28
from bzrlib.errors import (BzrCommandError,
31
 
                           UnrelatedBranches,
 
29
                           BzrError,
32
30
                           NoCommonAncestor,
33
31
                           NoCommits,
34
 
                           WorkingTreeNotRevision,
 
32
                           NoSuchRevision,
35
33
                           NotBranchError,
36
34
                           NotVersionedError,
37
 
                           BzrError)
38
 
from bzrlib.delta import compare_trees
 
35
                           UnrelatedBranches,
 
36
                           WorkingTreeNotRevision,
 
37
                           )
 
38
from bzrlib.fetch import greedy_fetch, fetch
 
39
import bzrlib.osutils
 
40
from bzrlib.osutils import rename, pathjoin
 
41
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
39
42
from bzrlib.trace import mutter, warning, note
40
 
from bzrlib.fetch import greedy_fetch, fetch
41
 
from bzrlib.revision import is_ancestor, NULL_REVISION
42
 
from bzrlib.osutils import rename
43
 
from bzrlib.revision import common_ancestor, MultipleRevisionSources
44
 
from bzrlib.errors import NoSuchRevision
45
43
 
46
44
# TODO: Report back as changes are merged in
47
45
 
48
 
# TODO: build_working_dir can be built on something simpler than merge()
49
 
 
50
 
# FIXME: merge() parameters seem oriented towards the command line
51
 
# NOTABUG: merge is a helper for commandline functions.  merge_inner is the
52
 
#          the core functionality.
53
 
 
54
46
# comments from abentley on irc: merge happens in two stages, each
55
47
# of which generates a changeset object
56
48
 
57
49
# stage 1: generate OLD->OTHER,
58
50
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
59
51
 
60
 
class MergeConflictHandler(ExceptionConflictHandler):
 
52
class _MergeConflictHandler(ExceptionConflictHandler):
61
53
    """Handle conflicts encountered while merging.
62
54
 
63
55
    This subclasses ExceptionConflictHandler, so that any types of
115
107
                    if file_id is not None:
116
108
                        new_path = self.this_tree.relpath(new_name)
117
109
                        rename(new_name, name)
118
 
                        self.this_tree.branch.rename_one(relpath, new_path)
119
 
                        assert self.this_tree.id2path(file_id) == relpath
120
 
                        self.this_tree._inventory = self.this_tree.read_working_inventory()
 
110
                        self.this_tree.rename_one(relpath, new_path)
121
111
                        assert self.this_tree.id2path(file_id) == new_path
122
112
        except OSError, e:
123
113
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
174
164
        return "skip"
175
165
 
176
166
    def rem_contents_conflict(self, filename, this_contents, base_contents):
177
 
        base_contents(filename+".BASE", self, False)
178
 
        this_contents(filename+".THIS", self, False)
179
 
        return ReplaceContents(this_contents, None)
180
 
 
181
 
    def rem_contents_conflict(self, filename, this_contents, base_contents):
182
 
        base_contents(filename+".BASE", self, False)
183
 
        this_contents(filename+".THIS", self, False)
 
167
        base_contents(filename+".BASE", self)
 
168
        this_contents(filename+".THIS", self)
184
169
        self.conflict("Other branch deleted locally modified file %s" %
185
170
                      filename)
186
171
        return ReplaceContents(this_contents, None)
204
189
            abspath = self.create_all_missing(entry.parent_id, tree)
205
190
        else:
206
191
            abspath = self.abs_this_path(entry.parent_id)
207
 
        entry_path = os.path.join(abspath, entry.name)
 
192
        entry_path = pathjoin(abspath, entry.name)
208
193
        if not os.path.isdir(entry_path):
209
194
            self.create(file_id, entry_path, tree)
210
195
        return entry_path
211
196
 
212
 
    def create(self, file_id, path, tree, reverse=False):
 
197
    def create(self, file_id, path, tree):
213
198
        """Uses tree data to create a filesystem object for the file_id"""
214
 
        from changeset import get_contents
215
 
        get_contents(tree, file_id)(path, self, reverse)
 
199
        from bzrlib._changeset import get_contents
 
200
        get_contents(tree, file_id)(path, self)
216
201
 
217
202
    def missing_for_merge(self, file_id, other_path):
218
203
        """The file_id doesn't exist in THIS, but does in OTHER and BASE"""
219
204
        self.conflict("Other branch modified locally deleted file %s" %
220
205
                      other_path)
221
206
        parent_dir = self.add_missing_parents(file_id, self.other_tree)
222
 
        stem = os.path.join(parent_dir, os.path.basename(other_path))
 
207
        stem = pathjoin(parent_dir, os.path.basename(other_path))
223
208
        self.create(file_id, stem+".OTHER", self.other_tree)
224
209
        self.create(file_id, stem+".BASE", self.base_tree)
225
210
 
228
213
        self.conflict("Three-way conflict merging %s" % filename)
229
214
 
230
215
    def finalize(self):
231
 
        if not self.ignore_zero:
232
 
            note("%d conflicts encountered.\n", self.conflicts)
233
 
            
234
 
def get_tree(treespec, local_branch=None):
 
216
        if self.conflicts == 0:
 
217
            if not self.ignore_zero:
 
218
                note("All changes applied successfully.")
 
219
        else:
 
220
            note("%d conflicts encountered." % self.conflicts)
 
221
 
 
222
def _get_tree(treespec, local_branch=None):
235
223
    location, revno = treespec
236
224
    branch = Branch.open_containing(location)[0]
237
225
    if revno is None:
242
230
        revision = branch.get_rev_id(revno)
243
231
        if revision is None:
244
232
            revision = NULL_REVISION
245
 
    return branch, get_revid_tree(branch, revision, local_branch)
246
 
 
247
 
def get_revid_tree(branch, revision, local_branch):
 
233
    return branch, _get_revid_tree(branch, revision, local_branch)
 
234
 
 
235
 
 
236
def _get_revid_tree(branch, revision, local_branch):
248
237
    if revision is None:
249
 
        base_tree = branch.working_tree()
 
238
        base_tree = branch.bzrdir.open_workingtree()
250
239
    else:
251
240
        if local_branch is not None:
252
 
            greedy_fetch(local_branch, branch, revision)
253
 
            base_tree = local_branch.revision_tree(revision)
 
241
            if local_branch.base != branch.base:
 
242
                greedy_fetch(local_branch, branch, revision)
 
243
            base_tree = local_branch.repository.revision_tree(revision)
254
244
        else:
255
 
            base_tree = branch.revision_tree(revision)
 
245
            base_tree = branch.repository.revision_tree(revision)
256
246
    return base_tree
257
247
 
258
248
 
259
 
def file_exists(tree, file_id):
260
 
    return tree.has_filename(tree.id2path(file_id))
261
 
    
262
 
 
263
 
def build_working_dir(to_dir):
264
 
    """Build a working directory in an empty directory.
265
 
 
266
 
    to_dir is a directory containing branch metadata but no working files,
267
 
    typically constructed by cloning an existing branch. 
268
 
 
269
 
    This is split out as a special idiomatic case of merge.  It could
270
 
    eventually be done by just building the tree directly calling into 
271
 
    lower-level code (e.g. constructing a changeset).
272
 
    """
273
 
    # RBC 20051019 is this not just 'export' ?
274
 
    # AB Well, export doesn't take care of inventory...
275
 
    this_branch = Branch.open_containing(to_dir)[0]
276
 
    transform_tree(this_branch.working_tree(), this_branch.basis_tree())
277
 
 
278
 
 
279
249
def transform_tree(from_tree, to_tree, interesting_ids=None):
280
250
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
281
251
                interesting_ids=interesting_ids)
282
252
 
283
253
 
284
 
def merge(other_revision, base_revision,
285
 
          check_clean=True, ignore_zero=False,
286
 
          this_dir=None, backup_files=False, merge_type=ApplyMerge3,
287
 
          file_list=None, show_base=False, reprocess=False):
288
 
    """Merge changes into a tree.
289
 
 
290
 
    base_revision
291
 
        list(path, revno) Base for three-way merge.  
292
 
        If [None, None] then a base will be automatically determined.
293
 
    other_revision
294
 
        list(path, revno) Other revision for three-way merge.
295
 
    this_dir
296
 
        Directory to merge changes into; '.' by default.
297
 
    check_clean
298
 
        If true, this_dir must have no uncommitted changes before the
299
 
        merge begins.
300
 
    ignore_zero - If true, suppress the "zero conflicts" message when 
301
 
        there are no conflicts; should be set when doing something we expect
302
 
        to complete perfectly.
303
 
    file_list - If supplied, merge only changes to selected files.
304
 
 
305
 
    All available ancestors of other_revision and base_revision are
306
 
    automatically pulled into the branch.
307
 
 
308
 
    The revno may be -1 to indicate the last revision on the branch, which is
309
 
    the typical case.
310
 
 
311
 
    This function is intended for use from the command line; programmatic
312
 
    clients might prefer to call merge_inner(), which has less magic behavior.
313
 
    """
314
 
    if this_dir is None:
315
 
        this_dir = '.'
316
 
    this_branch = Branch.open_containing(this_dir)[0]
317
 
    if show_base and not merge_type is ApplyMerge3:
318
 
        raise BzrCommandError("Show-base is not supported for this merge"
319
 
                              " type. %s" % merge_type)
320
 
    if reprocess and not merge_type is ApplyMerge3:
321
 
        raise BzrCommandError("Reprocess is not supported for this merge"
322
 
                              " type. %s" % merge_type)
323
 
    if reprocess and show_base:
324
 
        raise BzrCommandError("Cannot reprocess and show base.")
325
 
    merger = Merger(this_branch)
326
 
    merger.check_basis(check_clean)
327
 
    merger.set_other(other_revision)
328
 
    merger.set_base(base_revision)
329
 
    if merger.base_rev_id == merger.other_rev_id:
330
 
        note('Nothing to do.')
331
 
        return 0
332
 
    merger.backup_files = backup_files
333
 
    merger.merge_type = merge_type 
334
 
    merger.set_interesting_files(file_list)
335
 
    merger.show_base = show_base 
336
 
    merger.reprocess = reprocess
337
 
    merger.conflict_handler = MergeConflictHandler(merger.this_tree, 
338
 
                                                   merger.base_tree, 
339
 
                                                   merger.other_tree,
340
 
                                                   ignore_zero=ignore_zero)
341
 
    conflicts = merger.do_merge()
342
 
    merger.set_pending()
343
 
    return conflicts
344
 
 
345
254
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
346
255
                backup_files=False, 
347
256
                merge_type=ApplyMerge3, 
349
258
                show_base=False, 
350
259
                reprocess=False, 
351
260
                other_rev_id=None,
352
 
                interesting_files=None):
 
261
                interesting_files=None,
 
262
                this_tree=None):
353
263
    """Primary interface for merging. 
354
264
 
355
265
        typical use is probably 
356
266
        'merge_inner(branch, branch.get_revision_tree(other_revision),
357
267
                     branch.get_revision_tree(base_revision))'
358
268
        """
359
 
    merger = Merger(this_branch, other_tree, base_tree)
 
269
    if this_tree is None:
 
270
        this_tree = this_branch.bzrdir.open_workingtree()
 
271
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree)
360
272
    merger.backup_files = backup_files
361
273
    merger.merge_type = merge_type
362
274
    merger.interesting_ids = interesting_ids
366
278
        merger._set_interesting_files(interesting_files)
367
279
    merger.show_base = show_base 
368
280
    merger.reprocess = reprocess
369
 
    merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree, 
370
 
                                                   other_tree,
371
 
                                                   ignore_zero=ignore_zero)
 
281
    merger.conflict_handler = _MergeConflictHandler(merger.this_tree, 
 
282
                                                    base_tree, other_tree,
 
283
                                                    ignore_zero=ignore_zero)
372
284
    merger.other_rev_id = other_rev_id
373
285
    merger.other_basis = other_rev_id
374
286
    return merger.do_merge()
375
287
 
376
288
 
377
289
class Merger(object):
378
 
    def __init__(self, this_branch, other_tree=None, base_tree=None):
 
290
    def __init__(self, this_branch, other_tree=None, base_tree=None, this_tree=None):
379
291
        object.__init__(self)
 
292
        assert this_tree is not None, "this_tree is required"
380
293
        self.this_branch = this_branch
381
294
        self.this_basis = this_branch.last_revision()
382
295
        self.this_rev_id = None
383
 
        self.this_tree = this_branch.working_tree()
 
296
        self.this_tree = this_tree
384
297
        self.this_revision_tree = None
385
298
        self.this_basis_tree = None
386
299
        self.other_tree = other_tree
390
303
        self.interesting_ids = None
391
304
        self.show_base = False
392
305
        self.reprocess = False
393
 
        self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree, 
394
 
                                                     other_tree)
 
306
        self.conflict_handler = _MergeConflictHandler(self.this_tree, 
 
307
                                                      base_tree, other_tree)
395
308
 
396
309
    def revision_tree(self, revision_id):
397
 
        return self.this_branch.revision_tree(revision_id)
 
310
        return self.this_branch.repository.revision_tree(revision_id)
398
311
 
399
312
    def ensure_revision_trees(self):
400
313
        if self.this_revision_tree is None:
401
 
            self.this_basis_tree = self.this_branch.revision_tree(
 
314
            self.this_basis_tree = self.this_branch.repository.revision_tree(
402
315
                self.this_basis)
403
316
            if self.this_basis == self.this_rev_id:
404
317
                self.this_revision_tree = self.this_basis_tree
405
318
 
406
 
 
407
319
        if self.other_rev_id is None:
408
320
            other_basis_tree = self.revision_tree(self.other_basis)
409
321
            changes = compare_trees(self.other_tree, other_basis_tree)
412
324
            other_rev_id = other_basis
413
325
            self.other_tree = other_basis_tree
414
326
 
415
 
 
416
327
    def file_revisions(self, file_id):
417
328
        self.ensure_revision_trees()
418
329
        def get_id(tree, file_id):
426
337
 
427
338
        trees = (self.this_basis_tree, self.other_tree)
428
339
        return [get_id(tree, file_id) for tree in trees]
429
 
            
430
340
 
431
341
    def merge_factory(self, file_id, base, other):
432
342
        if self.merge_type.history_based:
458
368
                raise BzrCommandError("Working tree has uncommitted changes.")
459
369
 
460
370
    def compare_basis(self):
461
 
        changes = compare_trees(self.this_branch.working_tree(), 
462
 
                                self.this_branch.basis_tree(), False)
 
371
        changes = compare_trees(self.this_tree, 
 
372
                                self.this_tree.basis_tree(), False)
463
373
        if not changes.has_changed():
464
374
            self.this_rev_id = self.this_basis
465
375
 
477
387
            return
478
388
 
479
389
        interesting_ids = set()
480
 
        for fname in file_list:
481
 
            path = self.this_tree.relpath(fname)
 
390
        for path in file_list:
482
391
            found_id = False
483
392
            for tree in (self.this_tree, self.base_tree, self.other_tree):
484
393
                file_id = tree.inventory.path2id(path)
486
395
                    interesting_ids.add(file_id)
487
396
                    found_id = True
488
397
            if not found_id:
489
 
                raise NotVersionedError(path=fname)
 
398
                raise NotVersionedError(path=path)
490
399
        self.interesting_ids = interesting_ids
491
400
 
492
401
    def set_pending(self):
494
403
            return
495
404
        if self.other_rev_id is None:
496
405
            return
497
 
        if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
 
406
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
 
407
        if self.other_rev_id in ancestry:
498
408
            return
499
 
        self.this_branch.working_tree().add_pending_merge(self.other_rev_id)
 
409
        self.this_tree.add_pending_merge(self.other_rev_id)
500
410
 
501
411
    def set_other(self, other_revision):
502
 
        other_branch, self.other_tree = get_tree(other_revision, 
503
 
                                                 self.this_branch)
 
412
        other_branch, self.other_tree = _get_tree(other_revision, 
 
413
                                                  self.this_branch)
504
414
        if other_revision[1] == -1:
505
415
            self.other_rev_id = other_branch.last_revision()
506
416
            if self.other_rev_id is None:
514
424
            self.other_basis = other_branch.last_revision()
515
425
            if self.other_basis is None:
516
426
                raise NoCommits(other_branch)
517
 
        fetch(from_branch=other_branch, to_branch=self.this_branch, 
518
 
              last_revision=self.other_basis)
 
427
        if other_branch.base != self.this_branch.base:
 
428
            fetch(from_branch=other_branch, to_branch=self.this_branch, 
 
429
                  last_revision=self.other_basis)
519
430
 
520
431
    def set_base(self, base_revision):
521
432
        mutter("doing merge() with no base_revision specified")
523
434
            try:
524
435
                self.base_rev_id = common_ancestor(self.this_basis, 
525
436
                                                   self.other_basis, 
526
 
                                                   self.this_branch)
 
437
                                                   self.this_branch.repository)
527
438
            except NoCommonAncestor:
528
439
                raise UnrelatedBranches()
529
 
            self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
 
440
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
530
441
                                            None)
531
442
            self.base_is_ancestor = True
532
443
        else:
533
 
            base_branch, self.base_tree = get_tree(base_revision)
 
444
            base_branch, self.base_tree = _get_tree(base_revision)
534
445
            if base_revision[1] == -1:
535
446
                self.base_rev_id = base_branch.last_revision()
536
447
            elif base_revision[1] is None:
556
467
        adjust_ids = []
557
468
        for id, path in inv_changes.iteritems():
558
469
            if path is not None:
559
 
                if path == '.':
560
 
                    path = ''
 
470
                if path == u'.':
 
471
                    path = u''
561
472
                else:
562
 
                    assert path.startswith('.' + os.sep), "path is %s" % path
 
473
                    assert path.startswith('.' + '/') or path.startswith('.' + '\\'), "path is %s" % path
563
474
                path = path[2:]
564
475
            adjust_ids.append((path, id))
565
476
        if len(adjust_ids) > 0:
566
 
            self.this_branch.working_tree().set_inventory(self.regen_inventory(adjust_ids))
 
477
            self.this_tree.set_inventory(self.regen_inventory(adjust_ids))
567
478
        conflicts = self.conflict_handler.conflicts
568
479
        self.conflict_handler.finalize()
569
480
        return conflicts
570
481
 
571
482
    def regen_inventory(self, new_entries):
572
 
        old_entries = self.this_branch.working_tree().read_working_inventory()
 
483
        old_entries = self.this_tree.read_working_inventory()
573
484
        new_inventory = {}
574
485
        by_path = {}
575
486
        new_entries_map = {} 
585
496
            entry = old_entries[file_id]
586
497
            if entry.parent_id is None:
587
498
                return entry.name
588
 
            return os.path.join(id2path(entry.parent_id), entry.name)
 
499
            return pathjoin(id2path(entry.parent_id), entry.name)
589
500
            
590
501
        for file_id in old_entries:
591
502
            entry = old_entries[file_id]
612
523
                parent = None
613
524
            else:
614
525
                parent = by_path[os.path.dirname(path)]
615
 
            abspath = os.path.join(self.this_tree.basedir, path)
 
526
            abspath = pathjoin(self.this_tree.basedir, path)
616
527
            kind = bzrlib.osutils.file_kind(abspath)
617
528
            new_inventory[file_id] = (path, file_id, parent, kind)
618
529
            by_path[path] = file_id 
628
539
        new_inventory_list.sort()
629
540
        return new_inventory_list
630
541
 
 
542
 
631
543
merge_types = {     "merge3": (ApplyMerge3, "Native diff3-style merge"), 
632
544
                     "diff3": (Diff3Merge,  "Merge using external diff3"),
633
545
                     'weave': (WeaveMerge, "Weave-based merge")
634
546
              }
635