~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 16:43:12 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730164312-b025fd3ff0cee59e
rename  gpl.txt => COPYING.txt

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
20
21
import warnings
21
22
 
22
 
from bzrlib import (
23
 
    osutils,
24
 
    registry,
25
 
    )
26
23
from bzrlib.branch import Branch
27
24
from bzrlib.conflicts import ConflictList, Conflict
28
25
from bzrlib.errors import (BzrCommandError,
39
36
                           BinaryFile,
40
37
                           )
41
38
from bzrlib.merge3 import Merge3
42
 
from bzrlib.osutils import rename, pathjoin
 
39
import bzrlib.osutils
 
40
from bzrlib.osutils import rename, pathjoin, rmtree
43
41
from progress import DummyProgress, ProgressPhase
44
42
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
45
43
from bzrlib.textfile import check_text_lines
46
44
from bzrlib.trace import mutter, warning, note
47
45
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
 
                              FinalPaths, create_by_entry, unique_add,
49
 
                              ROOT_PARENT)
 
46
                              FinalPaths, create_by_entry, unique_add)
50
47
from bzrlib.versionedfile import WeaveMerge
51
48
from bzrlib import ui
52
49
 
53
50
# TODO: Report back as changes are merged in
54
51
 
55
52
def _get_tree(treespec, local_branch=None):
56
 
    from bzrlib import workingtree
57
53
    location, revno = treespec
 
54
    branch = Branch.open_containing(location)[0]
58
55
    if revno is None:
59
 
        tree = workingtree.WorkingTree.open_containing(location)[0]
60
 
        return tree.branch, tree
61
 
    branch = Branch.open_containing(location)[0]
62
 
    if revno == -1:
63
 
        revision_id = branch.last_revision()
 
56
        revision = None
 
57
    elif revno == -1:
 
58
        revision = branch.last_revision()
64
59
    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:
 
60
        revision = branch.get_rev_id(revno)
 
61
        if revision is None:
 
62
            revision = NULL_REVISION
 
63
    return branch, _get_revid_tree(branch, revision, local_branch)
 
64
 
 
65
 
 
66
def _get_revid_tree(branch, revision, local_branch):
 
67
    if revision is None:
73
68
        base_tree = branch.bzrdir.open_workingtree()
74
69
    else:
75
70
        if local_branch is not None:
76
71
            if local_branch.base != branch.base:
77
 
                local_branch.fetch(branch, revision_id)
78
 
            base_tree = local_branch.repository.revision_tree(revision_id)
 
72
                local_branch.fetch(branch, revision)
 
73
            base_tree = local_branch.repository.revision_tree(revision)
79
74
        else:
80
 
            base_tree = branch.repository.revision_tree(revision_id)
 
75
            base_tree = branch.repository.revision_tree(revision)
81
76
    return base_tree
82
77
 
83
78
 
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
 
 
94
79
def transform_tree(from_tree, to_tree, interesting_ids=None):
95
80
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
96
81
                interesting_ids=interesting_ids, this_tree=from_tree)
97
82
 
98
83
 
99
84
class Merger(object):
100
 
    def __init__(self, this_branch, other_tree=None, base_tree=None,
101
 
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
102
 
                 recurse='down'):
 
85
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
 
86
                 this_tree=None, pb=DummyProgress()):
103
87
        object.__init__(self)
104
88
        assert this_tree is not None, "this_tree is required"
105
89
        self.this_branch = this_branch
109
93
        self.this_revision_tree = None
110
94
        self.this_basis_tree = None
111
95
        self.other_tree = other_tree
112
 
        self.other_branch = None
113
96
        self.base_tree = base_tree
114
97
        self.ignore_zero = False
115
98
        self.backup_files = False
116
99
        self.interesting_ids = None
117
100
        self.show_base = False
118
101
        self.reprocess = False
119
 
        self._pb = pb
 
102
        self._pb = pb 
120
103
        self.pp = None
121
 
        self.recurse = recurse
