92
70
:param other_path: Path to the file text for the OTHER tree
94
72
self.add_suffix(this_path, ".THIS")
95
self.dump(base_lines, this_path+".BASE")
96
self.dump(other_lines, this_path+".OTHER")
73
self.copy(base_path, this_path+".BASE")
74
self.copy(other_path, this_path+".OTHER")
97
75
os.rename(new_file, this_path)
98
76
self.conflict("Diff3 conflict encountered in %s" % this_path)
100
def new_contents_conflict(self, filename, other_contents):
101
"""Conflicting contents for newly added file."""
102
self.copy(other_contents, filename + ".OTHER")
103
self.conflict("Conflict in newly added file %s" % filename)
106
78
def target_exists(self, entry, target, old_path):
107
79
"""Handle the case when the target file or dir exists"""
108
80
moved_path = self.add_suffix(target, ".moved")
109
81
self.conflict("Moved existing %s to %s" % (target, moved_path))
111
def rmdir_non_empty(self, filename):
112
"""Handle the case where the dir to be removed still has contents"""
113
self.conflict("Directory %s not removed because it is not empty"\
117
83
def finalize(self):
118
84
if not self.ignore_zero:
119
85
print "%d conflicts encountered.\n" % self.conflicts
121
def get_tree(treespec, temp_root, label, local_branch=None):
87
class SourceFile(object):
88
def __init__(self, path, id, present=None, isdir=None):
91
self.present = present
93
self.interesting = True
96
return "SourceFile(%s, %s)" % (self.path, self.id)
98
def get_tree(treespec, temp_root, label):
122
99
location, revno = treespec
123
100
branch = find_branch(location)
124
101
if revno is None:
102
base_tree = branch.working_tree()
126
103
elif revno == -1:
127
revision = branch.last_patch()
129
revision = branch.lookup_revision(revno)
130
return branch, get_revid_tree(branch, revision, temp_root, label,
133
def get_revid_tree(branch, revision, temp_root, label, local_branch):
135
base_tree = branch.working_tree()
137
if local_branch is not None:
138
greedy_fetch(local_branch, branch, revision)
139
base_tree = local_branch.revision_tree(revision)
141
base_tree = branch.revision_tree(revision)
104
base_tree = branch.basis_tree()
106
base_tree = branch.revision_tree(branch.lookup_revision(revno))
142
107
temp_path = os.path.join(temp_root, label)
143
108
os.mkdir(temp_path)
144
return MergeTree(base_tree, temp_path)
109
return branch, MergeTree(base_tree, temp_path)
112
def abspath(tree, file_id):
113
path = tree.inventory.id2path(file_id)
147
118
def file_exists(tree, file_id):
148
119
return tree.has_filename(tree.id2path(file_id))
121
def inventory_map(tree):
123
for file_id in tree.inventory:
124
if not file_exists(tree, file_id):
126
path = abspath(tree, file_id)
127
inventory[path] = SourceFile(path, file_id)
151
131
class MergeTree(object):
152
132
def __init__(self, tree, tempdir):
212
172
If true, this_dir must have no uncommitted changes before the
214
all available ancestors of other_revision and base_revision are
215
automatically pulled into the branch.
217
from bzrlib.revision import common_ancestor, is_ancestor
218
from bzrlib.revision import MultipleRevisionSources
219
from bzrlib.errors import NoSuchRevision
220
175
tempdir = tempfile.mkdtemp(prefix="bzr-")
222
177
if this_dir is None:
224
179
this_branch = find_branch(this_dir)
225
this_rev_id = this_branch.last_patch()
226
if this_rev_id is None:
227
raise BzrCommandError("This branch has no commits")
229
181
changes = compare_trees(this_branch.working_tree(),
230
182
this_branch.basis_tree(), False)
231
183
if changes.has_changed():
232
184
raise BzrCommandError("Working tree has uncommitted changes.")
233
other_branch, other_tree = get_tree(other_revision, tempdir, "other",
235
if other_revision[1] == -1:
236
other_rev_id = other_branch.last_patch()
237
other_basis = other_rev_id
238
elif other_revision[1] is not None:
239
other_rev_id = other_branch.lookup_revision(other_revision[1])
240
other_basis = other_rev_id
243
other_basis = other_branch.last_patch()
185
other_branch, other_tree = get_tree(other_revision, tempdir, "other")
244
186
if base_revision == [None, None]:
245
base_rev_id = common_ancestor(this_rev_id, other_basis,
247
if base_rev_id is None:
187
if other_revision[1] == -1:
190
o_revno = other_revision[1]
191
base_revno = this_branch.common_ancestor(other_branch,
192
other_revno=o_revno)[0]
193
if base_revno is None:
248
194
raise UnrelatedBranches()
249
base_tree = get_revid_tree(this_branch, base_rev_id, tempdir,
251
base_is_ancestor = True
253
base_branch, base_tree = get_tree(base_revision, tempdir, "base")
254
if base_revision[1] == -1:
255
base_rev_id = base_branch.last_patch()
256
elif base_revision[1] is None:
259
base_rev_id = base_branch.lookup_revision(base_revision[1])
260
if base_rev_id is not None:
261
base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
262
MultipleRevisionSources(
266
base_is_ancestor = False
267
if file_list is None:
268
interesting_ids = None
270
interesting_ids = set()
271
this_tree = this_branch.working_tree()
272
for fname in file_list:
273
path = this_branch.relpath(fname)
275
for tree in (this_tree, base_tree.tree, other_tree.tree):
276
file_id = tree.inventory.path2id(path)
277
if file_id is not None:
278
interesting_ids.add(file_id)
281
raise BzrCommandError("%s is not a source file in any"
195
base_revision = ['.', base_revno]
196
base_branch, base_tree = get_tree(base_revision, tempdir, "base")
283
197
merge_inner(this_branch, other_tree, base_tree, tempdir,
284
ignore_zero=ignore_zero, backup_files=backup_files,
285
merge_type=merge_type, interesting_ids=interesting_ids)
286
if base_is_ancestor and other_rev_id is not None:
287
this_branch.add_pending_merge(other_rev_id)
198
ignore_zero=ignore_zero)
289
200
shutil.rmtree(tempdir)
292
def set_interesting(inventory_a, inventory_b, interesting_ids):
293
"""Mark files whose ids are in interesting_ids as interesting
295
for inventory in (inventory_a, inventory_b):
296
for path, source_file in inventory.iteritems():
297
source_file.interesting = source_file.id in interesting_ids
300
def generate_cset_optimized(tree_a, tree_b, interesting_ids=None):
301
"""Generate a changeset. If interesting_ids is supplied, only changes
302
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.
304
cset = generate_changeset(tree_a, tree_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
221
cset = generate_changeset(tree_a, tree_b, inventory_a, inventory_b)
305
222
for entry in cset.entries.itervalues():
306
223
entry.metadata_change = None
310
227
def merge_inner(this_branch, other_tree, base_tree, tempdir,
311
ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
312
interesting_ids=None):
314
def merge_factory(file_id, base, other):
315
contents_change = merge_type(file_id, base, other)
317
contents_change = BackupBeforeChange(contents_change)
318
return contents_change
320
229
this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
322
231
def get_inventory(tree):
323
return tree.tree.inventory
232
return tree.inventory
325
234
inv_changes = merge_flex(this_tree, base_tree, other_tree,
326
235
generate_cset_optimized, get_inventory,
327
236
MergeConflictHandler(base_tree.root,
328
ignore_zero=ignore_zero),
329
merge_factory=merge_factory,
330
interesting_ids=interesting_ids)
237
ignore_zero=ignore_zero))
333
240
for id, path in inv_changes.iteritems():
338
assert path.startswith('./'), "path is %s" % path
245
assert path.startswith('./')
340
247
adjust_ids.append((path, id))
341
if len(adjust_ids) > 0:
342
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))
346
251
def regen_inventory(this_branch, root, new_entries):
347
252
old_entries = this_branch.read_working_inventory()
348
253
new_inventory = {}
351
for path, file_id in new_entries:
354
new_entries_map[file_id] = path
356
def id2path(file_id):
357
path = new_entries_map.get(file_id)
360
entry = old_entries[file_id]
361
if entry.parent_id is None:
363
return os.path.join(id2path(entry.parent_id), entry.name)
365
255
for file_id in old_entries:
366
256
entry = old_entries[file_id]
367
path = id2path(file_id)
257
path = old_entries.id2path(file_id)
368
258
new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
369
259
by_path[path] = file_id