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
63
revision = branch.last_revision()
62
65
revision = branch.get_rev_id(revno)
86
89
class Merger(object):
87
def __init__(self, this_branch, other_tree=None, base_tree=None,
88
this_tree=None, pb=DummyProgress()):
90
def __init__(self, this_branch, other_tree=None, base_tree=None,
91
this_tree=None, pb=DummyProgress(), change_reporter=None,
89
93
object.__init__(self)
90
94
assert this_tree is not None, "this_tree is required"
91
95
self.this_branch = this_branch
95
99
self.this_revision_tree = None
96
100
self.this_basis_tree = None
97
101
self.other_tree = other_tree
102
self.other_branch = None
98
103
self.base_tree = base_tree
99
104
self.ignore_zero = False
100
105
self.backup_files = False
101
106
self.interesting_ids = None
102
107
self.show_base = False
103
108
self.reprocess = False
111
self.recurse = recurse
112
self.change_reporter = change_reporter
108
114
def revision_tree(self, revision_id):
109
115
return self.this_branch.repository.revision_tree(revision_id)
118
124
if self.other_rev_id is None:
119
125
other_basis_tree = self.revision_tree(self.other_basis)
120
changes = compare_trees(self.other_tree, other_basis_tree)
126
changes = other_basis_tree.changes_from(self.other_tree)
121
127
if changes.has_changed():
122
128
raise WorkingTreeNotRevision(self.this_tree)
123
other_rev_id = other_basis
129
other_rev_id = self.other_basis
124
130
self.other_tree = other_basis_tree
126
132
def file_revisions(self, file_id):
137
143
trees = (self.this_basis_tree, self.other_tree)
138
144
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")
146
def check_basis(self, check_clean, require_commits=True):
147
if self.this_basis is None and require_commits is True:
148
raise BzrCommandError("This branch has no commits."
149
" (perhaps you would prefer 'bzr pull')")
144
151
self.compare_basis()
145
152
if self.this_basis != self.this_rev_id:
146
153
raise BzrCommandError("Working tree has uncommitted changes.")
148
155
def compare_basis(self):
149
changes = compare_trees(self.this_tree,
150
self.this_tree.basis_tree(), False)
156
changes = self.this_tree.changes_from(self.this_tree.basis_tree())
151
157
if not changes.has_changed():
152
158
self.this_rev_id = self.this_basis
167
173
interesting_ids = set()
168
174
for path in file_list:
176
# TODO: jam 20070226 The trees are not locked at this time,
177
# wouldn't it make merge faster if it locks everything in the
178
# beginning? It locks at do_merge time, but this happens
170
180
for tree in (self.this_tree, self.base_tree, self.other_tree):
171
file_id = tree.inventory.path2id(path)
181
file_id = tree.path2id(path)
172
182
if file_id is not None:
173
183
interesting_ids.add(file_id)
184
194
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
185
195
if self.other_rev_id in ancestry:
187
self.this_tree.add_pending_merge(self.other_rev_id)
197
self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
189
199
def set_other(self, other_revision):
190
other_branch, self.other_tree = _get_tree(other_revision,
200
"""Set the revision and tree to merge from.
202
This sets the other_tree, other_rev_id, other_basis attributes.
204
:param other_revision: The [path, revision] list to merge from.
206
self.other_branch, self.other_tree = _get_tree(other_revision,
191
207
self.this_branch)
192
208
if other_revision[1] == -1:
193
self.other_rev_id = other_branch.last_revision()
209
self.other_rev_id = self.other_branch.last_revision()
194
210
if self.other_rev_id is None:
195
raise NoCommits(other_branch)
211
raise NoCommits(self.other_branch)
196
212
self.other_basis = self.other_rev_id
197
213
elif other_revision[1] is not None:
198
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
214
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
199
215
self.other_basis = self.other_rev_id
201
217
self.other_rev_id = None
202
self.other_basis = other_branch.last_revision()
218
self.other_basis = self.other_branch.last_revision()
203
219
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)
220
raise NoCommits(self.other_branch)
221
if self.other_branch.base != self.this_branch.base:
222
self.this_branch.fetch(self.other_branch,
223
last_revision=self.other_basis)
225
def set_other_revision(self, revision_id, other_branch):
226
"""Set 'other' based on a branch and revision id
228
:param revision_id: The revision to use for a tree
229
:param other_branch: The branch containing this tree
231
self.other_rev_id = revision_id
232
self.other_branch = other_branch
233
self.this_branch.fetch(other_branch, self.other_rev_id)
234
self.other_tree = self.revision_tree(revision_id)
235
self.other_basis = revision_id
238
self.set_base([None, None])
208
240
def set_base(self, base_revision):
241
"""Set the base revision to use for the merge.
243
:param base_revision: A 2-list containing a path and revision number.
209
245
mutter("doing merge() with no base_revision specified")
210
246
if base_revision == [None, None]:
212
pb = bzrlib.ui.ui_factory.nested_progress_bar()
248
pb = ui.ui_factory.nested_progress_bar()
214
250
this_repo = self.this_branch.repository
215
251
self.base_rev_id = common_ancestor(self.this_basis,
237
273
self.this_branch)
239
275
def do_merge(self):
240
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
241
'other_tree': self.other_tree,
276
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
277
'other_tree': self.other_tree,
242
278
'interesting_ids': self.interesting_ids,
244
280
if self.merge_type.requires_base:
253
289
elif self.show_base:
254
290
raise BzrError("Showing base is not supported for this"
255
291
" merge type. %s" % self.merge_type)
256
merge = self.merge_type(pb=self._pb, **kwargs)
292
self.this_tree.lock_tree_write()
293
if self.base_tree is not None:
294
self.base_tree.lock_read()
295
if self.other_tree is not None:
296
self.other_tree.lock_read()
298
merge = self.merge_type(pb=self._pb,
299
change_reporter=self.change_reporter,
301
if self.recurse == 'down':
302
for path, file_id in self.this_tree.iter_references():
303
sub_tree = self.this_tree.get_nested_tree(file_id, path)
304
other_revision = self.other_tree.get_reference_revision(
306
if other_revision == sub_tree.last_revision():
308
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
309
sub_merge.merge_type = self.merge_type
310
relpath = self.this_tree.relpath(path)
311
other_branch = self.other_branch.reference_parent(file_id, relpath)
312
sub_merge.set_other_revision(other_revision, other_branch)
313
base_revision = self.base_tree.get_reference_revision(file_id)
314
sub_merge.base_tree = \
315
sub_tree.branch.repository.revision_tree(base_revision)
319
if self.other_tree is not None:
320
self.other_tree.unlock()
321
if self.base_tree is not None:
322
self.base_tree.unlock()
323
self.this_tree.unlock()
257
324
if len(merge.cooked_conflicts) == 0:
258
325
if not self.ignore_zero:
259
326
note("All changes applied successfully.")
342
409
def __init__(self, working_tree, this_tree, base_tree, other_tree,
343
410
interesting_ids=None, reprocess=False, show_base=False,
344
pb=DummyProgress(), pp=None):
411
pb=DummyProgress(), pp=None, change_reporter=None):
345
412
"""Initialize the merger object and perform the merge."""
346
413
object.__init__(self)
347
414
self.this_tree = working_tree
415
self.this_tree.lock_tree_write()
348
416
self.base_tree = base_tree
417
self.base_tree.lock_read()
349
418
self.other_tree = other_tree
419
self.other_tree.lock_read()
350
420
self._raw_conflicts = []
351
421
self.cooked_conflicts = []
352
422
self.reprocess = reprocess
353
423
self.show_base = show_base
426
self.change_reporter = change_reporter
356
427
if self.pp is None:
357
428
self.pp = ProgressPhase("Merge phase", 3, self.pb)
362
433
all_ids = set(base_tree)
363
434
all_ids.update(other_tree)
364
working_tree.lock_write()
365
435
self.tt = TreeTransform(working_tree, self.pb)
367
437
self.pp.next_phase()
368
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
438
child_pb = ui.ui_factory.nested_progress_bar()
370
440
for num, file_id in enumerate(all_ids):
371
441
child_pb.update('Preparing file merge', num, len(all_ids))
374
444
self.merge_executable(file_id, file_status)
376
446
child_pb.finished()
378
448
self.pp.next_phase()
379
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
449
child_pb = ui.ui_factory.nested_progress_bar()
381
451
fs_conflicts = resolve_conflicts(self.tt, child_pb)
383
453
child_pb.finished()
454
if change_reporter is not None:
455
from bzrlib import delta
456
delta.report_changes(self.tt._iter_changes(), change_reporter)
384
457
self.cook_conflicts(fs_conflicts)
385
458
for conflict in self.cooked_conflicts:
386
459
warning(conflict)
388
461
results = self.tt.apply()
389
462
self.write_modified(results)
391
working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
464
working_tree.add_conflicts(self.cooked_conflicts)
392
465
except UnsupportedOperation:
399
working_tree.unlock()
469
self.other_tree.unlock()
470
self.base_tree.unlock()
471
self.this_tree.unlock()
476
self.tt.final_kind(self.tt.root)
478
self.tt.cancel_deletion(self.tt.root)
479
if self.tt.final_file_id(self.tt.root) is None:
480
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
482
if self.other_tree.inventory.root is None:
484
other_root_file_id = self.other_tree.inventory.root.file_id
485
other_root = self.tt.trans_id_file_id(other_root_file_id)
486
if other_root == self.tt.root:
489
self.tt.final_kind(other_root)
492
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
493
self.tt.cancel_creation(other_root)
494
self.tt.cancel_versioning(other_root)
496
def reparent_children(self, ie, target):
497
for thing, child in ie.children.iteritems():
498
trans_id = self.tt.trans_id_file_id(child.file_id)
499
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
402
501
def write_modified(self, results):
403
502
modified_hashes = {}
404
503
for path in results.modified_paths:
509
608
"conflict": other_entry}
510
609
trans_id = self.tt.trans_id_file_id(file_id)
511
610
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,
611
if parent_id is not None:
612
parent_trans_id = self.tt.trans_id_file_id(parent_id)
613
self.tt.adjust_path(winner_entry[name_winner].name,
614
parent_trans_id, trans_id)
516
616
def merge_contents(self, file_id):
517
617
"""Performa a merge on file_id contents."""
535
633
parent_id = self.tt.final_parent(trans_id)
536
634
if file_id in self.this_tree.inventory:
537
635
self.tt.unversion_file(trans_id)
538
self.tt.delete_contents(trans_id)
636
if file_id in self.this_tree:
637
self.tt.delete_contents(trans_id)
539
638
file_group = self._dump_conflicts(name, parent_id, file_id,
540
639
set_version=True)
541
640
self._raw_conflicts.append(('contents conflict', file_group))
781
880
def __init__(self, working_tree, this_tree, base_tree, other_tree,
782
881
interesting_ids=None, pb=DummyProgress(), pp=None,
882
reprocess=False, change_reporter=None):
784
883
self.this_revision_tree = self._get_revision_tree(this_tree)
785
884
self.other_revision_tree = self._get_revision_tree(other_tree)
786
885
super(WeaveMerger, self).__init__(working_tree, this_tree,
787
886
base_tree, other_tree,
788
887
interesting_ids=interesting_ids,
789
pb=pb, pp=pp, reprocess=reprocess)
888
pb=pb, pp=pp, reprocess=reprocess,
889
change_reporter=change_reporter)
791
891
def _get_revision_tree(self, tree):
792
"""Return a revision tree releated to this tree.
892
"""Return a revision tree related to this tree.
793
893
If the tree is a WorkingTree, the basis will be returned.
795
895
if getattr(tree, 'get_weave', False) is False:
844
944
class Diff3Merger(Merge3Merger):
845
945
"""Three-way merger using external diff3 for text merging"""
846
947
def dump_file(self, temp_dir, name, tree, file_id):
847
948
out_path = pathjoin(temp_dir, name)
848
out_file = file(out_path, "wb")
849
in_file = tree.get_file(file_id)
949
out_file = open(out_path, "wb")
951
in_file = tree.get_file(file_id)
854
958
def text_merge(self, file_id, trans_id):
866
970
status = bzrlib.patch.diff3(new_file, this, base, other)
867
971
if status not in (0, 1):
868
972
raise BzrError("Unhandled diff3 exit code")
869
self.tt.create_file(file(new_file, "rb"), trans_id)
973
f = open(new_file, 'rb')
975
self.tt.create_file(f, trans_id)
871
979
name = self.tt.final_name(trans_id)
872
980
parent_id = self.tt.final_parent(trans_id)
873
981
self._dump_conflicts(name, parent_id, file_id)
874
self._raw_conflicts.append(('text conflict', trans_id))
982
self._raw_conflicts.append(('text conflict', trans_id))
984
osutils.rmtree(temp_dir)
879
987
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
881
merge_type=Merge3Merger,
882
interesting_ids=None,
989
merge_type=Merge3Merger,
990
interesting_ids=None,
885
993
other_rev_id=None,
886
994
interesting_files=None,
997
change_reporter=None):
889
998
"""Primary interface for merging.
891
1000
typical use is probably
893
1002
branch.get_revision_tree(base_revision))'
895
1004
if this_tree is None:
896
warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
1005
warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
897
1006
"bzrlib version 0.8.",
898
1007
DeprecationWarning,
900
1009
this_tree = this_branch.bzrdir.open_workingtree()
901
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1010
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1011
pb=pb, change_reporter=change_reporter)
903
1012
merger.backup_files = backup_files
904
1013
merger.merge_type = merge_type
905
1014
merger.interesting_ids = interesting_ids
908
1017
assert not interesting_ids, ('Only supply interesting_ids'
909
1018
' or interesting_files')
910
1019
merger._set_interesting_files(interesting_files)
911
merger.show_base = show_base
1020
merger.show_base = show_base
912
1021
merger.reprocess = reprocess
913
1022
merger.other_rev_id = other_rev_id
914
1023
merger.other_basis = other_rev_id
915
1024
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)
1026
def get_merge_type_registry():
1027
"""Merge type registry is in bzrlib.option to avoid circular imports.
1029
This method provides a sanctioned way to retrieve it.
1031
from bzrlib import option
1032
return option._merge_type_registry