20
from shutil import rmtree
21
from tempfile import mkdtemp
24
import bzrlib.revision
25
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
26
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
27
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
28
24
from bzrlib.branch import Branch
29
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
30
from bzrlib.errors import NoCommits
25
from bzrlib.conflicts import ConflictList
31
26
from bzrlib.delta import compare_trees
27
from bzrlib.errors import (BzrCommandError,
37
WorkingTreeNotRevision,
39
from bzrlib.merge3 import Merge3
41
from bzrlib.osutils import rename, pathjoin
42
from progress import DummyProgress, ProgressPhase
43
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
44
from bzrlib.symbol_versioning import *
32
45
from bzrlib.trace import mutter, warning, note
33
from bzrlib.fetch import greedy_fetch, fetch
34
from bzrlib.revision import is_ancestor
35
from bzrlib.osutils import rename
36
from bzrlib.revision import common_ancestor, MultipleRevisionSources
37
from bzrlib.errors import NoSuchRevision
39
# TODO: build_working_dir can be built on something simpler than merge()
41
# FIXME: merge() parameters seem oriented towards the command line
42
# NOTABUG: merge is a helper for commandline functions. merge_inner is the
43
# the core functionality.
45
# comments from abentley on irc: merge happens in two stages, each
46
# of which generates a changeset object
48
# stage 1: generate OLD->OTHER,
49
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
51
class MergeConflictHandler(ExceptionConflictHandler):
52
"""Handle conflicts encountered while merging.
54
This subclasses ExceptionConflictHandler, so that any types of
55
conflict that are not explicitly handled cause an exception and
58
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
59
ExceptionConflictHandler.__init__(self)
61
self.ignore_zero = ignore_zero
62
self.this_tree = this_tree
63
self.base_tree = base_tree
64
self.other_tree = other_tree
66
def copy(self, source, dest):
67
"""Copy the text and mode of a file
68
:param source: The path of the file to copy
69
:param dest: The distination file to create
71
s_file = file(source, "rb")
72
d_file = file(dest, "wb")
75
os.chmod(dest, 0777 & os.stat(source).st_mode)
77
def dump(self, lines, dest):
78
"""Copy the text and mode of a file
79
:param source: The path of the file to copy
80
:param dest: The distination file to create
82
d_file = file(dest, "wb")
86
def add_suffix(self, name, suffix, last_new_name=None):
87
"""Rename a file to append a suffix. If the new name exists, the
88
suffix is added repeatedly until a non-existant name is found
90
:param name: The path of the file
91
:param suffix: The suffix to append
92
:param last_new_name: (used for recursive calls) the last name tried
94
if last_new_name is None:
96
new_name = last_new_name+suffix
98
rename(name, new_name)
101
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
103
return self.add_suffix(name, suffix, last_new_name=new_name)
105
def conflict(self, text):
110
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
112
Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER. The
113
main file will be a version with diff3 conflicts.
114
:param new_file: Path to the output file with diff3 markers
115
:param this_path: Path to the file text for the THIS tree
116
:param base_path: Path to the file text for the BASE tree
117
:param other_path: Path to the file text for the OTHER tree
119
self.add_suffix(this_path, ".THIS")
120
self.dump(base_lines, this_path+".BASE")
121
self.dump(other_lines, this_path+".OTHER")
122
rename(new_file, this_path)
123
self.conflict("Diff3 conflict encountered in %s" % this_path)
125
def new_contents_conflict(self, filename, other_contents):
126
"""Conflicting contents for newly added file."""
127
other.contents.apply(filename + ".OTHER")
128
self.conflict("Conflict in newly added file %s" % filename)
131
def target_exists(self, entry, target, old_path):
132
"""Handle the case when the target file or dir exists"""
133
moved_path = self.add_suffix(target, ".moved")
134
self.conflict("Moved existing %s to %s" % (target, moved_path))
136
def rmdir_non_empty(self, filename):
137
"""Handle the case where the dir to be removed still has contents"""
138
self.conflict("Directory %s not removed because it is not empty"\
142
def rem_contents_conflict(self, filename, this_contents, base_contents):
143
base_contents(filename+".BASE", self, False)
144
this_contents(filename+".THIS", self, False)
145
return ReplaceContents(this_contents, None)
147
def rem_contents_conflict(self, filename, this_contents, base_contents):
148
base_contents(filename+".BASE", self, False)
149
this_contents(filename+".THIS", self, False)
150
self.conflict("Other branch deleted locally modified file %s" %
152
return ReplaceContents(this_contents, None)
154
def abs_this_path(self, file_id):
155
"""Return the absolute path for a file_id in the this tree."""
156
return self.this_tree.id2abspath(file_id)
158
def add_missing_parents(self, file_id, tree):
159
"""If some of the parents for file_id are missing, add them."""
160
entry = tree.inventory[file_id]
161
if entry.parent_id not in self.this_tree:
162
return self.create_all_missing(entry.parent_id, tree)
164
return self.abs_this_path(entry.parent_id)
166
def create_all_missing(self, file_id, tree):
167
"""Add contents for a file_id and all its parents to a tree."""
168
entry = tree.inventory[file_id]
169
if entry.parent_id is not None and entry.parent_id not in self.this_tree:
170
abspath = self.create_all_missing(entry.parent_id, tree)
172
abspath = self.abs_this_path(entry.parent_id)
173
entry_path = os.path.join(abspath, entry.name)
174
if not os.path.isdir(entry_path):
175
self.create(file_id, entry_path, tree)
178
def create(self, file_id, path, tree, reverse=False):
179
"""Uses tree data to create a filesystem object for the file_id"""
180
from changeset import get_contents
181
get_contents(tree, file_id)(path, self, reverse)
183
def missing_for_merge(self, file_id, other_path):
184
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
185
self.conflict("Other branch modified locally deleted file %s" %
187
parent_dir = self.add_missing_parents(file_id, self.other_tree)
188
stem = os.path.join(parent_dir, os.path.basename(other_path))
189
self.create(file_id, stem+".OTHER", self.other_tree)
190
self.create(file_id, stem+".BASE", self.base_tree)
192
def threeway_contents_conflict(filename, this_contents, base_contents,
194
self.conflict("Three-way conflict merging %s" % filename)
197
if not self.ignore_zero:
198
note("%d conflicts encountered.\n" % self.conflicts)
200
def get_tree(treespec, local_branch=None):
46
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
47
FinalPaths, create_by_entry, unique_add)
50
# TODO: Report back as changes are merged in
52
def _get_tree(treespec, local_branch=None):
201
53
location, revno = treespec
202
54
branch = Branch.open_containing(location)[0]
206
58
revision = branch.last_revision()
208
60
revision = branch.get_rev_id(revno)
209
return branch, get_revid_tree(branch, revision, local_branch)
211
def get_revid_tree(branch, revision, local_branch):
62
revision = NULL_REVISION
63
return branch, _get_revid_tree(branch, revision, local_branch)
66
def _get_revid_tree(branch, revision, local_branch):
212
67
if revision is None:
213
base_tree = branch.working_tree()
68
base_tree = branch.bzrdir.open_workingtree()
215
70
if local_branch is not None:
216
greedy_fetch(local_branch, branch, revision)
217
base_tree = local_branch.revision_tree(revision)
71
if local_branch.base != branch.base:
72
local_branch.fetch(branch, revision)
73
base_tree = local_branch.repository.revision_tree(revision)
219
base_tree = branch.revision_tree(revision)
75
base_tree = branch.repository.revision_tree(revision)
223
def file_exists(tree, file_id):
224
return tree.has_filename(tree.id2path(file_id))
227
def build_working_dir(to_dir):
228
"""Build a working directory in an empty directory.
230
to_dir is a directory containing branch metadata but no working files,
231
typically constructed by cloning an existing branch.
233
This is split out as a special idiomatic case of merge. It could
234
eventually be done by just building the tree directly calling into
235
lower-level code (e.g. constructing a changeset).
237
# RBC 20051019 is this not just 'export' ?
238
merge((to_dir, -1), (to_dir, 0), this_dir=to_dir,
239
check_clean=False, ignore_zero=True)
242
def merge(other_revision, base_revision,
243
check_clean=True, ignore_zero=False,
244
this_dir=None, backup_files=False, merge_type=ApplyMerge3,
246
"""Merge changes into a tree.
249
tuple(path, revision) Base for three-way merge.
251
tuple(path, revision) Other revision for three-way merge.
253
Directory to merge changes into; '.' by default.
255
If true, this_dir must have no uncommitted changes before the
257
ignore_zero - If true, suppress the "zero conflicts" message when
258
there are no conflicts; should be set when doing something we expect
259
to complete perfectly.
261
All available ancestors of other_revision and base_revision are
262
automatically pulled into the branch.
266
this_branch = Branch.open_containing(this_dir)[0]
267
this_rev_id = this_branch.last_revision()
268
if this_rev_id is None:
269
raise BzrCommandError("This branch has no commits")
271
changes = compare_trees(this_branch.working_tree(),
272
this_branch.basis_tree(), False)
273
if changes.has_changed():
274
raise BzrCommandError("Working tree has uncommitted changes.")
275
other_branch, other_tree = get_tree(other_revision, this_branch)
276
if other_revision[1] == -1:
277
other_rev_id = other_branch.last_revision()
278
if other_rev_id is None:
279
raise NoCommits(other_branch)
280
other_basis = other_rev_id
281
elif other_revision[1] is not None:
282
other_rev_id = other_branch.get_rev_id(other_revision[1])
283
other_basis = other_rev_id
286
other_basis = other_branch.last_revision()
287
if other_basis is None:
288
raise NoCommits(other_branch)
289
if base_revision == [None, None]:
79
def transform_tree(from_tree, to_tree, interesting_ids=None):
80
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
81
interesting_ids=interesting_ids, this_tree=from_tree)
85
def __init__(self, this_branch, other_tree=None, base_tree=None,
86
this_tree=None, pb=DummyProgress()):
88
assert this_tree is not None, "this_tree is required"
89
self.this_branch = this_branch
90
self.this_basis = this_branch.last_revision()
91
self.this_rev_id = None
92
self.this_tree = this_tree
93
self.this_revision_tree = None
94
self.this_basis_tree = None
95
self.other_tree = other_tree
96
self.base_tree = base_tree
97
self.ignore_zero = False
98
self.backup_files = False
99
self.interesting_ids = None
100
self.show_base = False
101
self.reprocess = False
106
def revision_tree(self, revision_id):
107
return self.this_branch.repository.revision_tree(revision_id)
109
def ensure_revision_trees(self):
110
if self.this_revision_tree is None:
111
self.this_basis_tree = self.this_branch.repository.revision_tree(
113
if self.this_basis == self.this_rev_id:
114
self.this_revision_tree = self.this_basis_tree
116
if self.other_rev_id is None:
117
other_basis_tree = self.revision_tree(self.other_basis)
118
changes = compare_trees(self.other_tree, other_basis_tree)
119
if changes.has_changed():
120
raise WorkingTreeNotRevision(self.this_tree)
121
other_rev_id = other_basis
122
self.other_tree = other_basis_tree
124
def file_revisions(self, file_id):
125
self.ensure_revision_trees()
126
def get_id(tree, file_id):
127
revision_id = tree.inventory[file_id].revision
128
assert revision_id is not None
130
if self.this_rev_id is None:
131
if self.this_basis_tree.get_file_sha1(file_id) != \
132
self.this_tree.get_file_sha1(file_id):
133
raise WorkingTreeNotRevision(self.this_tree)
135
trees = (self.this_basis_tree, self.other_tree)
136
return [get_id(tree, file_id) for tree in trees]
138
def check_basis(self, check_clean):
139
if self.this_basis is None:
140
raise BzrCommandError("This branch has no commits")
143
if self.this_basis != self.this_rev_id:
144
raise BzrCommandError("Working tree has uncommitted changes.")
146
def compare_basis(self):
147
changes = compare_trees(self.this_tree,
148
self.this_tree.basis_tree(), False)
149
if not changes.has_changed():
150
self.this_rev_id = self.this_basis
152
def set_interesting_files(self, file_list):
291
base_rev_id = common_ancestor(this_rev_id, other_basis,
293
except NoCommonAncestor:
294
raise UnrelatedBranches()
295
base_tree = get_revid_tree(this_branch, base_rev_id, None)
296
base_is_ancestor = True
298
base_branch, base_tree = get_tree(base_revision)
299
if base_revision[1] == -1:
300
base_rev_id = base_branch.last_revision()
301
elif base_revision[1] is None:
304
base_rev_id = base_branch.get_rev_id(base_revision[1])
305
fetch(from_branch=base_branch, to_branch=this_branch)
306
base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
308
if file_list is None:
309
interesting_ids = None
154
self._set_interesting_files(file_list)
155
except NotVersionedError, e:
156
raise BzrCommandError("%s is not a source file in any"
159
def _set_interesting_files(self, file_list):
160
"""Set the list of interesting ids from a list of files."""
161
if file_list is None:
162
self.interesting_ids = None
311
165
interesting_ids = set()
312
this_tree = this_branch.working_tree()
313
for fname in file_list:
314
path = this_tree.relpath(fname)
166
for path in file_list:
316
for tree in (this_tree, base_tree, other_tree):
168
for tree in (self.this_tree, self.base_tree, self.other_tree):
317
169
file_id = tree.inventory.path2id(path)
318
170
if file_id is not None:
319
171
interesting_ids.add(file_id)
322
raise BzrCommandError("%s is not a source file in any"
324
merge_inner(this_branch, other_tree, base_tree, tempdir=None,
325
ignore_zero=ignore_zero, backup_files=backup_files,
326
merge_type=merge_type, interesting_ids=interesting_ids)
327
if base_is_ancestor and other_rev_id is not None\
328
and other_rev_id not in this_branch.revision_history():
329
this_branch.add_pending_merge(other_rev_id)
332
def set_interesting(inventory_a, inventory_b, interesting_ids):
333
"""Mark files whose ids are in interesting_ids as interesting
335
for inventory in (inventory_a, inventory_b):
336
for path, source_file in inventory.iteritems():
337
source_file.interesting = source_file.id in interesting_ids
340
def merge_inner(this_branch, other_tree, base_tree, tempdir=None,
341
ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
342
interesting_ids=None):
174
raise NotVersionedError(path=path)
175
self.interesting_ids = interesting_ids
177
def set_pending(self):
178
if not self.base_is_ancestor:
180
if self.other_rev_id is None:
182
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
183
if self.other_rev_id in ancestry:
185
self.this_tree.add_pending_merge(self.other_rev_id)
187
def set_other(self, other_revision):
188
other_branch, self.other_tree = _get_tree(other_revision,
190
if other_revision[1] == -1:
191
self.other_rev_id = other_branch.last_revision()
192
if self.other_rev_id is None:
193
raise NoCommits(other_branch)
194
self.other_basis = self.other_rev_id
195
elif other_revision[1] is not None:
196
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
197
self.other_basis = self.other_rev_id
199
self.other_rev_id = None
200
self.other_basis = other_branch.last_revision()
201
if self.other_basis is None:
202
raise NoCommits(other_branch)
203
if other_branch.base != self.this_branch.base:
204
self.this_branch.fetch(other_branch, last_revision=self.other_basis)
206
def set_base(self, base_revision):
207
mutter("doing merge() with no base_revision specified")
208
if base_revision == [None, None]:
210
pb = bzrlib.ui.ui_factory.nested_progress_bar()
212
this_repo = self.this_branch.repository
213
self.base_rev_id = common_ancestor(self.this_basis,
218
except NoCommonAncestor:
219
raise UnrelatedBranches()
220
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
222
self.base_is_ancestor = True
224
base_branch, self.base_tree = _get_tree(base_revision)
225
if base_revision[1] == -1:
226
self.base_rev_id = base_branch.last_revision()
227
elif base_revision[1] is None:
228
self.base_rev_id = None
230
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
231
if self.this_branch.base != base_branch.base:
232
self.this_branch.fetch(base_branch)
233
self.base_is_ancestor = is_ancestor(self.this_basis,
238
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
239
'other_tree': self.other_tree,
240
'interesting_ids': self.interesting_ids,
242
if self.merge_type.requires_base:
243
kwargs['base_tree'] = self.base_tree
244
if self.merge_type.supports_reprocess:
245
kwargs['reprocess'] = self.reprocess
247
raise BzrError("Reprocess is not supported for this merge"
248
" type. %s" % merge_type)
249
if self.merge_type.supports_show_base:
250
kwargs['show_base'] = self.show_base
252
raise BzrError("Showing base is not supported for this"
253
" merge type. %s" % self.merge_type)
254
merge = self.merge_type(pb=self._pb, **kwargs)
255
if len(merge.cooked_conflicts) == 0:
256
if not self.ignore_zero:
257
note("All changes applied successfully.")
259
note("%d conflicts encountered." % len(merge.cooked_conflicts))
261
return len(merge.cooked_conflicts)
263
def regen_inventory(self, new_entries):
264
old_entries = self.this_tree.read_working_inventory()
268
for path, file_id in new_entries:
271
new_entries_map[file_id] = path
273
def id2path(file_id):
274
path = new_entries_map.get(file_id)
277
entry = old_entries[file_id]
278
if entry.parent_id is None:
280
return pathjoin(id2path(entry.parent_id), entry.name)
282
for file_id in old_entries:
283
entry = old_entries[file_id]
284
path = id2path(file_id)
285
if file_id in self.base_tree.inventory:
286
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
288
executable = getattr(entry, 'executable', False)
289
new_inventory[file_id] = (path, file_id, entry.parent_id,
290
entry.kind, executable)
292
by_path[path] = file_id
297
for path, file_id in new_entries:
299
del new_inventory[file_id]
302
new_path_list.append((path, file_id))
303
if file_id not in old_entries:
305
# Ensure no file is added before its parent
307
for path, file_id in new_path_list:
311
parent = by_path[os.path.dirname(path)]
312
abspath = pathjoin(self.this_tree.basedir, path)
313
kind = bzrlib.osutils.file_kind(abspath)
314
if file_id in self.base_tree.inventory:
315
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
318
new_inventory[file_id] = (path, file_id, parent, kind, executable)
319
by_path[path] = file_id
321
# Get a list in insertion order
322
new_inventory_list = new_inventory.values()
323
mutter ("""Inventory regeneration:
324
old length: %i insertions: %i deletions: %i new_length: %i"""\
325
% (len(old_entries), insertions, deletions,
326
len(new_inventory_list)))
327
assert len(new_inventory_list) == len(old_entries) + insertions\
329
new_inventory_list.sort()
330
return new_inventory_list
333
class Merge3Merger(object):
334
"""Three-way merger that uses the merge3 text merger"""
336
supports_reprocess = True
337
supports_show_base = True
338
history_based = False
340
def __init__(self, working_tree, this_tree, base_tree, other_tree,
341
interesting_ids=None, reprocess=False, show_base=False,
342
pb=DummyProgress(), pp=None):
343
"""Initialize the merger object and perform the merge."""
344
object.__init__(self)
345
self.this_tree = working_tree
346
self.base_tree = base_tree
347
self.other_tree = other_tree
348
self._raw_conflicts = []
349
self.cooked_conflicts = []
350
self.reprocess = reprocess
351
self.show_base = show_base
355
self.pp = ProgressPhase("Merge phase", 3, self.pb)
357
if interesting_ids is not None:
358
all_ids = interesting_ids
360
all_ids = set(base_tree)
361
all_ids.update(other_tree)
362
working_tree.lock_write()
363
self.tt = TreeTransform(working_tree, self.pb)
366
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
368
for num, file_id in enumerate(all_ids):
369
child_pb.update('Preparing file merge', num, len(all_ids))
370
self.merge_names(file_id)
371
file_status = self.merge_contents(file_id)
372
self.merge_executable(file_id, file_status)
377
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
379
fs_conflicts = resolve_conflicts(self.tt, child_pb)
382
self.cook_conflicts(fs_conflicts)
383
for conflict in self.cooked_conflicts:
386
results = self.tt.apply()
387
self.write_modified(results)
389
working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
390
except UnsupportedOperation:
397
working_tree.unlock()
400
def write_modified(self, results):
402
for path in results.modified_paths:
403
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
406
hash = self.this_tree.get_file_sha1(file_id)
409
modified_hashes[file_id] = hash
410
self.this_tree.set_merge_modified(modified_hashes)
413
def parent(entry, file_id):
414
"""Determine the parent for a file_id (used as a key method)"""
417
return entry.parent_id
420
def name(entry, file_id):
421
"""Determine the name for a file_id (used as a key method)"""
427
def contents_sha1(tree, file_id):
428
"""Determine the sha1 of the file contents (used as a key method)."""
429
if file_id not in tree:
431
return tree.get_file_sha1(file_id)
434
def executable(tree, file_id):
435
"""Determine the executability of a file-id (used as a key method)."""
436
if file_id not in tree:
438
if tree.kind(file_id) != "file":
440
return tree.is_executable(file_id)
443
def kind(tree, file_id):
444
"""Determine the kind of a file-id (used as a key method)."""
445
if file_id not in tree:
447
return tree.kind(file_id)
450
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
451
"""Do a three-way test on a scalar.
452
Return "this", "other" or "conflict", depending whether a value wins.
454
key_base = key(base_tree, file_id)
455
key_other = key(other_tree, file_id)
456
#if base == other, either they all agree, or only THIS has changed.
457
if key_base == key_other:
459
key_this = key(this_tree, file_id)
460
if key_this not in (key_base, key_other):
462
# "Ambiguous clean merge"
463
elif key_this == key_other:
466
assert key_this == key_base
469
def merge_names(self, file_id):
470
"""Perform a merge on file_id names and parents"""
472
if file_id in tree.inventory:
473
return tree.inventory[file_id]
476
this_entry = get_entry(self.this_tree)
477
other_entry = get_entry(self.other_tree)
478
base_entry = get_entry(self.base_tree)
479
name_winner = self.scalar_three_way(this_entry, base_entry,
480
other_entry, file_id, self.name)
481
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
482
other_entry, file_id,
484
if this_entry is None:
485
if name_winner == "this":
486
name_winner = "other"
487
if parent_id_winner == "this":
488
parent_id_winner = "other"
489
if name_winner == "this" and parent_id_winner == "this":
491
if name_winner == "conflict":
492
trans_id = self.tt.trans_id_file_id(file_id)
493
self._raw_conflicts.append(('name conflict', trans_id,
494
self.name(this_entry, file_id),
495
self.name(other_entry, file_id)))
496
if parent_id_winner == "conflict":
497
trans_id = self.tt.trans_id_file_id(file_id)
498
self._raw_conflicts.append(('parent conflict', trans_id,
499
self.parent(this_entry, file_id),
500
self.parent(other_entry, file_id)))
501
if other_entry is None:
502
# it doesn't matter whether the result was 'other' or
503
# 'conflict'-- if there's no 'other', we leave it alone.
505
# if we get here, name_winner and parent_winner are set to safe values.
506
winner_entry = {"this": this_entry, "other": other_entry,
507
"conflict": other_entry}
508
trans_id = self.tt.trans_id_file_id(file_id)
509
parent_id = winner_entry[parent_id_winner].parent_id
510
parent_trans_id = self.tt.trans_id_file_id(parent_id)
511
self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
514
def merge_contents(self, file_id):
515
"""Performa a merge on file_id contents."""
516
def contents_pair(tree):
517
if file_id not in tree:
519
kind = tree.kind(file_id)
520
if kind == "root_directory":
523
contents = tree.get_file_sha1(file_id)
524
elif kind == "symlink":
525
contents = tree.get_symlink_target(file_id)
528
return kind, contents
529
# See SPOT run. run, SPOT, run.
530
# So we're not QUITE repeating ourselves; we do tricky things with
532
base_pair = contents_pair(self.base_tree)
533
other_pair = contents_pair(self.other_tree)
534
if base_pair == other_pair:
535
# OTHER introduced no changes
537
this_pair = contents_pair(self.this_tree)
538
if this_pair == other_pair:
539
# THIS and OTHER introduced the same changes
542
trans_id = self.tt.trans_id_file_id(file_id)
543
if this_pair == base_pair:
544
# only OTHER introduced changes
545
if file_id in self.this_tree:
546
# Remove any existing contents
547
self.tt.delete_contents(trans_id)
548
if file_id in self.other_tree:
549
# OTHER changed the file
550
create_by_entry(self.tt,
551
self.other_tree.inventory[file_id],
552
self.other_tree, trans_id)
553
if file_id not in self.this_tree.inventory:
554
self.tt.version_file(file_id, trans_id)
556
elif file_id in self.this_tree.inventory:
557
# OTHER deleted the file
558
self.tt.unversion_file(trans_id)
560
#BOTH THIS and OTHER introduced changes; scalar conflict
561
elif this_pair[0] == "file" and other_pair[0] == "file":
562
# THIS and OTHER are both files, so text merge. Either
563
# BASE is a file, or both converted to files, so at least we
564
# have agreement that output should be a file.
565
if file_id not in self.this_tree.inventory:
566
self.tt.version_file(file_id, trans_id)
567
self.text_merge(file_id, trans_id)
569
self.tt.tree_kind(trans_id)
570
self.tt.delete_contents(trans_id)
575
# Scalar conflict, can't text merge. Dump conflicts
576
trans_id = self.tt.trans_id_file_id(file_id)
577
name = self.tt.final_name(trans_id)
578
parent_id = self.tt.final_parent(trans_id)
579
if file_id in self.this_tree.inventory:
580
self.tt.unversion_file(trans_id)
581
self.tt.delete_contents(trans_id)
582
file_group = self._dump_conflicts(name, parent_id, file_id,
584
self._raw_conflicts.append(('contents conflict', file_group))
586
def get_lines(self, tree, file_id):
587
"""Return the lines in a file, or an empty list."""
589
return tree.get_file(file_id).readlines()
593
def text_merge(self, file_id, trans_id):
594
"""Perform a three-way text merge on a file_id"""
595
# it's possible that we got here with base as a different type.
596
# if so, we just want two-way text conflicts.
597
if file_id in self.base_tree and \
598
self.base_tree.kind(file_id) == "file":
599
base_lines = self.get_lines(self.base_tree, file_id)
602
other_lines = self.get_lines(self.other_tree, file_id)
603
this_lines = self.get_lines(self.this_tree, file_id)
604
m3 = Merge3(base_lines, this_lines, other_lines)
605
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
606
if self.show_base is True:
607
base_marker = '|' * 7
611
def iter_merge3(retval):
612
retval["text_conflicts"] = False
613
for line in m3.merge_lines(name_a = "TREE",
614
name_b = "MERGE-SOURCE",
615
name_base = "BASE-REVISION",
616
start_marker=start_marker,
617
base_marker=base_marker,
618
reprocess=self.reprocess):
619
if line.startswith(start_marker):
620
retval["text_conflicts"] = True
621
yield line.replace(start_marker, '<' * 7)
625
merge3_iterator = iter_merge3(retval)
626
self.tt.create_file(merge3_iterator, trans_id)
627
if retval["text_conflicts"] is True:
628
self._raw_conflicts.append(('text conflict', trans_id))
629
name = self.tt.final_name(trans_id)
630
parent_id = self.tt.final_parent(trans_id)
631
file_group = self._dump_conflicts(name, parent_id, file_id,
632
this_lines, base_lines,
634
file_group.append(trans_id)
636
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
637
base_lines=None, other_lines=None, set_version=False,
639
"""Emit conflict files.
640
If this_lines, base_lines, or other_lines are omitted, they will be
641
determined automatically. If set_version is true, the .OTHER, .THIS
642
or .BASE (in that order) will be created as versioned files.
644
data = [('OTHER', self.other_tree, other_lines),
645
('THIS', self.this_tree, this_lines)]
647
data.append(('BASE', self.base_tree, base_lines))
650
for suffix, tree, lines in data:
652
trans_id = self._conflict_file(name, parent_id, tree, file_id,
654
file_group.append(trans_id)
655
if set_version and not versioned:
656
self.tt.version_file(file_id, trans_id)
660
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
662
"""Emit a single conflict file."""
663
name = name + '.' + suffix
664
trans_id = self.tt.create_path(name, parent_id)
665
entry = tree.inventory[file_id]
666
create_by_entry(self.tt, entry, tree, trans_id, lines)
669
def merge_executable(self, file_id, file_status):
670
"""Perform a merge on the execute bit."""
671
if file_status == "deleted":
673
trans_id = self.tt.trans_id_file_id(file_id)
675
if self.tt.final_kind(trans_id) != "file":
679
winner = self.scalar_three_way(self.this_tree, self.base_tree,
680
self.other_tree, file_id,
682
if winner == "conflict":
683
# There must be a None in here, if we have a conflict, but we
684
# need executability since file status was not deleted.
685
if self.other_tree.is_executable(file_id) is None:
690
if file_status == "modified":
691
executability = self.this_tree.is_executable(file_id)
692
if executability is not None:
693
trans_id = self.tt.trans_id_file_id(file_id)
694
self.tt.set_executability(executability, trans_id)
696
assert winner == "other"
697
if file_id in self.other_tree:
698
executability = self.other_tree.is_executable(file_id)
699
elif file_id in self.this_tree:
700
executability = self.this_tree.is_executable(file_id)
701
elif file_id in self.base_tree:
702
executability = self.base_tree.is_executable(file_id)
703
if executability is not None:
704
trans_id = self.tt.trans_id_file_id(file_id)
705
self.tt.set_executability(executability, trans_id)
707
def cook_conflicts(self, fs_conflicts):
708
"""Convert all conflicts into a form that doesn't depend on trans_id"""
709
from conflicts import Conflict
711
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
712
fp = FinalPaths(self.tt)
713
for conflict in self._raw_conflicts:
714
conflict_type = conflict[0]
715
if conflict_type in ('name conflict', 'parent conflict'):
716
trans_id = conflict[1]
717
conflict_args = conflict[2:]
718
if trans_id not in name_conflicts:
719
name_conflicts[trans_id] = {}
720
unique_add(name_conflicts[trans_id], conflict_type,
722
if conflict_type == 'contents conflict':
723
for trans_id in conflict[1]:
724
file_id = self.tt.final_file_id(trans_id)
725
if file_id is not None:
727
path = fp.get_path(trans_id)
728
for suffix in ('.BASE', '.THIS', '.OTHER'):
729
if path.endswith(suffix):
730
path = path[:-len(suffix)]
732
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
733
self.cooked_conflicts.append(c)
734
if conflict_type == 'text conflict':
735
trans_id = conflict[1]
736
path = fp.get_path(trans_id)
737
file_id = self.tt.final_file_id(trans_id)
738
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
739
self.cooked_conflicts.append(c)
741
for trans_id, conflicts in name_conflicts.iteritems():
743
this_parent, other_parent = conflicts['parent conflict']
744
assert this_parent != other_parent
746
this_parent = other_parent = \
747
self.tt.final_file_id(self.tt.final_parent(trans_id))
749
this_name, other_name = conflicts['name conflict']
750
assert this_name != other_name
752
this_name = other_name = self.tt.final_name(trans_id)
753
other_path = fp.get_path(trans_id)
754
if this_parent is not None:
756
fp.get_path(self.tt.trans_id_file_id(this_parent))
757
this_path = pathjoin(this_parent_path, this_name)
759
this_path = "<deleted>"
760
file_id = self.tt.final_file_id(trans_id)
761
c = Conflict.factory('path conflict', path=this_path,
762
conflict_path=other_path, file_id=file_id)
763
self.cooked_conflicts.append(c)
766
class WeaveMerger(Merge3Merger):
767
"""Three-way tree merger, text weave merger."""
768
supports_reprocess = False
769
supports_show_base = False
771
def __init__(self, working_tree, this_tree, base_tree, other_tree,
772
interesting_ids=None, pb=DummyProgress(), pp=None):
773
self.this_revision_tree = self._get_revision_tree(this_tree)
774
self.other_revision_tree = self._get_revision_tree(other_tree)
775
super(WeaveMerger, self).__init__(working_tree, this_tree,
776
base_tree, other_tree,
777
interesting_ids=interesting_ids,
780
def _get_revision_tree(self, tree):
781
"""Return a revision tree releated to this tree.
782
If the tree is a WorkingTree, the basis will be returned.
784
if getattr(tree, 'get_weave', False) is False:
785
# If we have a WorkingTree, try using the basis
786
return tree.branch.basis_tree()
790
def _check_file(self, file_id):
791
"""Check that the revision tree's version of the file matches."""
792
for tree, rt in ((self.this_tree, self.this_revision_tree),
793
(self.other_tree, self.other_revision_tree)):
796
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
797
raise WorkingTreeNotRevision(self.this_tree)
799
def _merged_lines(self, file_id):
800
"""Generate the merged lines.
801
There is no distinction between lines that are meant to contain <<<<<<<
804
weave = self.this_revision_tree.get_weave(file_id)
805
this_revision_id = self.this_revision_tree.inventory[file_id].revision
806
other_revision_id = \
807
self.other_revision_tree.inventory[file_id].revision
808
plan = weave.plan_merge(this_revision_id, other_revision_id)
809
return weave.weave_merge(plan, '<<<<<<< TREE\n',
810
'>>>>>>> MERGE-SOURCE\n')
812
def text_merge(self, file_id, trans_id):
813
"""Perform a (weave) text merge for a given file and file-id.
814
If conflicts are encountered, .THIS and .OTHER files will be emitted,
815
and a conflict will be noted.
817
self._check_file(file_id)
818
lines = list(self._merged_lines(file_id))
819
conflicts = '<<<<<<< TREE\n' in lines
820
self.tt.create_file(lines, trans_id)
822
self._raw_conflicts.append(('text conflict', trans_id))
823
name = self.tt.final_name(trans_id)
824
parent_id = self.tt.final_parent(trans_id)
825
file_group = self._dump_conflicts(name, parent_id, file_id,
827
file_group.append(trans_id)
830
class Diff3Merger(Merge3Merger):
831
"""Three-way merger using external diff3 for text merging"""
832
def dump_file(self, temp_dir, name, tree, file_id):
833
out_path = pathjoin(temp_dir, name)
834
out_file = file(out_path, "wb")
835
in_file = tree.get_file(file_id)
840
def text_merge(self, file_id, trans_id):
841
"""Perform a diff3 merge using a specified file-id and trans-id.
842
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
843
will be dumped, and a will be conflict noted.
846
temp_dir = mkdtemp(prefix="bzr-")
848
new_file = pathjoin(temp_dir, "new")
849
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
850
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
851
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
852
status = bzrlib.patch.diff3(new_file, this, base, other)
853
if status not in (0, 1):
854
raise BzrError("Unhandled diff3 exit code")
855
self.tt.create_file(file(new_file, "rb"), trans_id)
857
name = self.tt.final_name(trans_id)
858
parent_id = self.tt.final_parent(trans_id)
859
self._dump_conflicts(name, parent_id, file_id)
860
self._raw_conflicts.append(('text conflict', trans_id))
865
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
867
merge_type=Merge3Merger,
868
interesting_ids=None,
872
interesting_files=None,
343
875
"""Primary interface for merging.
345
typical use is probably
346
'merge_inner(branch, branch.get_revision_tree(other_revision),
347
branch.get_revision_tree(base_revision))'
350
_tempdir = tempfile.mkdtemp(prefix="bzr-")
354
_merge_inner(this_branch, other_tree, base_tree, _tempdir,
355
ignore_zero, merge_type, backup_files, interesting_ids)
358
shutil.rmtree(_tempdir)
361
def _merge_inner(this_branch, other_tree, base_tree, user_tempdir,
362
ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
363
interesting_ids=None):
364
def merge_factory(file_id, base, other):
365
contents_change = merge_type(file_id, base, other)
367
contents_change = BackupBeforeChange(contents_change)
368
return contents_change
370
this_tree = get_tree((this_branch.base, None))[1]
372
def get_inventory(tree):
373
return tree.inventory
375
inv_changes = merge_flex(this_tree, base_tree, other_tree,
376
generate_changeset, get_inventory,
377
MergeConflictHandler(this_tree, base_tree,
378
other_tree, ignore_zero=ignore_zero),
379
merge_factory=merge_factory,
380
interesting_ids=interesting_ids)
383
for id, path in inv_changes.iteritems():
388
assert path.startswith('.' + os.sep), "path is %s" % path
390
adjust_ids.append((path, id))
391
if len(adjust_ids) > 0:
392
this_branch.set_inventory(regen_inventory(this_branch,
397
def regen_inventory(this_branch, root, new_entries):
398
old_entries = this_branch.read_working_inventory()
402
for path, file_id in new_entries:
405
new_entries_map[file_id] = path
407
def id2path(file_id):
408
path = new_entries_map.get(file_id)
411
entry = old_entries[file_id]
412
if entry.parent_id is None:
414
return os.path.join(id2path(entry.parent_id), entry.name)
416
for file_id in old_entries:
417
entry = old_entries[file_id]
418
path = id2path(file_id)
419
new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
420
by_path[path] = file_id
425
for path, file_id in new_entries:
427
del new_inventory[file_id]
430
new_path_list.append((path, file_id))
431
if file_id not in old_entries:
433
# Ensure no file is added before its parent
435
for path, file_id in new_path_list:
439
parent = by_path[os.path.dirname(path)]
440
kind = bzrlib.osutils.file_kind(os.path.join(root, path))
441
new_inventory[file_id] = (path, file_id, parent, kind)
442
by_path[path] = file_id
444
# Get a list in insertion order
445
new_inventory_list = new_inventory.values()
446
mutter ("""Inventory regeneration:
447
old length: %i insertions: %i deletions: %i new_length: %i"""\
448
% (len(old_entries), insertions, deletions, len(new_inventory_list)))
449
assert len(new_inventory_list) == len(old_entries) + insertions - deletions
450
new_inventory_list.sort()
451
return new_inventory_list
453
merge_types = { "merge3": (ApplyMerge3, "Native diff3-style merge"),
454
"diff3": (Diff3Merge, "Merge using external diff3")
877
typical use is probably
878
'merge_inner(branch, branch.get_revision_tree(other_revision),
879
branch.get_revision_tree(base_revision))'
881
if this_tree is None:
882
warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
883
"bzrlib version 0.8.",
886
this_tree = this_branch.bzrdir.open_workingtree()
887
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
889
merger.backup_files = backup_files
890
merger.merge_type = merge_type
891
merger.interesting_ids = interesting_ids
892
merger.ignore_zero = ignore_zero
893
if interesting_files:
894
assert not interesting_ids, ('Only supply interesting_ids'
895
' or interesting_files')
896
merger._set_interesting_files(interesting_files)
897
merger.show_base = show_base
898
merger.reprocess = reprocess
899
merger.other_rev_id = other_rev_id
900
merger.other_basis = other_rev_id
901
return merger.do_merge()
904
merge_types = { "merge3": (Merge3Merger, "Native diff3-style merge"),
905
"diff3": (Diff3Merger, "Merge using external diff3"),
906
'weave': (WeaveMerger, "Weave-based merge")