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
41
39
from bzrlib.merge3 import Merge3
42
from bzrlib.osutils import rename, pathjoin
41
from bzrlib.osutils import rename, pathjoin, rmtree
43
42
from progress import DummyProgress, ProgressPhase
44
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
43
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)
50
49
from bzrlib.versionedfile import WeaveMerge
53
52
# TODO: Report back as changes are merged in
55
54
def _get_tree(treespec, local_branch=None):
56
from bzrlib import workingtree
57
55
location, revno = treespec
56
branch = Branch.open_containing(location)[0]
59
tree = workingtree.WorkingTree.open_containing(location)[0]
60
return tree.branch, tree
61
branch = Branch.open_containing(location)[0]
63
revision_id = branch.last_revision()
60
revision = branch.last_revision()
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:
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):
73
70
base_tree = branch.bzrdir.open_workingtree()
75
72
if local_branch is not None:
76
73
if local_branch.base != branch.base:
77
local_branch.fetch(branch, revision_id)
78
base_tree = local_branch.repository.revision_tree(revision_id)
74
local_branch.fetch(branch, revision)
75
base_tree = local_branch.repository.revision_tree(revision)
80
base_tree = branch.repository.revision_tree(revision_id)
77
base_tree = branch.repository.revision_tree(revision)
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)
94
81
def transform_tree(from_tree, to_tree, interesting_ids=None):
95
82
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
96
83
interesting_ids=interesting_ids, this_tree=from_tree)
99
86
class Merger(object):
100
def __init__(self, this_branch, other_tree=None, base_tree=None,
101
this_tree=None, pb=DummyProgress(), change_reporter=None,
87
def __init__(self, this_branch, other_tree=None, base_tree=None,
88
this_tree=None, pb=DummyProgress()):
103
89
object.__init__(self)
104
90
assert this_tree is not None, "this_tree is required"
105
91
self.this_branch = this_branch
156
140
def check_basis(self, check_clean, require_commits=True):
157
141
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')")
142
raise BzrCommandError("This branch has no commits")
161
144
self.compare_basis()
162
145
if self.this_basis != self.this_rev_id:
163
146
raise BzrCommandError("Working tree has uncommitted changes.")
165
148
def compare_basis(self):
166
changes = self.this_tree.changes_from(self.this_tree.basis_tree())
149
changes = compare_trees(self.this_tree,
150
self.this_tree.basis_tree(), False)
167
151
if not changes.has_changed():
168
152
self.this_rev_id = self.this_basis
202
182
if self.other_rev_id is None:
204
ancestry = set(self.this_branch.repository.get_ancestry(
205
self.this_basis, topo_sorted=False))
184
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
206
185
if self.other_rev_id in ancestry:
208
self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
187
self.this_tree.add_pending_merge(self.other_rev_id)
210
189
def set_other(self, other_revision):
211
"""Set the revision and tree to merge from.
213
This sets the other_tree, other_rev_id, other_basis attributes.
215
:param other_revision: The [path, revision] list to merge from.
217
self.other_branch, self.other_tree = _get_tree(other_revision,
190
other_branch, self.other_tree = _get_tree(other_revision,
218
191
self.this_branch)
219
192
if other_revision[1] == -1:
220
self.other_rev_id = self.other_branch.last_revision()
193
self.other_rev_id = other_branch.last_revision()
221
194
if self.other_rev_id is None:
222
raise NoCommits(self.other_branch)
195
raise NoCommits(other_branch)
223
196
self.other_basis = self.other_rev_id
224
197
elif other_revision[1] is not None:
225
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
198
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
226
199
self.other_basis = self.other_rev_id
228
201
self.other_rev_id = None
229
self.other_basis = self.other_branch.last_revision()
202
self.other_basis = other_branch.last_revision()
230
203
if self.other_basis is None:
231
raise NoCommits(self.other_branch)
232
if self.other_branch.base != self.this_branch.base:
233
self.this_branch.fetch(self.other_branch,
234
last_revision=self.other_basis)
236
def set_other_revision(self, revision_id, other_branch):
237
"""Set 'other' based on a branch and revision id
239
:param revision_id: The revision to use for a tree
240
:param other_branch: The branch containing this tree
242
self.other_rev_id = revision_id
243
self.other_branch = other_branch
244
self.this_branch.fetch(other_branch, self.other_rev_id)
245
self.other_tree = self.revision_tree(revision_id)
246
self.other_basis = revision_id
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)
248
208
def find_base(self):
249
209
self.set_base([None, None])
251
211
def set_base(self, base_revision):
252
"""Set the base revision to use for the merge.
254
:param base_revision: A 2-list containing a path and revision number.
256
212
mutter("doing merge() with no base_revision specified")
257
213
if base_revision == [None, None]:
259
pb = ui.ui_factory.nested_progress_bar()
215
pb = bzrlib.ui.ui_factory.nested_progress_bar()
261
217
this_repo = self.this_branch.repository
262
graph = this_repo.get_graph()
263
revisions = [ensure_null(self.this_basis),
264
ensure_null(self.other_basis)]
265
if NULL_REVISION in revisions:
266
self.base_rev_id = NULL_REVISION
268
self.base_rev_id = graph.find_unique_lca(*revisions)
269
if self.base_rev_id == NULL_REVISION:
270
raise UnrelatedBranches()
218
self.base_rev_id = common_ancestor(self.this_basis,
273
223
except NoCommonAncestor:
274
224
raise UnrelatedBranches()
275
self.base_tree = _get_revid_tree_from_tree(self.this_tree,
225
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
278
227
self.base_is_ancestor = True
280
229
base_branch, self.base_tree = _get_tree(base_revision)
307
256
elif self.show_base:
308
257
raise BzrError("Showing base is not supported for this"
309
258
" merge type. %s" % self.merge_type)
310
self.this_tree.lock_tree_write()
311
if self.base_tree is not None:
312
self.base_tree.lock_read()
313
if self.other_tree is not None:
314
self.other_tree.lock_read()
316
merge = self.merge_type(pb=self._pb,
317
change_reporter=self.change_reporter,
319
if self.recurse == 'down':
320
for path, file_id in self.this_tree.iter_references():
321
sub_tree = self.this_tree.get_nested_tree(file_id, path)
322
other_revision = self.other_tree.get_reference_revision(
324
if other_revision == sub_tree.last_revision():
326
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
327
sub_merge.merge_type = self.merge_type
328
relpath = self.this_tree.relpath(path)
329
other_branch = self.other_branch.reference_parent(file_id, relpath)
330
sub_merge.set_other_revision(other_revision, other_branch)
331
base_revision = self.base_tree.get_reference_revision(file_id)
332
sub_merge.base_tree = \
333
sub_tree.branch.repository.revision_tree(base_revision)
337
if self.other_tree is not None:
338
self.other_tree.unlock()
339
if self.base_tree is not None:
340
self.base_tree.unlock()
341
self.this_tree.unlock()
259
merge = self.merge_type(pb=self._pb, **kwargs)
342
260
if len(merge.cooked_conflicts) == 0:
343
261
if not self.ignore_zero:
344
262
note("All changes applied successfully.")
427
345
def __init__(self, working_tree, this_tree, base_tree, other_tree,
428
346
interesting_ids=None, reprocess=False, show_base=False,
429
pb=DummyProgress(), pp=None, change_reporter=None):
347
pb=DummyProgress(), pp=None):
430
348
"""Initialize the merger object and perform the merge."""
431
349
object.__init__(self)
432
350
self.this_tree = working_tree
433
self.this_tree.lock_tree_write()
434
351
self.base_tree = base_tree
435
self.base_tree.lock_read()
436
352
self.other_tree = other_tree
437
self.other_tree.lock_read()
438
353
self._raw_conflicts = []
439
354
self.cooked_conflicts = []
440
355
self.reprocess = reprocess
441
356
self.show_base = show_base
444
self.change_reporter = change_reporter
445
359
if self.pp is None:
446
360
self.pp = ProgressPhase("Merge phase", 3, self.pb)
479
391
results = self.tt.apply()
480
392
self.write_modified(results)
482
working_tree.add_conflicts(self.cooked_conflicts)
394
working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
483
395
except UnsupportedOperation:
487
self.other_tree.unlock()
488
self.base_tree.unlock()
489
self.this_tree.unlock()
402
working_tree.unlock()
494
self.tt.final_kind(self.tt.root)
496
self.tt.cancel_deletion(self.tt.root)
497
if self.tt.final_file_id(self.tt.root) is None:
498
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
500
if self.other_tree.inventory.root is None:
502
other_root_file_id = self.other_tree.inventory.root.file_id
503
other_root = self.tt.trans_id_file_id(other_root_file_id)
504
if other_root == self.tt.root:
507
self.tt.final_kind(other_root)
510
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
511
self.tt.cancel_creation(other_root)
512
self.tt.cancel_versioning(other_root)
514
def reparent_children(self, ie, target):
515
for thing, child in ie.children.iteritems():
516
trans_id = self.tt.trans_id_file_id(child.file_id)
517
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
519
405
def write_modified(self, results):
520
406
modified_hashes = {}
521
407
for path in results.modified_paths:
626
512
"conflict": other_entry}
627
513
trans_id = self.tt.trans_id_file_id(file_id)
628
514
parent_id = winner_entry[parent_id_winner].parent_id
629
if parent_id is not None:
630
parent_trans_id = self.tt.trans_id_file_id(parent_id)
631
self.tt.adjust_path(winner_entry[name_winner].name,
632
parent_trans_id, trans_id)
515
parent_trans_id = self.tt.trans_id_file_id(parent_id)
516
self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
634
519
def merge_contents(self, file_id):
635
520
"""Performa a merge on file_id contents."""
898
784
def __init__(self, working_tree, this_tree, base_tree, other_tree,
899
785
interesting_ids=None, pb=DummyProgress(), pp=None,
900
reprocess=False, change_reporter=None):
901
787
self.this_revision_tree = self._get_revision_tree(this_tree)
902
788
self.other_revision_tree = self._get_revision_tree(other_tree)
903
789
super(WeaveMerger, self).__init__(working_tree, this_tree,
904
790
base_tree, other_tree,
905
791
interesting_ids=interesting_ids,
906
pb=pb, pp=pp, reprocess=reprocess,
907
change_reporter=change_reporter)
792
pb=pb, pp=pp, reprocess=reprocess)
909
794
def _get_revision_tree(self, tree):
910
"""Return a revision tree related to this tree.
795
"""Return a revision tree releated to this tree.
911
796
If the tree is a WorkingTree, the basis will be returned.
913
798
if getattr(tree, 'get_weave', False) is False:
962
847
class Diff3Merger(Merge3Merger):
963
848
"""Three-way merger using external diff3 for text merging"""
965
849
def dump_file(self, temp_dir, name, tree, file_id):
966
850
out_path = pathjoin(temp_dir, name)
967
out_file = open(out_path, "wb")
969
in_file = tree.get_file(file_id)
851
out_file = file(out_path, "wb")
852
in_file = tree.get_file(file_id)
976
857
def text_merge(self, file_id, trans_id):
988
869
status = bzrlib.patch.diff3(new_file, this, base, other)
989
870
if status not in (0, 1):
990
871
raise BzrError("Unhandled diff3 exit code")
991
f = open(new_file, 'rb')
993
self.tt.create_file(f, trans_id)
872
self.tt.create_file(file(new_file, "rb"), trans_id)
997
874
name = self.tt.final_name(trans_id)
998
875
parent_id = self.tt.final_parent(trans_id)
999
876
self._dump_conflicts(name, parent_id, file_id)
1000
self._raw_conflicts.append(('text conflict', trans_id))
877
self._raw_conflicts.append(('text conflict', trans_id))
1002
osutils.rmtree(temp_dir)
1005
882
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1007
merge_type=Merge3Merger,
1008
interesting_ids=None,
884
merge_type=Merge3Merger,
885
interesting_ids=None,
1011
888
other_rev_id=None,
1012
889
interesting_files=None,
1015
change_reporter=None):
1016
892
"""Primary interface for merging.
1018
894
typical use is probably
1020
896
branch.get_revision_tree(base_revision))'
1022
898
if this_tree is None:
1023
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1024
"parameter as of bzrlib version 0.8.")
1025
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1026
pb=pb, change_reporter=change_reporter)
899
warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
900
"bzrlib version 0.8.",
903
this_tree = this_branch.bzrdir.open_workingtree()
904
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1027
906
merger.backup_files = backup_files
1028
907
merger.merge_type = merge_type
1029
908
merger.interesting_ids = interesting_ids
1032
911
assert not interesting_ids, ('Only supply interesting_ids'
1033
912
' or interesting_files')
1034
913
merger._set_interesting_files(interesting_files)
1035
merger.show_base = show_base
914
merger.show_base = show_base
1036
915
merger.reprocess = reprocess
1037
916
merger.other_rev_id = other_rev_id
1038
917
merger.other_basis = other_rev_id
1039
918
return merger.do_merge()
1041
def get_merge_type_registry():
1042
"""Merge type registry is in bzrlib.option to avoid circular imports.
1044
This method provides a sanctioned way to retrieve it.
1046
from bzrlib import option
1047
return option._merge_type_registry
921
merge_types = { "merge3": (Merge3Merger, "Native diff3-style merge"),
922
"diff3": (Diff3Merger, "Merge using external diff3"),
923
'weave': (WeaveMerger, "Weave-based merge")
927
def merge_type_help():
928
templ = '%s%%7s: %%s' % (' '*12)
929
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
930
return '\n'.join(lines)