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
2
from changeset import generate_changeset, ExceptionConflictHandler
3
from changeset import Inventory
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)
87
75
os.rename(new_file, this_path)
88
76
self.conflict("Diff3 conflict encountered in %s" % this_path)
90
def new_contents_conflict(self, filename, other_contents):
91
"""Conflicting contents for newly added file."""
92
self.copy(other_contents, filename + ".OTHER")
93
self.conflict("Conflict in newly added file %s" % filename)
96
78
def target_exists(self, entry, target, old_path):
97
79
"""Handle the case when the target file or dir exists"""
98
80
moved_path = self.add_suffix(target, ".moved")
99
81
self.conflict("Moved existing %s to %s" % (target, moved_path))
101
def rmdir_non_empty(self, filename):
102
"""Handle the case where the dir to be removed still has contents"""
103
self.conflict("Directory %s not removed because it is not empty"\
107
83
def finalize(self):
108
84
if not self.ignore_zero:
109
85
print "%d conflicts encountered.\n" % self.conflicts
163
141
os.mkdir(os.path.join(self.tempdir, "texts"))
166
def __contains__(self, file_id):
167
return id in self.tree
169
def get_file_sha1(self, id):
170
return self.tree.get_file_sha1(id)
172
144
def readonly_path(self, id):
173
if id not in self.tree:
175
145
if self.root is not None:
176
146
return self.tree.abspath(self.tree.id2path(id))
225
194
raise UnrelatedBranches()
226
195
base_revision = ['.', base_revno]
227
196
base_branch, base_tree = get_tree(base_revision, tempdir, "base")
228
if file_list is None:
229
interesting_ids = None
231
interesting_ids = set()
232
this_tree = this_branch.working_tree()
233
for fname in file_list:
234
path = this_branch.relpath(fname)
236
for tree in (this_tree, base_tree.tree, other_tree.tree):
237
file_id = tree.inventory.path2id(path)
238
if file_id is not None:
239
interesting_ids.add(file_id)
242
raise BzrCommandError("%s is not a source file in any"
244
197
merge_inner(this_branch, other_tree, base_tree, tempdir,
245
ignore_zero=ignore_zero, backup_files=backup_files,
246
merge_type=merge_type, interesting_ids=interesting_ids)
198
ignore_zero=ignore_zero)
248
200
shutil.rmtree(tempdir)
251
def set_interesting(inventory_a, inventory_b, interesting_ids):
252
"""Mark files whose ids are in interesting_ids as interesting
254
for inventory in (inventory_a, inventory_b):
255
for path, source_file in inventory.iteritems():
256
source_file.interesting = source_file.id in interesting_ids
259
def generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b,
260
interesting_ids=None):
261
"""Generate a changeset. If interesting_ids is supplied, only changes
262
to those files will be shown. Metadata changes are stripped.
203
def generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b):
204
"""Generate a changeset, using the text_id to mark really-changed files.
205
This permits blazing comparisons when text_ids are present. It also
206
disables metadata comparison for files with identical texts.
264
if interesting_ids is not None:
265
set_interesting(inventory_a, inventory_b, interesting_ids)
208
for file_id in tree_a.tree.inventory:
209
if file_id not in tree_b.tree.inventory:
211
entry_a = tree_a.tree.inventory[file_id]
212
entry_b = tree_b.tree.inventory[file_id]
213
if (entry_a.kind, entry_b.kind) != ("file", "file"):
215
if None in (entry_a.text_id, entry_b.text_id):
217
if entry_a.text_id != entry_b.text_id:
219
inventory_a[abspath(tree_a.tree, file_id)].interesting = False
220
inventory_b[abspath(tree_b.tree, file_id)].interesting = False
266
221
cset = generate_changeset(tree_a, tree_b, inventory_a, inventory_b)
267
222
for entry in cset.entries.itervalues():
268
223
entry.metadata_change = None
272
227
def merge_inner(this_branch, other_tree, base_tree, tempdir,
273
ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
274
interesting_ids=None):
276
def merge_factory(base_file, other_file):
277
contents_change = merge_type(base_file, other_file)
279
contents_change = BackupBeforeChange(contents_change)
280
return contents_change
282
def generate_cset(tree_a, tree_b, inventory_a, inventory_b):
283
return generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b,
286
229
this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
288
231
def get_inventory(tree):
289
232
return tree.inventory
291
234
inv_changes = merge_flex(this_tree, base_tree, other_tree,
292
generate_cset, get_inventory,
235
generate_cset_optimized, get_inventory,
293
236
MergeConflictHandler(base_tree.root,
294
ignore_zero=ignore_zero),
295
merge_factory=merge_factory)
237
ignore_zero=ignore_zero))
298
240
for id, path in inv_changes.iteritems():
303
245
assert path.startswith('./')
305
247
adjust_ids.append((path, id))
306
if len(adjust_ids) > 0:
307
this_branch.set_inventory(regen_inventory(this_branch, this_tree.root,
248
this_branch.set_inventory(regen_inventory(this_branch, this_tree.root, adjust_ids))
311
251
def regen_inventory(this_branch, root, new_entries):