3
from tempfile import mkstemp
5
from bzrlib import _changeset
6
from bzrlib._changeset import Inventory, apply_changeset, invert_dict
7
from bzrlib.osutils import backup_file, rename, pathjoin
8
from bzrlib.merge3 import Merge3
10
from bzrlib.atomicfile import AtomicFile
11
from bzrlib._changeset import get_contents
15
"""Contents-change wrapper around merge3.Merge3"""
19
def __init__(self, file_id, base, other, show_base=False, reprocess=False):
20
self.file_id = file_id
23
self.show_base = show_base
24
self.reprocess = reprocess
26
def is_creation(self):
29
def is_deletion(self):
32
def __eq__(self, other):
33
if not isinstance(other, ApplyMerge3):
35
return (self.base == other.base and
36
self.other == other.other and self.file_id == other.file_id)
38
def __ne__(self, other):
39
return not (self == other)
41
def apply(self, filename, conflict_handler):
42
output_file, new_file = mkstemp(dir=os.path.dirname(filename),
43
prefix=os.path.basename(filename))
44
output_file = fdopen(output_file, 'wb')
48
if self.file_id not in tree:
49
raise Exception("%s not in tree" % self.file_id)
51
return tree.get_file(self.file_id).readlines()
52
base_lines = get_lines(base)
53
other_lines = get_lines(other)
54
m3 = Merge3(base_lines, file(filename, "rb").readlines(), other_lines)
57
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
58
if self.show_base is True:
62
for line in m3.merge_lines(name_a = "TREE", name_b = "MERGE-SOURCE",
63
name_base = "BASE-REVISION",
64
start_marker=start_marker, base_marker=base_marker,
65
reprocess = self.reprocess):
66
if line.startswith(start_marker):
68
output_file.write(line.replace(start_marker, '<' * 7))
70
output_file.write(line)
73
os.chmod(new_file, os.stat(filename).st_mode)
74
rename(new_file, filename)
77
conflict_handler.merge_conflict(new_file, filename, base_lines,
82
"""Contents-change wrapper around weave merge"""
86
def __init__(self, weave, this_revision_id, other_revision_id):
88
self.this_revision_id = this_revision_id
89
self.other_revision_id = other_revision_id
91
def is_creation(self):
94
def is_deletion(self):
97
def __eq__(self, other):
98
if not isinstance(other, WeaveMerge):
100
return self.weave == other.weave and\
101
self.this_revision_id == other.this_revision_id and\
102
self.other_revision_id == other.other_revision_id
104
def __ne__(self, other):
105
return not (self == other)
107
def apply(self, filename, conflict_handler):
108
this_i = self.weave.lookup(self.this_revision_id)
109
other_i = self.weave.lookup(self.other_revision_id)
110
plan = self.weave.plan_merge(this_i, other_i)
111
lines = self.weave.weave_merge(plan)
113
out_file = AtomicFile(filename, mode='wb')
115
if line == '<<<<<<<\n':
119
conflict_handler.weave_merge_conflict(filename, self.weave,
125
class BackupBeforeChange:
126
"""Contents-change wrapper to back up file first"""
128
def __init__(self, contents_change):
129
self.contents_change = contents_change
131
def is_creation(self):
132
return self.contents_change.is_creation()
134
def is_deletion(self):
135
return self.contents_change.is_deletion()
137
def __eq__(self, other):
138
if not isinstance(other, BackupBeforeChange):
140
return (self.contents_change == other.contents_change)
142
def __ne__(self, other):
143
return not (self == other)
145
def apply(self, filename, conflict_handler):
146
backup_file(filename)
147
self.contents_change.apply(filename, conflict_handler)
150
def invert_invent(inventory):
152
for file_id in inventory:
153
path = inventory.id2path(file_id)
158
invert_invent[file_id] = path
162
def merge_flex(this, base, other, changeset_function, inventory_function,
163
conflict_handler, merge_factory, interesting_ids):
164
cset = changeset_function(base, other, interesting_ids)
165
new_cset = make_merge_changeset(cset, this, base, other,
166
conflict_handler, merge_factory)
167
result = apply_changeset(new_cset, invert_invent(this.inventory),
168
this.basedir, conflict_handler)
172
def make_merge_changeset(cset, this, base, other,
173
conflict_handler, merge_factory):
174
new_cset = _changeset.Changeset()
176
for entry in cset.entries.itervalues():
177
if entry.is_boring():
178
new_cset.add_entry(entry)
180
new_entry = make_merged_entry(entry, this, base, other,
182
new_contents = make_merged_contents(entry, this, base, other,
185
new_entry.contents_change = new_contents
186
new_entry.metadata_change = make_merged_metadata(entry, base, other)
187
new_cset.add_entry(new_entry)
192
class ThreeWayConflict(Exception):
193
def __init__(self, this, base, other):
197
msg = "Conflict merging %s %s and %s" % (this, base, other)
198
Exception.__init__(self, msg)
201
def threeway_select(this, base, other):
202
"""Returns a value selected by the three-way algorithm.
203
Raises ThreewayConflict if the algorithm yields a conflict"""
211
raise ThreeWayConflict(this, base, other)
214
def make_merged_entry(entry, this, base, other, conflict_handler):
215
from bzrlib.trace import mutter
216
def entry_data(file_id, tree):
217
assert hasattr(tree, "__contains__"), "%s" % tree
218
if not tree.has_or_had_id(file_id):
219
return (None, None, "")
220
entry = tree.inventory[file_id]
221
my_dir = tree.id2path(entry.parent_id)
224
return entry.name, entry.parent_id, my_dir
225
this_name, this_parent, this_dir = entry_data(entry.id, this)
226
base_name, base_parent, base_dir = entry_data(entry.id, base)
227
other_name, other_parent, other_dir = entry_data(entry.id, other)
228
mutter("Dirs: this, base, other %r %r %r", this_dir, base_dir, other_dir)
229
mutter("Names: this, base, other %r %r %r", this_name, base_name, other_name)
232
new_name = threeway_select(this_name, base_name, other_name)
233
except ThreeWayConflict:
234
new_name = conflict_handler.rename_conflict(entry.id, this_name,
235
base_name, other_name)
237
old_parent = this_parent
239
new_parent = threeway_select(this_parent, base_parent, other_parent)
240
except ThreeWayConflict:
241
new_parent = conflict_handler.move_conflict(entry.id, this_dir,
243
def get_path(name, parent):
246
assert parent is None
248
parent_dir = {this_parent: this_dir, other_parent: other_dir,
249
base_parent: base_dir}
250
directory = parent_dir[parent]
251
return pathjoin(directory, name)
253
assert parent is None
256
old_path = get_path(old_name, old_parent)
258
new_entry = _changeset.ChangesetEntry(entry.id, old_parent, old_path)
259
new_entry.new_path = get_path(new_name, new_parent)
260
new_entry.new_parent = new_parent
261
mutter(repr(new_entry))
265
def make_merged_contents(entry, this, base, other, conflict_handler,
267
contents = entry.contents_change
271
this_path = this.id2abspath(entry.id)
275
if this_path is None:
276
return conflict_handler.missing_for_merge(entry.id,
277
other.id2path(entry.id))
278
return merge_factory(entry.id, base, other)
280
if isinstance(contents, _changeset.ReplaceContents):
281
base_contents = contents.old_contents
282
other_contents = contents.new_contents
283
if base_contents is None and other_contents is None:
285
if other_contents is None:
286
this_contents = get_contents(this, entry.id)
287
if this_path is not None and bzrlib.osutils.lexists(this_path):
288
if this_contents != base_contents:
289
return conflict_handler.rem_contents_conflict(this_path,
290
this_contents, base_contents)
294
elif base_contents is None:
295
if this_path is None or not bzrlib.osutils.lexists(this_path):
298
this_contents = get_contents(this, entry.id)
299
if this_contents == other_contents:
302
conflict_handler.new_contents_conflict(this_path,
304
elif isinstance(base_contents, _changeset.TreeFileCreate) and \
305
isinstance(other_contents, _changeset.TreeFileCreate):
308
this_contents = get_contents(this, entry.id)
309
if this_contents == base_contents:
311
elif this_contents == other_contents:
313
elif base_contents == other_contents:
316
conflict_handler.threeway_contents_conflict(this_path,
322
def make_merged_metadata(entry, base, other):
323
metadata = entry.metadata_change
326
assert isinstance(metadata, _changeset.ChangeExecFlag)
327
if metadata.new_exec_flag is None:
329
elif metadata.old_exec_flag is None:
332
return ExecFlagMerge(base, other, entry.id)
335
class ExecFlagMerge(object):
336
def __init__(self, base_tree, other_tree, file_id):
337
self.base_tree = base_tree
338
self.other_tree = other_tree
339
self.file_id = file_id
341
def apply(self, filename, conflict_handler):
342
base = self.base_tree
343
other = self.other_tree
344
base_exec_flag = base.is_executable(self.file_id)
345
other_exec_flag = other.is_executable(self.file_id)
346
this_mode = os.stat(filename).st_mode
347
this_exec_flag = bool(this_mode & 0111)
348
if (base_exec_flag != other_exec_flag and
349
this_exec_flag != other_exec_flag):
350
assert this_exec_flag == base_exec_flag
351
current_mode = os.stat(filename).st_mode
355
to_mode = current_mode | (0100 & ~umask)
356
# Enable x-bit for others only if they can read it.
357
if current_mode & 0004:
358
to_mode |= 0001 & ~umask
359
if current_mode & 0040:
360
to_mode |= 0010 & ~umask
362
to_mode = current_mode & ~0111
363
os.chmod(filename, to_mode)