40
35
WorkingTreeNotRevision,
43
37
from bzrlib.merge3 import Merge3
44
39
from bzrlib.osutils import rename, pathjoin
45
40
from progress import DummyProgress, ProgressPhase
46
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
47
from bzrlib.textfile import check_text_lines
41
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
42
from bzrlib.symbol_versioning import *
48
43
from bzrlib.trace import mutter, warning, note
49
44
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
50
conflict_pass, FinalPaths, create_by_entry,
51
unique_add, ROOT_PARENT)
52
from bzrlib.versionedfile import WeaveMerge
45
conflicts_strings, FinalPaths, create_by_entry,
55
49
# TODO: Report back as changes are merged in
51
def _get_tree(treespec, local_branch=None):
52
location, revno = treespec
53
branch = Branch.open_containing(location)[0]
57
revision = branch.last_revision()
59
revision = branch.get_rev_id(revno)
61
revision = NULL_REVISION
62
return branch, _get_revid_tree(branch, revision, local_branch)
65
def _get_revid_tree(branch, revision, local_branch):
67
base_tree = branch.bzrdir.open_workingtree()
69
if local_branch is not None:
70
if local_branch.base != branch.base:
71
local_branch.fetch(branch, revision)
72
base_tree = local_branch.repository.revision_tree(revision)
74
base_tree = branch.repository.revision_tree(revision)
58
78
def transform_tree(from_tree, to_tree, interesting_ids=None):
59
79
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
63
83
class Merger(object):
64
def __init__(self, this_branch, other_tree=None, base_tree=None,
65
this_tree=None, pb=DummyProgress(), change_reporter=None,
84
def __init__(self, this_branch, other_tree=None, base_tree=None,
85
this_tree=None, pb=DummyProgress()):
67
86
object.__init__(self)
68
87
assert this_tree is not None, "this_tree is required"
69
88
self.this_branch = this_branch
70
self.this_basis = _mod_revision.ensure_null(
71
this_branch.last_revision())
89
self.this_basis = this_branch.last_revision()
72
90
self.this_rev_id = None
73
91
self.this_tree = this_tree
74
92
self.this_revision_tree = None
75
93
self.this_basis_tree = None
76
94
self.other_tree = other_tree
77
self.other_branch = None
78
95
self.base_tree = base_tree
79
96
self.ignore_zero = False
80
97
self.backup_files = False
81
98
self.interesting_ids = None
82
self.interesting_files = None
83
99
self.show_base = False
84
100
self.reprocess = False
87
self.recurse = recurse
88
self.change_reporter = change_reporter
89
self._cached_trees = {}
91
def revision_tree(self, revision_id, branch=None):
92
if revision_id not in self._cached_trees:
94
branch = self.this_branch
96
tree = self.this_tree.revision_tree(revision_id)
97
except errors.NoSuchRevisionInTree:
98
tree = branch.repository.revision_tree(revision_id)
99
self._cached_trees[revision_id] = tree
100
return self._cached_trees[revision_id]
102
def _get_tree(self, treespec, possible_transports=None):
103
from bzrlib import workingtree
104
location, revno = treespec
106
tree = workingtree.WorkingTree.open_containing(location)[0]
107
return tree.branch, tree
108
branch = Branch.open_containing(location, possible_transports)[0]
110
revision_id = branch.last_revision()
112
revision_id = branch.get_rev_id(revno)
113
revision_id = ensure_null(revision_id)
114
return branch, self.revision_tree(revision_id, branch)
105
def revision_tree(self, revision_id):
106
return self.this_branch.repository.revision_tree(revision_id)
116
108
def ensure_revision_trees(self):
117
109
if self.this_revision_tree is None:
118
self.this_basis_tree = self.revision_tree(self.this_basis)
110
self.this_basis_tree = self.this_branch.repository.revision_tree(
119
112
if self.this_basis == self.this_rev_id:
120
113
self.this_revision_tree = self.this_basis_tree
122
115
if self.other_rev_id is None:
123
116
other_basis_tree = self.revision_tree(self.other_basis)
124
changes = other_basis_tree.changes_from(self.other_tree)
117
changes = compare_trees(self.other_tree, other_basis_tree)
125
118
if changes.has_changed():
126
119
raise WorkingTreeNotRevision(self.this_tree)
127
other_rev_id = self.other_basis
120
other_rev_id = other_basis
128
121
self.other_tree = other_basis_tree
130
123
def file_revisions(self, file_id):
141
134
trees = (self.this_basis_tree, self.other_tree)
142
135
return [get_id(tree, file_id) for tree in trees]
144
def check_basis(self, check_clean, require_commits=True):
145
if self.this_basis is None and require_commits is True:
146
raise BzrCommandError("This branch has no commits."
147
" (perhaps you would prefer 'bzr pull')")
137
def check_basis(self, check_clean):
138
if self.this_basis is None:
139
raise BzrCommandError("This branch has no commits")
149
141
self.compare_basis()
150
142
if self.this_basis != self.this_rev_id:
151
143
raise BzrCommandError("Working tree has uncommitted changes.")
153
145
def compare_basis(self):
155
basis_tree = self.revision_tree(self.this_tree.last_revision())
156
except errors.RevisionNotPresent:
157
basis_tree = self.this_tree.basis_tree()
158
changes = self.this_tree.changes_from(basis_tree)
146
changes = compare_trees(self.this_tree,
147
self.this_tree.basis_tree(), False)
159
148
if not changes.has_changed():
160
149
self.this_rev_id = self.this_basis
162
151
def set_interesting_files(self, file_list):
163
self.interesting_files = file_list
153
self._set_interesting_files(file_list)
154
except NotVersionedError, e:
155
raise BzrCommandError("%s is not a source file in any"
158
def _set_interesting_files(self, file_list):
159
"""Set the list of interesting ids from a list of files."""
160
if file_list is None:
161
self.interesting_ids = None
164
interesting_ids = set()
165
for path in file_list:
167
for tree in (self.this_tree, self.base_tree, self.other_tree):
168
file_id = tree.inventory.path2id(path)
169
if file_id is not None:
170
interesting_ids.add(file_id)
173
raise NotVersionedError(path=path)
174
self.interesting_ids = interesting_ids
165
176
def set_pending(self):
166
if not self.base_is_ancestor or not self.base_is_other_ancestor:
170
def _add_parent(self):
171
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
172
new_parent_trees = []
173
for revision_id in new_parents:
175
tree = self.revision_tree(revision_id)
176
except errors.RevisionNotPresent:
180
new_parent_trees.append((revision_id, tree))
182
self.this_tree.set_parent_trees(new_parent_trees,
183
allow_leftmost_as_ghost=True)
185
for _revision_id, tree in new_parent_trees:
189
def set_other(self, other_revision, possible_transports=None):
190
"""Set the revision and tree to merge from.
192
This sets the other_tree, other_rev_id, other_basis attributes.
194
:param other_revision: The [path, revision] list to merge from.
196
self.other_branch, self.other_tree = self._get_tree(other_revision,
177
if not self.base_is_ancestor:
179
if self.other_rev_id is None:
181
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
182
if self.other_rev_id in ancestry:
184
self.this_tree.add_pending_merge(self.other_rev_id)
186
def set_other(self, other_revision):
187
other_branch, self.other_tree = _get_tree(other_revision,
198
189
if other_revision[1] == -1:
199
self.other_rev_id = _mod_revision.ensure_null(
200
self.other_branch.last_revision())
201
if _mod_revision.is_null(self.other_rev_id):
202
raise NoCommits(self.other_branch)
190
self.other_rev_id = other_branch.last_revision()
191
if self.other_rev_id is None:
192
raise NoCommits(other_branch)
203
193
self.other_basis = self.other_rev_id
204
194
elif other_revision[1] is not None:
205
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])
206
196
self.other_basis = self.other_rev_id
208
198
self.other_rev_id = None
209
self.other_basis = self.other_branch.last_revision()
199
self.other_basis = other_branch.last_revision()
210
200
if self.other_basis is None:
211
raise NoCommits(self.other_branch)
212
if self.other_rev_id is not None:
213
self._cached_trees[self.other_rev_id] = self.other_tree
214
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
216
def set_other_revision(self, revision_id, other_branch):
217
"""Set 'other' based on a branch and revision id
219
:param revision_id: The revision to use for a tree
220
:param other_branch: The branch containing this tree
222
self.other_rev_id = revision_id
223
self.other_branch = other_branch
224
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
225
self.other_tree = self.revision_tree(revision_id)
226
self.other_basis = revision_id
228
def _maybe_fetch(self, source, target, revision_id):
229
if (source.repository.bzrdir.root_transport.base !=
230
target.repository.bzrdir.root_transport.base):
231
target.fetch(source, revision_id)
234
this_repo = self.this_branch.repository
235
graph = this_repo.get_graph()
236
revisions = [ensure_null(self.this_basis),
237
ensure_null(self.other_basis)]
238
if NULL_REVISION in revisions:
239
self.base_rev_id = NULL_REVISION
241
self.base_rev_id = graph.find_unique_lca(*revisions)
242
if self.base_rev_id == NULL_REVISION:
243
raise UnrelatedBranches()
244
self.base_tree = self.revision_tree(self.base_rev_id)
245
self.base_is_ancestor = True
246
self.base_is_other_ancestor = True
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)
248
205
def set_base(self, base_revision):
249
"""Set the base revision to use for the merge.
251
:param base_revision: A 2-list containing a path and revision number.
253
206
mutter("doing merge() with no base_revision specified")
254
207
if base_revision == [None, None]:
209
pb = bzrlib.ui.ui_factory.nested_progress_bar()
211
this_repo = self.this_branch.repository
212
self.base_rev_id = common_ancestor(self.this_basis,
217
except NoCommonAncestor:
218
raise UnrelatedBranches()
219
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
221
self.base_is_ancestor = True
257
base_branch, self.base_tree = self._get_tree(base_revision)
223
base_branch, self.base_tree = _get_tree(base_revision)
258
224
if base_revision[1] == -1:
259
225
self.base_rev_id = base_branch.last_revision()
260
226
elif base_revision[1] is None:
261
self.base_rev_id = _mod_revision.NULL_REVISION
227
self.base_rev_id = None
263
self.base_rev_id = _mod_revision.ensure_null(
264
base_branch.get_rev_id(base_revision[1]))
265
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
229
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
230
if self.this_branch.base != base_branch.base:
231
self.this_branch.fetch(base_branch)
266
232
self.base_is_ancestor = is_ancestor(self.this_basis,
267
233
self.base_rev_id,
268
234
self.this_branch)
269
self.base_is_other_ancestor = is_ancestor(self.other_basis,
273
236
def do_merge(self):
274
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
275
'other_tree': self.other_tree,
237
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
238
'other_tree': self.other_tree,
276
239
'interesting_ids': self.interesting_ids,
277
'interesting_files': self.interesting_files,
279
241
if self.merge_type.requires_base:
280
242
kwargs['base_tree'] = self.base_tree
281
243
if self.merge_type.supports_reprocess:
282
244
kwargs['reprocess'] = self.reprocess
283
245
elif self.reprocess:
284
raise BzrError("Conflict reduction is not supported for merge"
285
" type %s." % self.merge_type)
246
raise BzrError("Reprocess is not supported for this merge"
247
" type. %s" % merge_type)
286
248
if self.merge_type.supports_show_base:
287
249
kwargs['show_base'] = self.show_base
288
250
elif self.show_base:
289
251
raise BzrError("Showing base is not supported for this"
290
252
" merge type. %s" % self.merge_type)
291
self.this_tree.lock_tree_write()
292
if self.base_tree is not None:
293
self.base_tree.lock_read()
294
if self.other_tree is not None:
295
self.other_tree.lock_read()
297
merge = self.merge_type(pb=self._pb,
298
change_reporter=self.change_reporter,
300
if self.recurse == 'down':
301
for path, file_id in self.this_tree.iter_references():
302
sub_tree = self.this_tree.get_nested_tree(file_id, path)
303
other_revision = self.other_tree.get_reference_revision(
305
if other_revision == sub_tree.last_revision():
307
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
308
sub_merge.merge_type = self.merge_type
309
relpath = self.this_tree.relpath(path)
310
other_branch = self.other_branch.reference_parent(file_id, relpath)
311
sub_merge.set_other_revision(other_revision, other_branch)
312
base_revision = self.base_tree.get_reference_revision(file_id)
313
sub_merge.base_tree = \
314
sub_tree.branch.repository.revision_tree(base_revision)
318
if self.other_tree is not None:
319
self.other_tree.unlock()
320
if self.base_tree is not None:
321
self.base_tree.unlock()
322
self.this_tree.unlock()
253
merge = self.merge_type(pb=self._pb, **kwargs)
323
254
if len(merge.cooked_conflicts) == 0:
324
255
if not self.ignore_zero:
325
256
note("All changes applied successfully.")
329
260
return len(merge.cooked_conflicts)
262
def regen_inventory(self, new_entries):
263
old_entries = self.this_tree.read_working_inventory()
267
for path, file_id in new_entries:
270
new_entries_map[file_id] = path
272
def id2path(file_id):
273
path = new_entries_map.get(file_id)
276
entry = old_entries[file_id]
277
if entry.parent_id is None:
279
return pathjoin(id2path(entry.parent_id), entry.name)
281
for file_id in old_entries:
282
entry = old_entries[file_id]
283
path = id2path(file_id)
284
new_inventory[file_id] = (path, file_id, entry.parent_id,
286
by_path[path] = file_id
291
for path, file_id in new_entries:
293
del new_inventory[file_id]
296
new_path_list.append((path, file_id))
297
if file_id not in old_entries:
299
# Ensure no file is added before its parent
301
for path, file_id in new_path_list:
305
parent = by_path[os.path.dirname(path)]
306
abspath = pathjoin(self.this_tree.basedir, path)
307
kind = bzrlib.osutils.file_kind(abspath)
308
new_inventory[file_id] = (path, file_id, parent, kind)
309
by_path[path] = file_id
311
# Get a list in insertion order
312
new_inventory_list = new_inventory.values()
313
mutter ("""Inventory regeneration:
314
old length: %i insertions: %i deletions: %i new_length: %i"""\
315
% (len(old_entries), insertions, deletions,
316
len(new_inventory_list)))
317
assert len(new_inventory_list) == len(old_entries) + insertions\
319
new_inventory_list.sort()
320
return new_inventory_list
332
323
class Merge3Merger(object):
333
324
"""Three-way merger that uses the merge3 text merger"""
335
326
supports_reprocess = True
336
327
supports_show_base = True
337
328
history_based = False
338
winner_idx = {"this": 2, "other": 1, "conflict": 1}
340
330
def __init__(self, working_tree, this_tree, base_tree, other_tree,
341
331
interesting_ids=None, reprocess=False, show_base=False,
342
pb=DummyProgress(), pp=None, change_reporter=None,
343
interesting_files=None):
344
"""Initialize the merger object and perform the merge.
346
:param working_tree: The working tree to apply the merge to
347
:param this_tree: The local tree in the merge operation
348
:param base_tree: The common tree in the merge operation
349
:param other_tree: The other other tree to merge changes from
350
:param interesting_ids: The file_ids of files that should be
351
participate in the merge. May not be combined with
353
:param: reprocess If True, perform conflict-reduction processing.
354
:param show_base: If True, show the base revision in text conflicts.
355
(incompatible with reprocess)
356
:param pb: A Progress bar
357
:param pp: A ProgressPhase object
358
:param change_reporter: An object that should report changes made
359
:param interesting_files: The tree-relative paths of files that should
360
participate in the merge. If these paths refer to directories,
361
the contents of those directories will also be included. May not
362
be combined with interesting_ids. If neither interesting_files nor
363
interesting_ids is specified, all files may participate in the
332
pb=DummyProgress(), pp=None):
333
"""Initialize the merger object and perform the merge."""
366
334
object.__init__(self)
367
if interesting_files is not None:
368
assert interesting_ids is None
369
self.interesting_ids = interesting_ids
370
self.interesting_files = interesting_files
371
335
self.this_tree = working_tree
372
self.this_tree.lock_tree_write()
373
336
self.base_tree = base_tree
374
self.base_tree.lock_read()
375
337
self.other_tree = other_tree
376
self.other_tree.lock_read()
377
338
self._raw_conflicts = []
378
339
self.cooked_conflicts = []
379
340
self.reprocess = reprocess
380
341
self.show_base = show_base
383
self.change_reporter = change_reporter
384
344
if self.pp is None:
385
345
self.pp = ProgressPhase("Merge phase", 3, self.pb)
347
if interesting_ids is not None:
348
all_ids = interesting_ids
350
all_ids = set(base_tree)
351
all_ids.update(other_tree)
352
working_tree.lock_write()
387
353
self.tt = TreeTransform(working_tree, self.pb)
389
355
self.pp.next_phase()
390
entries = self._entries3()
391
child_pb = ui.ui_factory.nested_progress_bar()
356
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
393
for num, (file_id, changed, parents3, names3,
394
executable3) in enumerate(entries):
395
child_pb.update('Preparing file merge', num, len(entries))
396
self._merge_names(file_id, parents3, names3)
398
file_status = self.merge_contents(file_id)
400
file_status = 'unmodified'
401
self._merge_executable(file_id,
402
executable3, file_status)
358
for num, file_id in enumerate(all_ids):
359
child_pb.update('Preparing file merge', num, len(all_ids))
360
self.merge_names(file_id)
361
file_status = self.merge_contents(file_id)
362
self.merge_executable(file_id, file_status)
404
364
child_pb.finished()
406
366
self.pp.next_phase()
407
child_pb = ui.ui_factory.nested_progress_bar()
367
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
409
fs_conflicts = resolve_conflicts(self.tt, child_pb,
410
lambda t, c: conflict_pass(t, c, self.other_tree))
369
fs_conflicts = resolve_conflicts(self.tt, child_pb)
412
371
child_pb.finished()
413
if change_reporter is not None:
414
from bzrlib import delta
415
delta.report_changes(self.tt._iter_changes(), change_reporter)
416
372
self.cook_conflicts(fs_conflicts)
417
for conflict in self.cooked_conflicts:
373
for line in conflicts_strings(self.cooked_conflicts):
419
375
self.pp.next_phase()
420
results = self.tt.apply(no_conflicts=True)
376
results = self.tt.apply()
421
377
self.write_modified(results)
423
working_tree.add_conflicts(self.cooked_conflicts)
424
except UnsupportedOperation:
428
self.other_tree.unlock()
429
self.base_tree.unlock()
430
self.this_tree.unlock()
383
working_tree.unlock()
434
"""Gather data about files modified between three trees.
436
Return a list of tuples of file_id, changed, parents3, names3,
437
executable3. changed is a boolean indicating whether the file contents
438
or kind were changed. parents3 is a tuple of parent ids for base,
439
other and this. names3 is a tuple of names for base, other and this.
440
executable3 is a tuple of execute-bit values for base, other and this.
443
iterator = self.other_tree._iter_changes(self.base_tree,
444
include_unchanged=True, specific_files=self.interesting_files,
445
extra_trees=[self.this_tree])
446
for (file_id, paths, changed, versioned, parents, names, kind,
447
executable) in iterator:
448
if (self.interesting_ids is not None and
449
file_id not in self.interesting_ids):
451
if file_id in self.this_tree.inventory:
452
entry = self.this_tree.inventory[file_id]
453
this_name = entry.name
454
this_parent = entry.parent_id
455
this_executable = entry.executable
459
this_executable = None
460
parents3 = parents + (this_parent,)
461
names3 = names + (this_name,)
462
executable3 = executable + (this_executable,)
463
result.append((file_id, changed, parents3, names3, executable3))
468
self.tt.final_kind(self.tt.root)
470
self.tt.cancel_deletion(self.tt.root)
471
if self.tt.final_file_id(self.tt.root) is None:
472
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
474
if self.other_tree.inventory.root is None:
476
other_root_file_id = self.other_tree.inventory.root.file_id
477
other_root = self.tt.trans_id_file_id(other_root_file_id)
478
if other_root == self.tt.root:
481
self.tt.final_kind(other_root)
484
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
485
self.tt.cancel_creation(other_root)
486
self.tt.cancel_versioning(other_root)
488
def reparent_children(self, ie, target):
489
for thing, child in ie.children.iteritems():
490
trans_id = self.tt.trans_id_file_id(child.file_id)
491
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
493
386
def write_modified(self, results):
494
387
modified_hashes = {}
495
388
for path in results.modified_paths:
881
735
this_name = other_name = self.tt.final_name(trans_id)
882
736
other_path = fp.get_path(trans_id)
883
if this_parent is not None and this_name is not None:
737
if this_parent is not None:
884
738
this_parent_path = \
885
739
fp.get_path(self.tt.trans_id_file_id(this_parent))
886
740
this_path = pathjoin(this_parent_path, this_name)
888
742
this_path = "<deleted>"
889
743
file_id = self.tt.final_file_id(trans_id)
890
c = Conflict.factory('path conflict', path=this_path,
891
conflict_path=other_path, file_id=file_id)
892
self.cooked_conflicts.append(c)
893
self.cooked_conflicts.sort(key=Conflict.sort_key)
744
self.cooked_conflicts.append(('path conflict', file_id, this_path,
896
748
class WeaveMerger(Merge3Merger):
897
749
"""Three-way tree merger, text weave merger."""
898
supports_reprocess = True
750
supports_reprocess = False
899
751
supports_show_base = False
901
753
def __init__(self, working_tree, this_tree, base_tree, other_tree,
902
interesting_ids=None, pb=DummyProgress(), pp=None,
903
reprocess=False, change_reporter=None,
904
interesting_files=None):
754
interesting_ids=None, pb=DummyProgress(), pp=None):
905
755
self.this_revision_tree = self._get_revision_tree(this_tree)
906
756
self.other_revision_tree = self._get_revision_tree(other_tree)
907
757
super(WeaveMerger, self).__init__(working_tree, this_tree,
908
758
base_tree, other_tree,
909
759
interesting_ids=interesting_ids,
910
pb=pb, pp=pp, reprocess=reprocess,
911
change_reporter=change_reporter)
913
762
def _get_revision_tree(self, tree):
914
"""Return a revision tree related to this tree.
763
"""Return a revision tree releated to this tree.
915
764
If the tree is a WorkingTree, the basis will be returned.
917
766
if getattr(tree, 'get_weave', False) is False: