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, NULL_REVISION
36
from bzrlib.osutils import rename
37
from bzrlib.revision import common_ancestor, MultipleRevisionSources
38
from bzrlib.errors import NoSuchRevision
40
# TODO: Report back as changes are merged in
42
# TODO: build_working_dir can be built on something simpler than merge()
44
# FIXME: merge() parameters seem oriented towards the command line
45
# NOTABUG: merge is a helper for commandline functions. merge_inner is the
46
# the core functionality.
48
# comments from abentley on irc: merge happens in two stages, each
49
# of which generates a changeset object
51
# stage 1: generate OLD->OTHER,
52
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
54
class MergeConflictHandler(ExceptionConflictHandler):
55
"""Handle conflicts encountered while merging.
57
This subclasses ExceptionConflictHandler, so that any types of
58
conflict that are not explicitly handled cause an exception and
61
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
62
ExceptionConflictHandler.__init__(self)
64
self.ignore_zero = ignore_zero
65
self.this_tree = this_tree
66
self.base_tree = base_tree
67
self.other_tree = other_tree
69
def copy(self, source, dest):
70
"""Copy the text and mode of a file
71
:param source: The path of the file to copy
72
:param dest: The distination file to create
74
s_file = file(source, "rb")
75
d_file = file(dest, "wb")
78
os.chmod(dest, 0777 & os.stat(source).st_mode)
80
def dump(self, lines, dest):
81
"""Copy the text and mode of a file
82
:param source: The path of the file to copy
83
:param dest: The distination file to create
85
d_file = file(dest, "wb")
89
def add_suffix(self, name, suffix, last_new_name=None):
90
"""Rename a file to append a suffix. If the new name exists, the
91
suffix is added repeatedly until a non-existant name is found
93
:param name: The path of the file
94
:param suffix: The suffix to append
95
:param last_new_name: (used for recursive calls) the last name tried
97
if last_new_name is None:
99
new_name = last_new_name+suffix
101
rename(name, new_name)
104
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
106
return self.add_suffix(name, suffix, last_new_name=new_name)
108
def conflict(self, text):
113
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
115
Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER. The
116
main file will be a version with diff3 conflicts.
117
:param new_file: Path to the output file with diff3 markers
118
:param this_path: Path to the file text for the THIS tree
119
:param base_path: Path to the file text for the BASE tree
120
:param other_path: Path to the file text for the OTHER tree
122
self.add_suffix(this_path, ".THIS")
123
self.dump(base_lines, this_path+".BASE")
124
self.dump(other_lines, this_path+".OTHER")
125
rename(new_file, this_path)
126
self.conflict("Diff3 conflict encountered in %s" % this_path)
128
def weave_merge_conflict(self, filename, weave, other_i, out_file):
130
Handle weave conflicts by producing a .THIS, and .OTHER. The
131
main file will be a version with diff3-style conflicts.
133
self.add_suffix(filename, ".THIS")
135
self.dump(weave.get_iter(other_i), filename+".OTHER")
136
self.conflict("Text conflict encountered in %s" % filename)
138
def new_contents_conflict(self, filename, other_contents):
139
"""Conflicting contents for newly added file."""
140
other_contents(filename + ".OTHER", self, False)
141
self.conflict("Conflict in newly added file %s" % filename)
144
def target_exists(self, entry, target, old_path):
145
"""Handle the case when the target file or dir exists"""
146
moved_path = self.add_suffix(target, ".moved")
147
self.conflict("Moved existing %s to %s" % (target, moved_path))
149
def rmdir_non_empty(self, filename):
150
"""Handle the case where the dir to be removed still has contents"""
151
self.conflict("Directory %s not removed because it is not empty"\
155
def rem_contents_conflict(self, filename, this_contents, base_contents):
156
base_contents(filename+".BASE", self, False)
157
this_contents(filename+".THIS", self, False)
158
return ReplaceContents(this_contents, None)
160
def rem_contents_conflict(self, filename, this_contents, base_contents):
161
base_contents(filename+".BASE", self, False)
162
this_contents(filename+".THIS", self, False)
163
self.conflict("Other branch deleted locally modified file %s" %
165
return ReplaceContents(this_contents, None)
167
def abs_this_path(self, file_id):
168
"""Return the absolute path for a file_id in the this tree."""
169
return self.this_tree.id2abspath(file_id)
171
def add_missing_parents(self, file_id, tree):
172
"""If some of the parents for file_id are missing, add them."""
173
entry = tree.inventory[file_id]
174
if entry.parent_id not in self.this_tree:
175
return self.create_all_missing(entry.parent_id, tree)
177
return self.abs_this_path(entry.parent_id)
179
def create_all_missing(self, file_id, tree):
180
"""Add contents for a file_id and all its parents to a tree."""
181
entry = tree.inventory[file_id]
182
if entry.parent_id is not None and entry.parent_id not in self.this_tree:
183
abspath = self.create_all_missing(entry.parent_id, tree)
185
abspath = self.abs_this_path(entry.parent_id)
186
entry_path = os.path.join(abspath, entry.name)
187
if not os.path.isdir(entry_path):
188
self.create(file_id, entry_path, tree)
191
def create(self, file_id, path, tree, reverse=False):
192
"""Uses tree data to create a filesystem object for the file_id"""
193
from changeset import get_contents
194
get_contents(tree, file_id)(path, self, reverse)
196
def missing_for_merge(self, file_id, other_path):
197
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
198
self.conflict("Other branch modified locally deleted file %s" %
200
parent_dir = self.add_missing_parents(file_id, self.other_tree)
201
stem = os.path.join(parent_dir, os.path.basename(other_path))
202
self.create(file_id, stem+".OTHER", self.other_tree)
203
self.create(file_id, stem+".BASE", self.base_tree)
205
def threeway_contents_conflict(filename, this_contents, base_contents,
207
self.conflict("Three-way conflict merging %s" % filename)
210
if not self.ignore_zero:
211
note("%d conflicts encountered.\n" % self.conflicts)
213
def get_tree(treespec, local_branch=None):
214
location, revno = treespec
215
branch = Branch.open_containing(location)[0]
219
revision = branch.last_revision()
221
revision = branch.get_rev_id(revno)
223
revision = NULL_REVISION
224
return branch, get_revid_tree(branch, revision, local_branch)
226
def get_revid_tree(branch, revision, local_branch):
228
base_tree = branch.working_tree()
230
if local_branch is not None:
231
greedy_fetch(local_branch, branch, revision)
232
base_tree = local_branch.revision_tree(revision)
234
base_tree = branch.revision_tree(revision)
238
def file_exists(tree, file_id):
239
return tree.has_filename(tree.id2path(file_id))
242
def build_working_dir(to_dir):
243
"""Build a working directory in an empty directory.
245
to_dir is a directory containing branch metadata but no working files,
246
typically constructed by cloning an existing branch.
248
This is split out as a special idiomatic case of merge. It could
249
eventually be done by just building the tree directly calling into
250
lower-level code (e.g. constructing a changeset).
252
# RBC 20051019 is this not just 'export' ?
253
# Well, export doesn't take care of inventory...
254
this_branch = Branch.open_containing(to_dir)[0]
255
transform_tree(this_branch.working_tree(), this_branch.basis_tree())
257
def transform_tree(from_tree, to_tree):
258
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True)
260
def merge(other_revision, base_revision,
261
check_clean=True, ignore_zero=False,
262
this_dir=None, backup_files=False, merge_type=ApplyMerge3,
263
file_list=None, show_base=False):
264
"""Merge changes into a tree.
267
list(path, revno) Base for three-way merge.
268
If [None, None] then a base will be automatically determined.
270
list(path, revno) Other revision for three-way merge.
272
Directory to merge changes into; '.' by default.
274
If true, this_dir must have no uncommitted changes before the
276
ignore_zero - If true, suppress the "zero conflicts" message when
277
there are no conflicts; should be set when doing something we expect
278
to complete perfectly.
279
file_list - If supplied, merge only changes to selected files.
281
All available ancestors of other_revision and base_revision are
282
automatically pulled into the branch.
284
The revno may be -1 to indicate the last revision on the branch, which is
287
This function is intended for use from the command line; programmatic
288
clients might prefer to call merge_inner(), which has less magic behavior.
292
this_branch = Branch.open_containing(this_dir)[0]
293
if show_base and not merge_type is ApplyMerge3:
294
raise BzrCommandError("Show-base is not supported for this merge"
295
" type. %s" % merge_type)
296
merger = Merger(this_branch)
297
merger.check_basis(check_clean)
298
merger.set_other(other_revision)
299
merger.set_base(base_revision)
300
merger.backup_files = backup_files
301
merger.merge_type = merge_type
302
merger.set_interesting_files(file_list)
303
merger.show_base = show_base
304
merger.conflict_handler = MergeConflictHandler(merger.this_tree,
307
ignore_zero=ignore_zero)
308
conflicts = merger.do_merge()
312
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
313
backup_files=False, merge_type=ApplyMerge3,
314
interesting_ids=None, show_base=False):
315
"""Primary interface for merging.
317
typical use is probably
318
'merge_inner(branch, branch.get_revision_tree(other_revision),
319
branch.get_revision_tree(base_revision))'
321
merger = Merger(this_branch, other_tree, base_tree)
322
merger.backup_files = False
323
merger.merge_type = ApplyMerge3
324
merger.interesting_ids = interesting_ids
325
merger.show_base = show_base
326
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
328
ignore_zero=ignore_zero)
329
return merger.do_merge()
332
class Merger(object):
333
def __init__(self, this_branch, other_tree=None, base_tree=None):
334
object.__init__(self)
335
self.this_branch = this_branch
336
self.this_basis = this_branch.last_revision()
337
self.this_rev_id = None
338
self.this_tree = this_branch.working_tree()
339
self.this_revision_tree = None
340
self.other_tree = other_tree
341
self.base_tree = base_tree
342
self.ignore_zero = False
343
self.backup_files = False
344
self.interesting_ids = None
345
self.show_base = False
346
self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree,
349
def revision_tree(self, revision_id):
350
return self.this_branch.revision_tree(revision_id)
352
def ensure_revision_trees(self):
353
if self.this_revision_tree is None:
354
if self.this_rev_id is None:
356
if self.this_rev_id is None:
357
raise WorkingTreeNotRevision(self.this_tree)
358
self.this_revision_tree = self.this_branch.revision_tree(
361
if self.other_rev_id is None:
362
other_basis_tree = self.revision_tree(self.other_basis)
363
changes = compare_trees(self.other_tree, other_basis_tree)
364
if changes.has_changed():
365
raise WorkingTreeNotRevision(self.this_tree)
366
other_rev_id = other_basis
367
self.other_tree = other_basis_tree
370
def file_revisions(self, file_id):
371
self.ensure_revision_trees()
372
def get_id(tree, file_id):
373
revision_id = tree.inventory[file_id].revision
374
assert revision_id is not None
376
trees = (self.this_revision_tree, self.other_tree)
377
return [get_id(tree, file_id) for tree in trees]
380
def merge_factory(self, file_id, base, other):
381
if self.merge_type.history_based:
382
t_revid, o_revid = self.file_revisions(file_id)
383
weave = self.this_revision_tree.get_weave(file_id)
384
contents_change = self.merge_type(weave, t_revid, o_revid)
386
if self.show_base is True:
387
contents_change = self.merge_type(file_id, base, other,
390
contents_change = self.merge_type(file_id, base, other)
391
if self.backup_files:
392
contents_change = BackupBeforeChange(contents_change)
393
return contents_change
395
def check_basis(self, check_clean):
396
if self.this_basis is None:
397
raise BzrCommandError("This branch has no commits")
400
if self.this_basis != self.this_rev_id:
401
raise BzrCommandError("Working tree has uncommitted changes.")
403
def compare_basis(self):
404
changes = compare_trees(self.this_branch.working_tree(),
405
self.this_branch.basis_tree(), False)
406
if not changes.has_changed():
407
self.this_rev_id = self.this_basis
409
def set_interesting_files(self, file_list):
410
if file_list is None:
411
self.interesting_ids = None
414
interesting_ids = set()
415
for fname in file_list:
416
path = self.this_tree.relpath(fname)
418
for tree in (self.this_tree, self.base_tree, self.other_tree):
419
file_id = tree.inventory.path2id(path)
420
if file_id is not None:
421
interesting_ids.add(file_id)
424
raise BzrCommandError("%s is not a source file in any"
426
self.interesting_ids = interesting_ids
428
def set_pending(self):
429
if not self.base_is_ancestor:
431
if self.other_rev_id is None:
433
if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
435
self.this_branch.add_pending_merge(self.other_rev_id)
437
def set_other(self, other_revision):
438
other_branch, self.other_tree = get_tree(other_revision,
440
if other_revision[1] == -1:
441
self.other_rev_id = other_branch.last_revision()
442
if self.other_rev_id is None:
443
raise NoCommits(other_branch)
444
self.other_basis = self.other_rev_id
445
elif other_revision[1] is not None:
446
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
447
self.other_basis = self.other_rev_id
449
self.other_rev_id = None
450
self.other_basis = other_branch.last_revision()
451
if self.other_basis is None:
452
raise NoCommits(other_branch)
453
fetch(from_branch=other_branch, to_branch=self.this_branch,
454
last_revision=self.other_basis)
456
def set_base(self, base_revision):
457
mutter("doing merge() with no base_revision specified")
458
if base_revision == [None, None]:
460
self.base_rev_id = common_ancestor(self.this_basis,
463
except NoCommonAncestor:
464
raise UnrelatedBranches()
465
self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
467
self.base_is_ancestor = True
469
base_branch, self.base_tree = get_tree(base_revision)
470
if base_revision[1] == -1:
471
self.base_rev_id = base_branch.last_revision()
472
elif base_revision[1] is None:
473
self.base_rev_id = None
475
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
476
fetch(from_branch=base_branch, to_branch=self.this_branch)
477
self.base_is_ancestor = is_ancestor(self.this_basis,
482
def get_inventory(tree):
483
return tree.inventory
485
inv_changes = merge_flex(self.this_tree, self.base_tree,
487
generate_changeset, get_inventory,
488
self.conflict_handler,
489
merge_factory=self.merge_factory,
490
interesting_ids=self.interesting_ids)
493
for id, path in inv_changes.iteritems():
498
assert path.startswith('.' + os.sep), "path is %s" % path
500
adjust_ids.append((path, id))
501
if len(adjust_ids) > 0:
502
self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
503
conflicts = self.conflict_handler.conflicts
504
self.conflict_handler.finalize()
507
def regen_inventory(self, new_entries):
508
old_entries = self.this_branch.read_working_inventory()
512
for path, file_id in new_entries:
515
new_entries_map[file_id] = path
517
def id2path(file_id):
518
path = new_entries_map.get(file_id)
521
entry = old_entries[file_id]
522
if entry.parent_id is None:
524
return os.path.join(id2path(entry.parent_id), entry.name)
526
for file_id in old_entries:
527
entry = old_entries[file_id]
528
path = id2path(file_id)
529
new_inventory[file_id] = (path, file_id, entry.parent_id,
531
by_path[path] = file_id
536
for path, file_id in new_entries:
538
del new_inventory[file_id]
541
new_path_list.append((path, file_id))
542
if file_id not in old_entries:
544
# Ensure no file is added before its parent
546
for path, file_id in new_path_list:
550
parent = by_path[os.path.dirname(path)]
551
abspath = os.path.join(self.this_tree.basedir, path)
552
kind = bzrlib.osutils.file_kind(abspath)
553
new_inventory[file_id] = (path, file_id, parent, kind)
554
by_path[path] = file_id
556
# Get a list in insertion order
557
new_inventory_list = new_inventory.values()
558
mutter ("""Inventory regeneration:
559
old length: %i insertions: %i deletions: %i new_length: %i"""\
560
% (len(old_entries), insertions, deletions,
561
len(new_inventory_list)))
562
assert len(new_inventory_list) == len(old_entries) + insertions\
564
new_inventory_list.sort()
565
return new_inventory_list
567
merge_types = { "merge3": (ApplyMerge3, "Native diff3-style merge"),
568
"diff3": (Diff3Merge, "Merge using external diff3"),
569
'weave': (WeaveMerge, "Weave-based merge")