122
 
        self.change_reporter = change_reporter
 
104
 
123
105
 
124
106
    def revision_tree(self, revision_id):
125
107
        return self.this_branch.repository.revision_tree(revision_id)
155
137
 
156
138
    def check_basis(self, check_clean, require_commits=True):
157
139
        if self.this_basis is None and require_commits is True:
158
 
            raise BzrCommandError("This branch has no commits."
159
 
                                  " (perhaps you would prefer 'bzr pull')")
 
140
            raise BzrCommandError("This branch has no commits")
160
141
        if check_clean:
161
142
            self.compare_basis()
162
143
            if self.this_basis != self.this_rev_id:
183
164
        interesting_ids = set()
184
165
        for path in file_list:
185
166
            found_id = False
186
 
            # TODO: jam 20070226 The trees are not locked at this time,
187
 
            #       wouldn't it make merge faster if it locks everything in the
188
 
            #       beginning? It locks at do_merge time, but this happens
189
 
            #       before that.
190
167
            for tree in (self.this_tree, self.base_tree, self.other_tree):
191
 
                file_id = tree.path2id(path)
 
168
                file_id = tree.inventory.path2id(path)
192
169
                if file_id is not None:
193
170
                    interesting_ids.add(file_id)
194
171
                    found_id = True
204
181
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
205
182
        if self.other_rev_id in ancestry:
206
183
            return
207
 
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
 
184
        self.this_tree.add_pending_merge(self.other_rev_id)
208
185
 
209
186
    def set_other(self, other_revision):
210
 
        """Set the revision and tree to merge from.
211
 
 
212
 
        This sets the other_tree, other_rev_id, other_basis attributes.
213
 
 
214
 
        :param other_revision: The [path, revision] list to merge from.
215
 
        """
216
 
        self.other_branch, self.other_tree = _get_tree(other_revision,
 
187
        other_branch, self.other_tree = _get_tree(other_revision, 
217
188
                                                  self.this_branch)
218
189
        if other_revision[1] == -1:
219
 
            self.other_rev_id = self.other_branch.last_revision()
 
190
            self.other_rev_id = other_branch.last_revision()
220
191
            if self.other_rev_id is None:
221
 
                raise NoCommits(self.other_branch)
 
192
                raise NoCommits(other_branch)
222
193
            self.other_basis = self.other_rev_id
223
194
        elif other_revision[1] is not None:
224
 
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
 
195
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
225
196
            self.other_basis = self.other_rev_id
226
197
        else:
227
198
            self.other_rev_id = None
228
 
            self.other_basis = self.other_branch.last_revision()
 
199
            self.other_basis = other_branch.last_revision()
229
200
            if self.other_basis is None:
230
 
                raise NoCommits(self.other_branch)
231
 
        if self.other_branch.base != self.this_branch.base:
232
 
            self.this_branch.fetch(self.other_branch,
233
 
                                   last_revision=self.other_basis)
234
 
 
235
 
    def set_other_revision(self, revision_id, other_branch):
236
 
        """Set 'other' based on a branch and revision id
237
 
 
238
 
        :param revision_id: The revision to use for a tree
239
 
        :param other_branch: The branch containing this tree
240
 
        """
241
 
        self.other_rev_id = revision_id
242
 
        self.other_branch = other_branch
243
 
        self.this_branch.fetch(other_branch, self.other_rev_id)
244
 
        self.other_tree = self.revision_tree(revision_id)
245
 
        self.other_basis = revision_id
 
201
                raise NoCommits(other_branch)
 
202
        if other_branch.base != self.this_branch.base:
 
203
            self.this_branch.fetch(other_branch, last_revision=self.other_basis)
246
204
 
247
205
    def find_base(self):
248
206
        self.set_base([None, None])
249
207
 
250
208
    def set_base(self, base_revision):
251
 
        """Set the base revision to use for the merge.
252
 
 
253
 
        :param base_revision: A 2-list containing a path and revision number.
254
 
        """
255
209
        mutter("doing merge() with no base_revision specified")
256
210
        if base_revision == [None, None]:
257
211
            try:
258
 
                pb = ui.ui_factory.nested_progress_bar()
 
212
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
259
213
                try:
260
214
                    this_repo = self.this_branch.repository
261
215
                    self.base_rev_id = common_ancestor(self.this_basis, 
265
219
                    pb.finished()
266
220
            except NoCommonAncestor:
267
221
                raise UnrelatedBranches()
268
 
            self.base_tree = _get_revid_tree_from_tree(self.this_tree,
269
 
                                                       self.base_rev_id,
270
 
                                                       None)
 
222
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
 
223
                                            None)
271
224
            self.base_is_ancestor = True
272
225
        else:
273
226
            base_branch, self.base_tree = _get_tree(base_revision)
284
237
                                                self.this_branch)
