1
# Copyright (C) 2005 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
import bzrlib.revision
25
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
26
from bzrlib.merge_core import WeaveMerge
27
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
28
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
29
from bzrlib.branch import Branch
30
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
31
from bzrlib.errors import NoCommits, WorkingTreeNotRevision
32
from bzrlib.delta import compare_trees
33
from bzrlib.trace import mutter, warning, note
34
from bzrlib.fetch import greedy_fetch, fetch
35
from bzrlib.revision import is_ancestor
36
from bzrlib.osutils import rename
37
from bzrlib.revision import common_ancestor, MultipleRevisionSources
38
from bzrlib.errors import NoSuchRevision
40
# TODO: build_working_dir can be built on something simpler than merge()
42
# FIXME: merge() parameters seem oriented towards the command line
43
# NOTABUG: merge is a helper for commandline functions. merge_inner is the
44
# the core functionality.
46
# comments from abentley on irc: merge happens in two stages, each
47
# of which generates a changeset object
49
# stage 1: generate OLD->OTHER,
50
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
52
class MergeConflictHandler(ExceptionConflictHandler):
53
"""Handle conflicts encountered while merging.
55
This subclasses ExceptionConflictHandler, so that any types of
56
conflict that are not explicitly handled cause an exception and
59
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
60
ExceptionConflictHandler.__init__(self)
62
self.ignore_zero = ignore_zero
63
self.this_tree = this_tree
64
self.base_tree = base_tree
65
self.other_tree = other_tree
67
def copy(self, source, dest):
68
"""Copy the text and mode of a file
69
:param source: The path of the file to copy
70
:param dest: The distination file to create
72
s_file = file(source, "rb")
73
d_file = file(dest, "wb")
76
os.chmod(dest, 0777 & os.stat(source).st_mode)
78
def dump(self, lines, dest):
79
"""Copy the text and mode of a file
80
:param source: The path of the file to copy
81
:param dest: The distination file to create
83
d_file = file(dest, "wb")
87
def add_suffix(self, name, suffix, last_new_name=None):
88
"""Rename a file to append a suffix. If the new name exists, the
89
suffix is added repeatedly until a non-existant name is found
91
:param name: The path of the file
92
:param suffix: The suffix to append
93
:param last_new_name: (used for recursive calls) the last name tried
95
if last_new_name is None:
97
new_name = last_new_name+suffix
99
rename(name, new_name)
102
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
104
return self.add_suffix(name, suffix, last_new_name=new_name)
106
def conflict(self, text):
111
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
113
Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER. The
114
main file will be a version with diff3 conflicts.
115
:param new_file: Path to the output file with diff3 markers
116
:param this_path: Path to the file text for the THIS tree
117
:param base_path: Path to the file text for the BASE tree
118
:param other_path: Path to the file text for the OTHER tree
120
self.add_suffix(this_path, ".THIS")
121
self.dump(base_lines, this_path+".BASE")
122
self.dump(other_lines, this_path+".OTHER")
123
rename(new_file, this_path)
124
self.conflict("Diff3 conflict encountered in %s" % this_path)
126
def weave_merge_conflict(self, filename, weave, other_i, out_file):
128
Handle weave conflicts by producing a .THIS, and .OTHER. The
129
main file will be a version with diff3-style conflicts.
131
self.add_suffix(filename, ".THIS")
133
self.dump(weave.get_iter(other_i), filename+".OTHER")
134
self.conflict("Text conflict encountered in %s" % filename)
136
def new_contents_conflict(self, filename, other_contents):
137
"""Conflicting contents for newly added file."""
138
other_contents(filename + ".OTHER", self, False)
139
self.conflict("Conflict in newly added file %s" % filename)
142
def target_exists(self, entry, target, old_path):
143
"""Handle the case when the target file or dir exists"""
144
moved_path = self.add_suffix(target, ".moved")
145
self.conflict("Moved existing %s to %s" % (target, moved_path))
147
def rmdir_non_empty(self, filename):
148
"""Handle the case where the dir to be removed still has contents"""
149
self.conflict("Directory %s not removed because it is not empty"\
153
def rem_contents_conflict(self, filename, this_contents, base_contents):
154
base_contents(filename+".BASE", self, False)
155
this_contents(filename+".THIS", self, False)
156
return ReplaceContents(this_contents, None)
158
def rem_contents_conflict(self, filename, this_contents, base_contents):
159
base_contents(filename+".BASE", self, False)
160
this_contents(filename+".THIS", self, False)
161
self.conflict("Other branch deleted locally modified file %s" %
163
return ReplaceContents(this_contents, None)
165
def abs_this_path(self, file_id):
166
"""Return the absolute path for a file_id in the this tree."""
167
return self.this_tree.id2abspath(file_id)
169
def add_missing_parents(self, file_id, tree):
170
"""If some of the parents for file_id are missing, add them."""
171
entry = tree.inventory[file_id]
172
if entry.parent_id not in self.this_tree:
173
return self.create_all_missing(entry.parent_id, tree)
175
return self.abs_this_path(entry.parent_id)
177
def create_all_missing(self, file_id, tree):
178
"""Add contents for a file_id and all its parents to a tree."""
179
entry = tree.inventory[file_id]
180
if entry.parent_id is not None and entry.parent_id not in self.this_tree:
181
abspath = self.create_all_missing(entry.parent_id, tree)
183
abspath = self.abs_this_path(entry.parent_id)
184
entry_path = os.path.join(abspath, entry.name)
185
if not os.path.isdir(entry_path):
186
self.create(file_id, entry_path, tree)
189
def create(self, file_id, path, tree, reverse=False):
190
"""Uses tree data to create a filesystem object for the file_id"""
191
from changeset import get_contents
192
get_contents(tree, file_id)(path, self, reverse)
194
def missing_for_merge(self, file_id, other_path):
195
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
196
self.conflict("Other branch modified locally deleted file %s" %
198
parent_dir = self.add_missing_parents(file_id, self.other_tree)
199
stem = os.path.join(parent_dir, os.path.basename(other_path))
200
self.create(file_id, stem+".OTHER", self.other_tree)
201
self.create(file_id, stem+".BASE", self.base_tree)
203
def threeway_contents_conflict(filename, this_contents, base_contents,
205
self.conflict("Three-way conflict merging %s" % filename)
208
if not self.ignore_zero:
209
note("%d conflicts encountered.\n" % self.conflicts)
211
def get_tree(treespec, local_branch=None):
212
location, revno = treespec
213
branch = Branch.open_containing(location)[0]
217
revision = branch.last_revision()
219
revision = branch.get_rev_id(revno)
220
return branch, get_revid_tree(branch, revision, local_branch)
222
def get_revid_tree(branch, revision, local_branch):
224
base_tree = branch.working_tree()
226
if local_branch is not None:
227
greedy_fetch(local_branch, branch, revision)
228
base_tree = local_branch.revision_tree(revision)
230
base_tree = branch.revision_tree(revision)
234
def file_exists(tree, file_id):
235
return tree.has_filename(tree.id2path(file_id))
238
def build_working_dir(to_dir):
239
"""Build a working directory in an empty directory.
241
to_dir is a directory containing branch metadata but no working files,
242
typically constructed by cloning an existing branch.
244
This is split out as a special idiomatic case of merge. It could
245
eventually be done by just building the tree directly calling into
246
lower-level code (e.g. constructing a changeset).
248
# RBC 20051019 is this not just 'export' ?
249
# Well, export doesn't take care of inventory...
250
this_branch = Branch.open_containing(to_dir)[0]
251
transform_tree(this_branch.working_tree(), this_branch.basis_tree())
253
def transform_tree(from_tree, to_tree):
254
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True)
256
def merge(other_revision, base_revision,
257
check_clean=True, ignore_zero=False,
258
this_dir=None, backup_files=False, merge_type=ApplyMerge3,
259
file_list=None, show_base=False):
260
"""Merge changes into a tree.
263
tuple(path, revision) Base for three-way merge.
265
tuple(path, revision) Other revision for three-way merge.
267
Directory to merge changes into; '.' by default.
269
If true, this_dir must have no uncommitted changes before the
271
ignore_zero - If true, suppress the "zero conflicts" message when
272
there are no conflicts; should be set when doing something we expect
273
to complete perfectly.
275
All available ancestors of other_revision and base_revision are
276
automatically pulled into the branch.
280
this_branch = Branch.open_containing(this_dir)[0]
282
merger = Merger(this_branch)
283
merger.check_basis(check_clean)
284
merger.set_other(other_revision)
285
merger.set_base(base_revision)
286
merger.backup_files = backup_files
287
merger.merge_type = merge_type
288
merger.set_interesting_files(file_list)
289
merger.show_base = show_base
290
merger.conflict_handler = MergeConflictHandler(merger.this_tree,
293
ignore_zero=ignore_zero)
294
conflicts = merger.do_merge()
298
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
299
backup_files=False, merge_type=ApplyMerge3,
300
interesting_ids=None, show_base=False):
301
"""Primary interface for merging.
303
typical use is probably
304
'merge_inner(branch, branch.get_revision_tree(other_revision),
305
branch.get_revision_tree(base_revision))'
307
merger = Merger(this_branch, other_tree, base_tree)
308
merger.backup_files = False
309
merger.merge_type = ApplyMerge3
310
merger.interesting_ids = interesting_ids
311
merger.show_base = show_base
312
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
314
ignore_zero=ignore_zero)
315
return merger.do_merge()
318
class Merger(object):
319
def __init__(self, this_branch, other_tree=None, base_tree=None):
320
object.__init__(self)
321
self.this_branch = this_branch
322
self.this_basis = this_branch.last_revision()
323
self.this_rev_id = None
324
self.this_tree = this_branch.working_tree()
325
self.this_revision_tree = None
326
self.other_tree = other_tree
327
self.base_tree = base_tree
328
self.ignore_zero = False
329
self.backup_files = False
330
self.interesting_ids = None
331
self.show_base = False
332
self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree,
335
def revision_tree(self, revision_id):
336
return self.this_branch.revision_tree(revision_id)
338
def ensure_revision_trees(self):
339
if self.this_revision_tree is None:
340
if self.this_rev_id is None:
342
if self.this_rev_id is None:
343
raise WorkingTreeNotRevision(self.this_tree)
344
self.this_revision_tree = self.this_branch.revision_tree(
347
if self.other_rev_id is None:
348
other_basis_tree = self.revision_tree(self.other_basis)
349
changes = compare_trees(self.other_tree, other_basis_tree)
350
if changes.has_changed():
351
raise WorkingTreeNotRevision(self.this_tree)
352
other_rev_id = other_basis
353
self.other_tree = other_basis_tree
356
def file_revisions(self, file_id):
357
self.ensure_revision_trees()
358
def get_id(tree, file_id):
359
revision_id = tree.inventory[file_id].revision
360
assert revision_id is not None
362
trees = (self.this_revision_tree, self.other_tree)
363
return [get_id(tree, file_id) for tree in trees]
366
def merge_factory(self, file_id, base, other):
367
if self.merge_type.history_based:
368
t_revid, o_revid = self.file_revisions(file_id)
369
weave = self.this_revision_tree.get_weave(file_id)
370
contents_change = self.merge_type(weave, t_revid, o_revid)
372
if self.show_base is True:
373
contents_change = self.merge_type(file_id, base, other,
376
contents_change = self.merge_type(file_id, base, other)
377
if self.backup_files:
378
contents_change = BackupBeforeChange(contents_change)
379
return contents_change
381
def check_basis(self, check_clean):
382
if self.this_basis is None:
383
raise BzrCommandError("This branch has no commits")
386
if self.this_basis != self.this_rev_id:
387
raise BzrCommandError("Working tree has uncommitted changes.")
389
def compare_basis(self):
390
changes = compare_trees(self.this_branch.working_tree(),
391
self.this_branch.basis_tree(), False)
392
if not changes.has_changed():
393
self.this_rev_id = self.this_basis
395
def set_interesting_files(self, file_list):
396
if file_list is None:
397
self.interesting_ids = None
400
interesting_ids = set()
401
for fname in file_list:
402
path = self.this_tree.relpath(fname)
404
for tree in (self.this_tree, self.base_tree, self.other_tree):
405
file_id = tree.inventory.path2id(path)
406
if file_id is not None:
407
interesting_ids.add(file_id)
410
raise BzrCommandError("%s is not a source file in any"
412
self.interesting_ids = interesting_ids
414
def set_pending(self):
415
if not self.base_is_ancestor:
417
if self.other_rev_id is None:
419
if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
421
self.this_branch.add_pending_merge(self.other_rev_id)
423
def set_other(self, other_revision):
424
other_branch, self.other_tree = get_tree(other_revision,
426
if other_revision[1] == -1:
427
self.other_rev_id = other_branch.last_revision()
428
if self.other_rev_id is None:
429
raise NoCommits(other_branch)
430
self.other_basis = self.other_rev_id
431
elif other_revision[1] is not None:
432
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
433
self.other_basis = self.other_rev_id
435
self.other_rev_id = None
436
self.other_basis = other_branch.last_revision()
437
if self.other_basis is None:
438
raise NoCommits(other_branch)
440
def set_base(self, base_revision):
441
if base_revision == [None, None]:
443
self.base_rev_id = common_ancestor(self.this_basis,
446
except NoCommonAncestor:
447
raise UnrelatedBranches()
448
self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
450
self.base_is_ancestor = True
452
base_branch, self.base_tree = get_tree(base_revision)
453
if base_revision[1] == -1:
454
self.base_rev_id = base_branch.last_revision()
455
elif base_revision[1] is None:
456
self.base_rev_id = None
458
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
459
fetch(from_branch=base_branch, to_branch=self.this_branch)
460
self.base_is_ancestor = is_ancestor(self.this_basis,
465
def get_inventory(tree):
466
return tree.inventory
468
inv_changes = merge_flex(self.this_tree, self.base_tree,
470
generate_changeset, get_inventory,
471
self.conflict_handler,
472
merge_factory=self.merge_factory,
473
interesting_ids=self.interesting_ids)
476
for id, path in inv_changes.iteritems():
481
assert path.startswith('.' + os.sep), "path is %s" % path
483
adjust_ids.append((path, id))
484
if len(adjust_ids) > 0:
485
self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
486
conflicts = self.conflict_handler.conflicts
487
self.conflict_handler.finalize()
490
def regen_inventory(self, new_entries):
491
old_entries = self.this_branch.read_working_inventory()
495
for path, file_id in new_entries:
498
new_entries_map[file_id] = path
500
def id2path(file_id):
501
path = new_entries_map.get(file_id)
504
entry = old_entries[file_id]
505
if entry.parent_id is None:
507
return os.path.join(id2path(entry.parent_id), entry.name)
509
for file_id in old_entries:
510
entry = old_entries[file_id]
511
path = id2path(file_id)
512
new_inventory[file_id] = (path, file_id, entry.parent_id,
514
by_path[path] = file_id
519
for path, file_id in new_entries:
521
del new_inventory[file_id]
524
new_path_list.append((path, file_id))
525
if file_id not in old_entries:
527
# Ensure no file is added before its parent
529
for path, file_id in new_path_list:
533
parent = by_path[os.path.dirname(path)]
534
abspath = os.path.join(self.this_tree.basedir, path)
535
kind = bzrlib.osutils.file_kind(abspath)
536
new_inventory[file_id] = (path, file_id, parent, kind)
537
by_path[path] = file_id
539
# Get a list in insertion order
540
new_inventory_list = new_inventory.values()
541
mutter ("""Inventory regeneration:
542
old length: %i insertions: %i deletions: %i new_length: %i"""\
543
% (len(old_entries), insertions, deletions,
544
len(new_inventory_list)))
545
assert len(new_inventory_list) == len(old_entries) + insertions\
547
new_inventory_list.sort()
548
return new_inventory_list
550
merge_types = { "merge3": (ApplyMerge3, "Native diff3-style merge"),
551
"diff3": (Diff3Merge, "Merge using external diff3"),
552
'weave': (WeaveMerge, "Weave-based merge")