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: 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)
222
return branch, get_revid_tree(branch, revision, local_branch)
224
def get_revid_tree(branch, revision, local_branch):
226
base_tree = branch.working_tree()
228
if local_branch is not None:
229
greedy_fetch(local_branch, branch, revision)
230
base_tree = local_branch.revision_tree(revision)
232
base_tree = branch.revision_tree(revision)
236
def file_exists(tree, file_id):
237
return tree.has_filename(tree.id2path(file_id))
240
def build_working_dir(to_dir):
241
"""Build a working directory in an empty directory.
243
to_dir is a directory containing branch metadata but no working files,
244
typically constructed by cloning an existing branch.
246
This is split out as a special idiomatic case of merge. It could
247
eventually be done by just building the tree directly calling into
248
lower-level code (e.g. constructing a changeset).
250
# RBC 20051019 is this not just 'export' ?
251
# Well, export doesn't take care of inventory...
252
this_branch = Branch.open_containing(to_dir)[0]
253
transform_tree(this_branch.working_tree(), this_branch.basis_tree())
255
def transform_tree(from_tree, to_tree):
256
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True)
258
def merge(other_revision, base_revision,
259
check_clean=True, ignore_zero=False,
260
this_dir=None, backup_files=False, merge_type=ApplyMerge3,
261
file_list=None, show_base=False):
262
"""Merge changes into a tree.
265
tuple(path, revno) Base for three-way merge.
266
If (None, None) then a base will be automatically determined.
268
tuple(path, revno) Other revision for three-way merge.
270
Directory to merge changes into; '.' by default.
272
If true, this_dir must have no uncommitted changes before the
274
ignore_zero - If true, suppress the "zero conflicts" message when
275
there are no conflicts; should be set when doing something we expect
276
to complete perfectly.
277
file_list - If true, merge only changes to selected files.
279
All available ancestors of other_revision and base_revision are
280
automatically pulled into the branch.
282
The revno may be -1 to indicate the last revision on the branch, which is the
285
This function is intended for use from the command line; programmatic clients
286
might prefer to call merge_inner(), which has less magic behavior.
288
# TODO: please check this docstring is true and accurate - mbp 20051024
291
this_branch = Branch.open_containing(this_dir)[0]
292
if show_base and not merge_type is ApplyMerge3:
293
raise BzrCommandError("Show-base is not supported for this merge"
294
" type. %s" % merge_type)
295
merger = Merger(this_branch)
296
merger.check_basis(check_clean)
297
merger.set_other(other_revision)
298
merger.set_base(base_revision)
299
merger.backup_files = backup_files
300
merger.merge_type = merge_type
301
merger.set_interesting_files(file_list)
302
merger.show_base = show_base
303
merger.conflict_handler = MergeConflictHandler(merger.this_tree,
306
ignore_zero=ignore_zero)
307
conflicts = merger.do_merge()
311
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
312
backup_files=False, merge_type=ApplyMerge3,
313
interesting_ids=None, show_base=False):
314
"""Primary interface for merging.
316
typical use is probably
317
'merge_inner(branch, branch.get_revision_tree(other_revision),
318
branch.get_revision_tree(base_revision))'
320
merger = Merger(this_branch, other_tree, base_tree)
321
merger.backup_files = False
322
merger.merge_type = ApplyMerge3
323
merger.interesting_ids = interesting_ids
324
merger.show_base = show_base
325
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
327
ignore_zero=ignore_zero)
328
return merger.do_merge()
331
class Merger(object):
332
def __init__(self, this_branch, other_tree=None, base_tree=None):
333
object.__init__(self)
334
self.this_branch = this_branch
335
self.this_basis = this_branch.last_revision()
336
self.this_rev_id = None
337
self.this_tree = this_branch.working_tree()
338
self.this_revision_tree = None
339
self.other_tree = other_tree
340
self.base_tree = base_tree
341
self.ignore_zero = False
342
self.backup_files = False
343
self.interesting_ids = None
344
self.show_base = False
345
self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree,
348
def revision_tree(self, revision_id):
349
return self.this_branch.revision_tree(revision_id)
351
def ensure_revision_trees(self):
352
if self.this_revision_tree is None:
353
if self.this_rev_id is None:
355
if self.this_rev_id is None:
356
raise WorkingTreeNotRevision(self.this_tree)
357
self.this_revision_tree = self.this_branch.revision_tree(
360
if self.other_rev_id is None:
361
other_basis_tree = self.revision_tree(self.other_basis)
362
changes = compare_trees(self.other_tree, other_basis_tree)
363
if changes.has_changed():
364
raise WorkingTreeNotRevision(self.this_tree)
365
other_rev_id = other_basis
366
self.other_tree = other_basis_tree
369
def file_revisions(self, file_id):
370
self.ensure_revision_trees()
371
def get_id(tree, file_id):
372
revision_id = tree.inventory[file_id].revision
373
assert revision_id is not None
375
trees = (self.this_revision_tree, self.other_tree)
376
return [get_id(tree, file_id) for tree in trees]
379
def merge_factory(self, file_id, base, other):
380
if self.merge_type.history_based:
381
t_revid, o_revid = self.file_revisions(file_id)
382
weave = self.this_revision_tree.get_weave(file_id)
383
contents_change = self.merge_type(weave, t_revid, o_revid)
385
if self.show_base is True:
386
contents_change = self.merge_type(file_id, base, other,
389
contents_change = self.merge_type(file_id, base, other)
390
if self.backup_files:
391
contents_change = BackupBeforeChange(contents_change)
392
return contents_change
394
def check_basis(self, check_clean):
395
if self.this_basis is None:
396
raise BzrCommandError("This branch has no commits")
399
if self.this_basis != self.this_rev_id:
400
raise BzrCommandError("Working tree has uncommitted changes.")
402
def compare_basis(self):
403
changes = compare_trees(self.this_branch.working_tree(),
404
self.this_branch.basis_tree(), False)
405
if not changes.has_changed():
406
self.this_rev_id = self.this_basis
408
def set_interesting_files(self, file_list):
409
if file_list is None:
410
self.interesting_ids = None
413
interesting_ids = set()
414
for fname in file_list:
415
path = self.this_tree.relpath(fname)
417
for tree in (self.this_tree, self.base_tree, self.other_tree):
418
file_id = tree.inventory.path2id(path)
419
if file_id is not None:
420
interesting_ids.add(file_id)
423
raise BzrCommandError("%s is not a source file in any"
425
self.interesting_ids = interesting_ids
427
def set_pending(self):
428
if not self.base_is_ancestor:
430
if self.other_rev_id is None:
432
if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
434
self.this_branch.add_pending_merge(self.other_rev_id)
436
def set_other(self, other_revision):
437
other_branch, self.other_tree = get_tree(other_revision,
439
if other_revision[1] == -1:
440
self.other_rev_id = other_branch.last_revision()
441
if self.other_rev_id is None:
442
raise NoCommits(other_branch)
443
self.other_basis = self.other_rev_id
444
elif other_revision[1] is not None:
445
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
446
self.other_basis = self.other_rev_id
448
self.other_rev_id = None
449
self.other_basis = other_branch.last_revision()
450
if self.other_basis is None:
451
raise NoCommits(other_branch)
452
fetch(from_branch=other_branch, to_branch=self.this_branch,
453
last_revision=self.other_basis)
455
def set_base(self, base_revision):
456
mutter("doing merge() with no base_revision specified")
457
if base_revision == [None, None]:
459
self.base_rev_id = common_ancestor(self.this_basis,
462
except NoCommonAncestor:
463
raise UnrelatedBranches()
464
self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
466
self.base_is_ancestor = True
468
base_branch, self.base_tree = get_tree(base_revision)
469
if base_revision[1] == -1:
470
self.base_rev_id = base_branch.last_revision()
471
elif base_revision[1] is None:
472
self.base_rev_id = None
474
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
475
fetch(from_branch=base_branch, to_branch=self.this_branch)
476
self.base_is_ancestor = is_ancestor(self.this_basis,
481
def get_inventory(tree):
482
return tree.inventory
484
inv_changes = merge_flex(self.this_tree, self.base_tree,
486
generate_changeset, get_inventory,
487
self.conflict_handler,
488
merge_factory=self.merge_factory,
489
interesting_ids=self.interesting_ids)
492
for id, path in inv_changes.iteritems():
497
assert path.startswith('.' + os.sep), "path is %s" % path
499
adjust_ids.append((path, id))
500
if len(adjust_ids) > 0:
501
self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
502
conflicts = self.conflict_handler.conflicts
503
self.conflict_handler.finalize()
506
def regen_inventory(self, new_entries):
507
old_entries = self.this_branch.read_working_inventory()
511
for path, file_id in new_entries:
514
new_entries_map[file_id] = path
516
def id2path(file_id):
517
path = new_entries_map.get(file_id)
520
entry = old_entries[file_id]
521
if entry.parent_id is None:
523
return os.path.join(id2path(entry.parent_id), entry.name)
525
for file_id in old_entries:
526
entry = old_entries[file_id]
527
path = id2path(file_id)
528
new_inventory[file_id] = (path, file_id, entry.parent_id,
530
by_path[path] = file_id
535
for path, file_id in new_entries:
537
del new_inventory[file_id]
540
new_path_list.append((path, file_id))
541
if file_id not in old_entries:
543
# Ensure no file is added before its parent
545
for path, file_id in new_path_list:
549
parent = by_path[os.path.dirname(path)]
550
abspath = os.path.join(self.this_tree.basedir, path)
551
kind = bzrlib.osutils.file_kind(abspath)
552
new_inventory[file_id] = (path, file_id, parent, kind)
553
by_path[path] = file_id
555
# Get a list in insertion order
556
new_inventory_list = new_inventory.values()
557
mutter ("""Inventory regeneration:
558
old length: %i insertions: %i deletions: %i new_length: %i"""\
559
% (len(old_entries), insertions, deletions,
560
len(new_inventory_list)))
561
assert len(new_inventory_list) == len(old_entries) + insertions\
563
new_inventory_list.sort()
564
return new_inventory_list
566
merge_types = { "merge3": (ApplyMerge3, "Native diff3-style merge"),
567
"diff3": (Diff3Merge, "Merge using external diff3"),
568
'weave': (WeaveMerge, "Weave-based merge")