285
238
 
286
239
    def do_merge(self):
287
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
288
 
                  'other_tree': self.other_tree,
 
240
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
 
241
                  'other_tree': self.other_tree, 
289
242
                  'interesting_ids': self.interesting_ids,
290
243
                  'pp': self.pp}
291
244
        if self.merge_type.requires_base:
300
253
        elif self.show_base:
301
254
            raise BzrError("Showing base is not supported for this"
302
255
                                  " merge type. %s" % self.merge_type)
303
 
        self.this_tree.lock_tree_write()
304
 
        if self.base_tree is not None:
305
 
            self.base_tree.lock_read()
306
 
        if self.other_tree is not None:
307
 
            self.other_tree.lock_read()
308
 
        try:
309
 
            merge = self.merge_type(pb=self._pb,
310
 
                                    change_reporter=self.change_reporter,
311
 
                                    **kwargs)
312
 
            if self.recurse == 'down':
313
 
                for path, file_id in self.this_tree.iter_references():
314
 
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
315
 
                    other_revision = self.other_tree.get_reference_revision(
316
 
                        file_id, path)
317
 
                    if  other_revision == sub_tree.last_revision():
318
 
                        continue
319
 
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
320
 
                    sub_merge.merge_type = self.merge_type
321
 
                    relpath = self.this_tree.relpath(path)
322
 
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
323
 
                    sub_merge.set_other_revision(other_revision, other_branch)
324
 
                    base_revision = self.base_tree.get_reference_revision(file_id)
325
 
                    sub_merge.base_tree = \
326
 
                        sub_tree.branch.repository.revision_tree(base_revision)
327
 
                    sub_merge.do_merge()
328
 
 
329
 
        finally:
330
 
            if self.other_tree is not None:
331
 
                self.other_tree.unlock()
332
 
            if self.base_tree is not None:
333
 
                self.base_tree.unlock()
334
 
            self.this_tree.unlock()
 
256
        merge = self.merge_type(pb=self._pb, **kwargs)
335
257
        if len(merge.cooked_conflicts) == 0:
336
258
            if not self.ignore_zero:
337
259
                note("All changes applied successfully.")
390
312
            else:
391
313
                parent = by_path[os.path.dirname(path)]
392
314
            abspath = pathjoin(self.this_tree.basedir, path)
393
 
            kind = osutils.file_kind(abspath)
 
315
            kind = bzrlib.osutils.file_kind(abspath)
394
316
            if file_id in self.base_tree.inventory:
395
317
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
396
318
            else:
419
341
 
420
342
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
421
343
                 interesting_ids=None, reprocess=False, show_base=False,
422
 
                 pb=DummyProgress(), pp=None, change_reporter=None):
 
344
                 pb=DummyProgress(), pp=None):
423
345
        """Initialize the merger object and perform the merge."""
424
346
        object.__init__(self)
425
347
        self.this_tree = working_tree
426
 
        self.this_tree.lock_tree_write()
427
348
        self.base_tree = base_tree
428
 
        self.base_tree.lock_read()
429
349
        self.other_tree = other_tree
