1
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
39
41
from bzrlib.merge3 import Merge3
41
from bzrlib.osutils import rename, pathjoin, rmtree
42
from bzrlib.osutils import rename, pathjoin
42
43
from progress import DummyProgress, ProgressPhase
43
44
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
44
from bzrlib.symbol_versioning import *
45
45
from bzrlib.textfile import check_text_lines
46
46
from bzrlib.trace import mutter, warning, note
47
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
FinalPaths, create_by_entry, unique_add)
48
FinalPaths, create_by_entry, unique_add,
49
50
from bzrlib.versionedfile import WeaveMerge
52
53
# TODO: Report back as changes are merged in
54
55
def _get_tree(treespec, local_branch=None):
56
from bzrlib import workingtree
55
57
location, revno = treespec
59
tree = workingtree.WorkingTree.open_containing(location)[0]
60
return tree.branch, tree
56
61
branch = Branch.open_containing(location)[0]
60
revision = branch.last_revision()
63
revision_id = branch.last_revision()
62
revision = branch.get_rev_id(revno)
64
revision = NULL_REVISION
65
return branch, _get_revid_tree(branch, revision, local_branch)
68
def _get_revid_tree(branch, revision, local_branch):
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)
71
def _get_revid_tree(branch, revision_id, local_branch):
72
if revision_id is None:
70
73
base_tree = branch.bzrdir.open_workingtree()
72
75
if local_branch is not None:
73
76
if local_branch.base != branch.base:
74
local_branch.fetch(branch, revision)
75
base_tree = local_branch.repository.revision_tree(revision)
77
local_branch.fetch(branch, revision_id)
78
base_tree = local_branch.repository.revision_tree(revision_id)
77
base_tree = branch.repository.revision_tree(revision)
80
base_tree = branch.repository.revision_tree(revision_id)
84
def _get_revid_tree_from_tree(tree, revision_id, local_branch):
85
if revision_id is None:
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)
81
94
def transform_tree(from_tree, to_tree, interesting_ids=None):
82
95
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
83
96
interesting_ids=interesting_ids, this_tree=from_tree)
86
99
class Merger(object):
87
def __init__(self, this_branch, other_tree=None, base_tree=None,
88
this_tree=None, pb=DummyProgress()):
100
def __init__(self, this_branch, other_tree=None, base_tree=None,
101
this_tree=None, pb=DummyProgress(), change_reporter=None,
89
103
object.__init__(self)
90
104
assert this_tree is not None, "this_tree is required"
91
105
self.this_branch = this_branch
137
153
trees = (self.this_basis_tree, self.other_tree)
138
154
return [get_id(tree, file_id) for tree in trees]
140
def check_basis(self, check_clean):
141
if self.this_basis is None:
142
raise BzrCommandError("This branch has no commits")
156
def check_basis(self, check_clean, require_commits=True):
157
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')")
144
161
self.compare_basis()
145
162
if self.this_basis != self.this_rev_id:
146
163
raise BzrCommandError("Working tree has uncommitted changes.")
148
165
def compare_basis(self):
149
changes = compare_trees(self.this_tree,
150
self.this_tree.basis_tree(), False)
166
changes = self.this_tree.changes_from(self.this_tree.basis_tree())
151
167
if not changes.has_changed():
152
168
self.this_rev_id = self.this_basis
184
204
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
185
205
if self.other_rev_id in ancestry:
187
self.this_tree.add_pending_merge(self.other_rev_id)
207
self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
189
209
def set_other(self, other_revision):
190
other_branch, self.other_tree = _get_tree(other_revision,
210
"""Set the revision and tree to merge from.
212
This sets the other_tree, other_rev_id, other_basis attributes.
214
:param other_revision: The [path, revision] list to merge from.
216
self.other_branch, self.other_tree = _get_tree(other_revision,
191
217
self.this_branch)
192
218
if other_revision[1] == -1:
193
self.other_rev_id = other_branch.last_revision()
219
self.other_rev_id = self.other_branch.last_revision()
194
220
if self.other_rev_id is None:
195
raise NoCommits(other_branch)
221
raise NoCommits(self.other_branch)
196
222
self.other_basis = self.other_rev_id
197
223
elif other_revision[1] is not None:
198
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
224
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
199
225
self.other_basis = self.other_rev_id
201
227
self.other_rev_id = None
202
self.other_basis = other_branch.last_revision()
228
self.other_basis = self.other_branch.last_revision()
203
229
if self.other_basis is None:
204
raise NoCommits(other_branch)
205
if other_branch.base != self.this_branch.base:
206
self.this_branch.fetch(other_branch, last_revision=self.other_basis)
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)
235
def set_other_revision(self, revision_id, other_branch):
236
"""Set 'other' based on a branch and revision id
238
:param revision_id: The revision to use for a tree
239
:param other_branch: The branch containing this tree
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
248
self.set_base([None, None])
208
250
def set_base(self, base_revision):
251
"""Set the base revision to use for the merge.
253
:param base_revision: A 2-list containing a path and revision number.
209
255
mutter("doing merge() with no base_revision specified")
210
256
if base_revision == [None, None]:
212
pb = bzrlib.ui.ui_factory.nested_progress_bar()
258
pb = ui.ui_factory.nested_progress_bar()
214
260
this_repo = self.this_branch.repository
215
261
self.base_rev_id = common_ancestor(self.this_basis,
253
300
elif self.show_base:
254
301
raise BzrError("Showing base is not supported for this"
255
302
" merge type. %s" % self.merge_type)
256
merge = self.merge_type(pb=self._pb, **kwargs)
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()
309
merge = self.merge_type(pb=self._pb,
310
change_reporter=self.change_reporter,
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(
317
if other_revision == sub_tree.last_revision():
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)
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()
257
335
if len(merge.cooked_conflicts) == 0:
258
336
if not self.ignore_zero:
259
337
note("All changes applied successfully.")
342
420
def __init__(self, working_tree, this_tree, base_tree, other_tree,
343
421
interesting_ids=None, reprocess=False, show_base=False,
344
pb=DummyProgress(), pp=None):
422
pb=DummyProgress(), pp=None, change_reporter=None):
345
423
"""Initialize the merger object and perform the merge."""
346
424
object.__init__(self)
347
425
self.this_tree = working_tree
426
self.this_tree.lock_tree_write()
348
427
self.base_tree = base_tree
428
self.base_tree.lock_read()
349
429
self.other_tree = other_tree
430
self.other_tree.lock_read()
350
431
self._raw_conflicts = []
351
432
self.cooked_conflicts = []
352
433
self.reprocess = reprocess
353
434
self.show_base = show_base
437
self.change_reporter = change_reporter
356
438
if self.pp is None:
357
439
self.pp = ProgressPhase("Merge phase", 3, self.pb)
388
472
results = self.tt.apply()
389
473
self.write_modified(results)
391
working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
475
working_tree.add_conflicts(self.cooked_conflicts)
392
476
except UnsupportedOperation:
399
working_tree.unlock()
480
self.other_tree.unlock()
481
self.base_tree.unlock()
482
self.this_tree.unlock()
487
self.tt.final_kind(self.tt.root)
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),
493
if self.other_tree.inventory.root is None:
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:
500
self.tt.final_kind(other_root)
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)
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)
402
512
def write_modified(self, results):
403
513
modified_hashes = {}
404
514
for path in results.modified_paths:
509
619
"conflict": other_entry}
510
620
trans_id = self.tt.trans_id_file_id(file_id)
511
621
parent_id = winner_entry[parent_id_winner].parent_id
512
parent_trans_id = self.tt.trans_id_file_id(parent_id)
513
self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_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)
516
627
def merge_contents(self, file_id):
517
628
"""Performa a merge on file_id contents."""
781
891
def __init__(self, working_tree, this_tree, base_tree, other_tree,
782
892
interesting_ids=None, pb=DummyProgress(), pp=None,
893
reprocess=False, change_reporter=None):
784
894
self.this_revision_tree = self._get_revision_tree(this_tree)
785
895
self.other_revision_tree = self._get_revision_tree(other_tree)
786
896
super(WeaveMerger, self).__init__(working_tree, this_tree,
787
897
base_tree, other_tree,
788
898
interesting_ids=interesting_ids,
789
pb=pb, pp=pp, reprocess=reprocess)
899
pb=pb, pp=pp, reprocess=reprocess,
900
change_reporter=change_reporter)
791
902
def _get_revision_tree(self, tree):
792
"""Return a revision tree releated to this tree.
903
"""Return a revision tree related to this tree.
793
904
If the tree is a WorkingTree, the basis will be returned.
795
906
if getattr(tree, 'get_weave', False) is False:
844
955
class Diff3Merger(Merge3Merger):
845
956
"""Three-way merger using external diff3 for text merging"""
846
958
def dump_file(self, temp_dir, name, tree, file_id):
847
959
out_path = pathjoin(temp_dir, name)
848
out_file = file(out_path, "wb")
849
in_file = tree.get_file(file_id)
960
out_file = open(out_path, "wb")
962
in_file = tree.get_file(file_id)
854
969
def text_merge(self, file_id, trans_id):
866
981
status = bzrlib.patch.diff3(new_file, this, base, other)
867
982
if status not in (0, 1):
868
983
raise BzrError("Unhandled diff3 exit code")
869
self.tt.create_file(file(new_file, "rb"), trans_id)
984
f = open(new_file, 'rb')
986
self.tt.create_file(f, trans_id)
871
990
name = self.tt.final_name(trans_id)
872
991
parent_id = self.tt.final_parent(trans_id)
873
992
self._dump_conflicts(name, parent_id, file_id)
874
self._raw_conflicts.append(('text conflict', trans_id))
993
self._raw_conflicts.append(('text conflict', trans_id))
995
osutils.rmtree(temp_dir)
879
998
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
881
merge_type=Merge3Merger,
882
interesting_ids=None,
1000
merge_type=Merge3Merger,
1001
interesting_ids=None,
885
1004
other_rev_id=None,
886
1005
interesting_files=None,
1008
change_reporter=None):
889
1009
"""Primary interface for merging.
891
1011
typical use is probably
893
1013
branch.get_revision_tree(base_revision))'
895
1015
if this_tree is None:
896
warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
897
"bzrlib version 0.8.",
900
this_tree = this_branch.bzrdir.open_workingtree()
901
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
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)
903
1020
merger.backup_files = backup_files
904
1021
merger.merge_type = merge_type
905
1022
merger.interesting_ids = interesting_ids
908
1025
assert not interesting_ids, ('Only supply interesting_ids'
909
1026
' or interesting_files')
910
1027
merger._set_interesting_files(interesting_files)
911
merger.show_base = show_base
1028
merger.show_base = show_base
912
1029
merger.reprocess = reprocess
913
1030
merger.other_rev_id = other_rev_id
914
1031
merger.other_basis = other_rev_id
915
1032
return merger.do_merge()
918
merge_types = { "merge3": (Merge3Merger, "Native diff3-style merge"),
919
"diff3": (Diff3Merger, "Merge using external diff3"),
920
'weave': (WeaveMerger, "Weave-based merge")
924
def merge_type_help():
925
templ = '%s%%7s: %%s' % (' '*12)
926
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
927
return '\n'.join(lines)
1034
def get_merge_type_registry():
1035
"""Merge type registry is in bzrlib.option to avoid circular imports.
1037
This method provides a sanctioned way to retrieve it.
1039
from bzrlib import option
1040
return option._merge_type_registry