1
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
2
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
3
from bzrlib.changeset import Inventory, Diff3Merge
1
from merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
2
from changeset import generate_changeset, ExceptionConflictHandler
3
from changeset import Inventory, Diff3Merge
4
4
from bzrlib import find_branch
5
5
import bzrlib.osutils
6
6
from bzrlib.errors import BzrCommandError
7
from bzrlib.delta import compare_trees
7
from bzrlib.diff import compare_trees
8
8
from trace import mutter, warning
15
# comments from abentley on irc: merge happens in two stages, each
16
# of which generates a changeset object
18
# stage 1: generate OLD->OTHER,
19
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
21
14
class UnrelatedBranches(BzrCommandError):
22
15
def __init__(self):
23
16
msg = "Branches have no common ancestor, and no base revision"\
28
21
class MergeConflictHandler(ExceptionConflictHandler):
29
"""Handle conflicts encountered while merging.
31
This subclasses ExceptionConflictHandler, so that any types of
32
conflict that are not explicitly handled cause an exception and
22
"""Handle conflicts encountered while merging"""
35
23
def __init__(self, dir, ignore_zero=False):
36
24
ExceptionConflictHandler.__init__(self, dir)
49
37
os.chmod(dest, 0777 & os.stat(source).st_mode)
51
def dump(self, lines, dest):
52
"""Copy the text and mode of a file
53
:param source: The path of the file to copy
54
:param dest: The distination file to create
56
d_file = file(dest, "wb")
60
39
def add_suffix(self, name, suffix, last_new_name=None):
61
40
"""Rename a file to append a suffix. If the new name exists, the
62
41
suffix is added repeatedly until a non-existant name is found
81
60
self.conflicts += 1
84
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
63
def merge_conflict(self, new_file, this_path, base_path, other_path):
86
65
Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER. The
87
66
main file will be a version with diff3 conflicts.
91
70
:param other_path: Path to the file text for the OTHER tree
93
72
self.add_suffix(this_path, ".THIS")
94
self.dump(base_lines, this_path+".BASE")
95
self.dump(other_lines, this_path+".OTHER")
73
self.copy(base_path, this_path+".BASE")
74
self.copy(other_path, this_path+".OTHER")
96
75
os.rename(new_file, this_path)
97
76
self.conflict("Diff3 conflict encountered in %s" % this_path)
99
def new_contents_conflict(self, filename, other_contents):
100
"""Conflicting contents for newly added file."""
101
self.copy(other_contents, filename + ".OTHER")
102
self.conflict("Conflict in newly added file %s" % filename)
105
78
def target_exists(self, entry, target, old_path):
106
79
"""Handle the case when the target file or dir exists"""
107
80
moved_path = self.add_suffix(target, ".moved")
117
90
if not self.ignore_zero:
118
91
print "%d conflicts encountered.\n" % self.conflicts
93
class SourceFile(object):
94
def __init__(self, path, id, present=None, isdir=None):
97
self.present = present
99
self.interesting = True
102
return "SourceFile(%s, %s)" % (self.path, self.id)
120
104
def get_tree(treespec, temp_root, label):
121
105
location, revno = treespec
122
106
branch = find_branch(location)
131
115
return branch, MergeTree(base_tree, temp_path)
118
def abspath(tree, file_id):
119
path = tree.inventory.id2path(file_id)
134
124
def file_exists(tree, file_id):
135
125
return tree.has_filename(tree.id2path(file_id))
127
def inventory_map(tree):
129
for file_id in tree.inventory:
130
path = abspath(tree, file_id)
131
inventory[path] = SourceFile(path, file_id)
138
135
class MergeTree(object):
139
136
def __init__(self, tree, tempdir):
142
139
self.root = tree.basedir
142
self.inventory = inventory_map(tree)
146
144
self.tempdir = tempdir
147
145
os.mkdir(os.path.join(self.tempdir, "texts"))
151
return self.tree.__iter__()
153
148
def __contains__(self, file_id):
154
return file_id in self.tree
156
def get_file(self, file_id):
157
return self.tree.get_file(file_id)
149
return id in self.tree
159
151
def get_file_sha1(self, id):
160
152
return self.tree.get_file_sha1(id)
162
def id2path(self, file_id):
163
return self.tree.id2path(file_id)
165
def has_id(self, file_id):
166
return self.tree.has_id(file_id)
168
154
def readonly_path(self, id):
169
155
if id not in self.tree:
252
238
source_file.interesting = source_file.id in interesting_ids
255
def generate_cset_optimized(tree_a, tree_b, interesting_ids=None):
241
def generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b,
242
interesting_ids=None):
256
243
"""Generate a changeset. If interesting_ids is supplied, only changes
257
244
to those files will be shown. Metadata changes are stripped.
259
cset = generate_changeset(tree_a, tree_b, interesting_ids)
246
if interesting_ids is not None:
247
set_interesting(inventory_a, inventory_b, interesting_ids)
248
cset = generate_changeset(tree_a, tree_b, inventory_a, inventory_b)
260
249
for entry in cset.entries.itervalues():
261
250
entry.metadata_change = None
266
255
ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
267
256
interesting_ids=None):
269
def merge_factory(file_id, base, other):
270
contents_change = merge_type(file_id, base, other)
258
def merge_factory(base_file, other_file):
259
contents_change = merge_type(base_file, other_file)
272
261
contents_change = BackupBeforeChange(contents_change)
273
262
return contents_change
264
def generate_cset(tree_a, tree_b, inventory_a, inventory_b):
265
return generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b,
275
268
this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
277
270
def get_inventory(tree):
278
return tree.tree.inventory
271
return tree.inventory
280
273
inv_changes = merge_flex(this_tree, base_tree, other_tree,
281
generate_cset_optimized, get_inventory,
274
generate_cset, get_inventory,
282
275
MergeConflictHandler(base_tree.root,
283
276
ignore_zero=ignore_zero),
284
merge_factory=merge_factory,
285
interesting_ids=interesting_ids)
277
merge_factory=merge_factory)
288
280
for id, path in inv_changes.iteritems():
293
assert path.startswith('./'), "path is %s" % path
285
assert path.startswith('./')
295
287
adjust_ids.append((path, id))
296
if len(adjust_ids) > 0:
297
this_branch.set_inventory(regen_inventory(this_branch, this_tree.root,
288
this_branch.set_inventory(regen_inventory(this_branch, this_tree.root, adjust_ids))
301
291
def regen_inventory(this_branch, root, new_entries):
302
292
old_entries = this_branch.read_working_inventory()
303
293
new_inventory = {}
306
for path, file_id in new_entries:
309
new_entries_map[file_id] = path
311
def id2path(file_id):
312
path = new_entries_map.get(file_id)
315
entry = old_entries[file_id]
316
if entry.parent_id is None:
318
return os.path.join(id2path(entry.parent_id), entry.name)
320
295
for file_id in old_entries:
321
296
entry = old_entries[file_id]
322
path = id2path(file_id)
297
path = old_entries.id2path(file_id)
323
298
new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
324
299
by_path[path] = file_id