430
 
        self.other_tree.lock_read()
431
350
        self._raw_conflicts = []
432
351
        self.cooked_conflicts = []
433
352
        self.reprocess = reprocess
434
353
        self.show_base = show_base
435
354
        self.pb = pb
436
355
        self.pp = pp
437
 
        self.change_reporter = change_reporter
438
356
        if self.pp is None:
439
357
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
440
358
 
443
361
        else:
444
362
            all_ids = set(base_tree)
445
363
            all_ids.update(other_tree)
 
364
        working_tree.lock_write()
446
365
        self.tt = TreeTransform(working_tree, self.pb)
447
366
        try:
448
367
            self.pp.next_phase()
455
374
                    self.merge_executable(file_id, file_status)
456
375
            finally:
457
376
                child_pb.finished()
458
 
            self.fix_root()
 
377
                
459
378
            self.pp.next_phase()
460
379
            child_pb = ui.ui_factory.nested_progress_bar()
461
380
            try:
462
381
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
463
382
            finally:
464
383
                child_pb.finished()
465
 
            if change_reporter is not None:
466
 
                from bzrlib import delta
467
 
                delta.report_changes(self.tt._iter_changes(), change_reporter)
468
384
            self.cook_conflicts(fs_conflicts)
469
385
            for conflict in self.cooked_conflicts:
470
386
                warning(conflict)
477
393
                pass
478
394
        finally:
479
395
            self.tt.finalize()
480
 
            self.other_tree.unlock()
481
 
            self.base_tree.unlock()
482
 
            self.this_tree.unlock()
 
396
            working_tree.unlock()
483
397
            self.pb.clear()
484
398
 
485
 
    def fix_root(self):
486
 
        try:
487
 
            self.tt.final_kind(self.tt.root)
488
 
        except NoSuchFile:
489
 
            self.tt.cancel_deletion(self.tt.root)
490
 
        if self.tt.final_file_id(self.tt.root) is None:
491
 
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
492
 
                                 self.tt.root)
493
 
        if self.other_tree.inventory.root is None:
494
 
            return
495
 
        other_root_file_id = self.other_tree.inventory.root.file_id
496
 
        other_root = self.tt.trans_id_file_id(other_root_file_id)
497
 
        if other_root == self.tt.root:
498
 
            return
499
 
        try:
500
 
            self.tt.final_kind(other_root)
501
 
        except NoSuchFile:
502
 
            return
503
 
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
504
 
        self.tt.cancel_creation(other_root)
505
 
        self.tt.cancel_versioning(other_root)
506
 
 
507
 
    def reparent_children(self, ie, target):
508
 
        for thing, child in ie.children.iteritems():
509
 
            trans_id = self.tt.trans_id_file_id(child.file_id)
510
 
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
511
 
 
512
399
    def write_modified(self, results):
513
400
        modified_hashes = {}
514
401
        for path in results.modified_paths:
619
506
                        "conflict": other_entry}
620
507
        trans_id = self.tt.trans_id_file_id(file_id)
621
508
        parent_id = winner_entry[parent_id_winner].parent_id
622
 
        if parent_id is not None:
623
 
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
624
 
            self.tt.adjust_path(winner_entry[name_winner].name, 
625
 
                                parent_trans_id, trans_id)
 
509
        parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
510
        self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
 
511
                            trans_id)
626
512
 
627
513
    def merge_contents(self, file_id):
628
514
        """Performa a merge on file_id contents."""
630
516
            if file_id not in tree:
631
517
                return (None, None)
632
518
            kind = tree.kind(file_id)
 
519
            if kind == "root_directory":
 
520
                kind = "directory"
633
521
            if kind == "file":
634
522
                contents = tree.get_file_sha1(file_id)
635
523
            elif kind == "symlink":
644
532
            parent_id = self.tt.final_parent(trans_id)
645
533
            if file_id in self.this_tree.inventory:
646
534
                self.tt.unversion_file(trans_id)
647
 
                if file_id in self.this_tree:
648
 
                    self.tt.delete_contents(trans_id)
 
535
                self.tt.delete_contents(trans_id)
649
536
            file_group = self._dump_conflicts(name, parent_id, file_id, 
650
537
                                              set_version=True)
651
538
            self._raw_conflicts.append(('contents conflict', file_group))
890
777
 
891
778
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
892
779
                 interesting_ids=None, pb=DummyProgress(), pp=None,
893
 
                 reprocess=False, change_reporter=None):
 
780
                 reprocess=False):
894
781
        self.this_revision_tree = self._get_revision_tree(this_tree)
895
782
        self.other_revision_tree = self._get_revision_tree(other_tree)
896
783
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
897
784
                                          base_tree, other_tree, 
898
785
                                          interesting_ids=interesting_ids, 
899
 
                                          pb=pb, pp=pp, reprocess=reprocess,
900
 
                                          change_reporter=change_reporter)
 
786
                                          pb=pb, pp=pp, reprocess=reprocess)
901
787
 
902
788
    def _get_revision_tree(self, tree):
903
789
        """Return a revision tree related to this tree.
972
858
        will be dumped, and a will be conflict noted.
973
859
        """
974
860
        import bzrlib.patch
975
 
        temp_dir = osutils.mkdtemp(prefix="bzr-")
 
861
        temp_dir = mkdtemp(prefix="bzr-")
976
862
        try:
977
863
            new_file = pathjoin(temp_dir, "new")
978
864
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
990
876
                name = self.tt.final_name(trans_id)
991
877
                parent_id = self.tt.final_parent(trans_id)
992
878
                self._dump_conflicts(name, parent_id, file_id)
993
 
                self._raw_conflicts.append(('text conflict', trans_id))
 
879
            self._raw_conflicts.append(('text conflict', trans_id))
994
880
        finally:
995
 
            osutils.rmtree(temp_dir)
 
881
            rmtree(temp_dir)
996
882
 
997
883
 
998
884
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
999
 
                backup_files=False,
1000
 
                merge_type=Merge3Merger,
1001
 
                interesting_ids=None,
1002
 
                show_base=False,
1003
 
                reprocess=False,
 
885
                backup_files=False, 
 
886
                merge_type=Merge3Merger, 
 
887
                interesting_ids=None, 
 
888
                show_base=False, 
 
889
                reprocess=False, 
1004
890
                other_rev_id=None,
1005
891
                interesting_files=None,
1006
892
                this_tree=None,
1007
 
                pb=DummyProgress(),
1008
 
                change_reporter=None):
 
893
                pb=DummyProgress()):
1009
894
    """Primary interface for merging. 
1010
895
 
1011
896
        typical use is probably 
1013
898
                     branch.get_revision_tree(base_revision))'
1014
899
        """
1015
900
    if this_tree is None:
1016
 
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1017
 
            "parameter as of bzrlib version 0.8.")
1018
 
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1019
 
                    pb=pb, change_reporter=change_reporter)
 
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)
1020
908
    merger.backup_files = backup_files
1021
909
    merger.merge_type = merge_type
1022
910
    merger.interesting_ids = interesting_ids
1025
913
        assert not interesting_ids, ('Only supply interesting_ids'
1026
914
                                     ' or interesting_files')
1027
915
        merger._set_interesting_files(interesting_files)
1028
 
    merger.show_base = show_base
 
916
    merger.show_base = show_base 
1029
917
    merger.reprocess = reprocess
1030
918
    merger.other_rev_id = other_rev_id
1031
919
    merger.other_basis = other_rev_id
1032
920
    return merger.do_merge()
1033
921
 
1034
 
def get_merge_type_registry():
1035
 
    """Merge type registry is in bzrlib.option to avoid circular imports.
1036
 
 
1037
 
    This method provides a sanctioned way to retrieve it.
1038
 
    """
1039
 
    from bzrlib import option
1040
 
    return option._merge_type_registry
 
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)