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, NotBranchError
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, fix_inventory=True):
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)
102
if fix_inventory is True:
104
relpath = self.this_tree.relpath(name)
105
except NotBranchError:
107
if relpath is not None:
108
file_id = self.this_tree.path2id(relpath)
109
if file_id is not None:
110
new_path = self.this_tree.relpath(new_name)
111
rename(new_name, name)
112
self.this_tree.branch.rename_one(relpath, new_path)
113
assert self.this_tree.id2path(file_id) == relpath
114
self.this_tree._inventory = self.this_tree.branch.inventory
115
assert self.this_tree.id2path(file_id) == new_path
117
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
119
return self.add_suffix(name, suffix, last_new_name=new_name,
120
fix_inventory=fix_inventory)
123
def conflict(self, text):
128
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
130
Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER. The
131
main file will be a version with diff3 conflicts.
132
:param new_file: Path to the output file with diff3 markers
133
:param this_path: Path to the file text for the THIS tree
134
:param base_path: Path to the file text for the BASE tree
135
:param other_path: Path to the file text for the OTHER tree
137
self.add_suffix(this_path, ".THIS", fix_inventory=False)
138
self.dump(base_lines, this_path+".BASE")
139
self.dump(other_lines, this_path+".OTHER")
140
rename(new_file, this_path)
141
self.conflict("Diff3 conflict encountered in %s" % this_path)
143
def weave_merge_conflict(self, filename, weave, other_i, out_file):
145
Handle weave conflicts by producing a .THIS, and .OTHER. The
146
main file will be a version with diff3-style conflicts.
148
self.add_suffix(filename, ".THIS")
150
self.dump(weave.get_iter(other_i), filename+".OTHER")
151
self.conflict("Text conflict encountered in %s" % filename)
153
def new_contents_conflict(self, filename, other_contents):
154
"""Conflicting contents for newly added file."""
155
other_contents(filename + ".OTHER", self, False)
156
self.conflict("Conflict in newly added file %s" % filename)
159
def target_exists(self, entry, target, old_path):
160
"""Handle the case when the target file or dir exists"""
161
moved_path = self.add_suffix(target, ".moved")
162
self.conflict("Moved existing %s to %s" % (target, moved_path))
164
def rmdir_non_empty(self, filename):
165
"""Handle the case where the dir to be removed still has contents"""
166
self.conflict("Directory %s not removed because it is not empty"\
170
def rem_contents_conflict(self, filename, this_contents, base_contents):
171
base_contents(filename+".BASE", self, False)
172
this_contents(filename+".THIS", self, False)
173
return ReplaceContents(this_contents, None)
175
def rem_contents_conflict(self, filename, this_contents, base_contents):
176
base_contents(filename+".BASE", self, False)
177
this_contents(filename+".THIS", self, False)
178
self.conflict("Other branch deleted locally modified file %s" %
180
return ReplaceContents(this_contents, None)
182
def abs_this_path(self, file_id):
183
"""Return the absolute path for a file_id in the this tree."""
184
return self.this_tree.id2abspath(file_id)
186
def add_missing_parents(self, file_id, tree):
187
"""If some of the parents for file_id are missing, add them."""
188
entry = tree.inventory[file_id]
189
if entry.parent_id not in self.this_tree:
190
return self.create_all_missing(entry.parent_id, tree)
192
return self.abs_this_path(entry.parent_id)
194
def create_all_missing(self, file_id, tree):
195
"""Add contents for a file_id and all its parents to a tree."""
196
entry = tree.inventory[file_id]
197
if entry.parent_id is not None and entry.parent_id not in self.this_tree:
198
abspath = self.create_all_missing(entry.parent_id, tree)
200
abspath = self.abs_this_path(entry.parent_id)
201
entry_path = os.path.join(abspath, entry.name)
202
if not os.path.isdir(entry_path):
203
self.create(file_id, entry_path, tree)
206
def create(self, file_id, path, tree, reverse=False):
207
"""Uses tree data to create a filesystem object for the file_id"""
208
from changeset import get_contents
209
get_contents(tree, file_id)(path, self, reverse)
211
def missing_for_merge(self, file_id, other_path):
212
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
213
self.conflict("Other branch modified locally deleted file %s" %
215
parent_dir = self.add_missing_parents(file_id, self.other_tree)
216
stem = os.path.join(parent_dir, os.path.basename(other_path))
217
self.create(file_id, stem+".OTHER", self.other_tree)
218
self.create(file_id, stem+".BASE", self.base_tree)
220
def threeway_contents_conflict(filename, this_contents, base_contents,
222
self.conflict("Three-way conflict merging %s" % filename)
225
if not self.ignore_zero:
226
note("%d conflicts encountered.\n" % self.conflicts)
228
def get_tree(treespec, local_branch=None):
229
location, revno = treespec
230
branch = Branch.open_containing(location)[0]
234
revision = branch.last_revision()
236
revision = branch.get_rev_id(revno)
238
revision = NULL_REVISION
239
return branch, get_revid_tree(branch, revision, local_branch)
241
def get_revid_tree(branch, revision, local_branch):
243
base_tree = branch.working_tree()
245
if local_branch is not None:
246
greedy_fetch(local_branch, branch, revision)
247
base_tree = local_branch.revision_tree(revision)
249
base_tree = branch.revision_tree(revision)
253
def file_exists(tree, file_id):
254
return tree.has_filename(tree.id2path(file_id))
257
def build_working_dir(to_dir):
258
"""Build a working directory in an empty directory.
260
to_dir is a directory containing branch metadata but no working files,
261
typically constructed by cloning an existing branch.
263
This is split out as a special idiomatic case of merge. It could
264
eventually be done by just building the tree directly calling into
265
lower-level code (e.g. constructing a changeset).
267
# RBC 20051019 is this not just 'export' ?
268
# Well, export doesn't take care of inventory...
269
this_branch = Branch.open_containing(to_dir)[0]
270
transform_tree(this_branch.working_tree(), this_branch.basis_tree())
272
def transform_tree(from_tree, to_tree):
273
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True)
275
def merge(other_revision, base_revision,
276
check_clean=True, ignore_zero=False,
277
this_dir=None, backup_files=False, merge_type=ApplyMerge3,
278
file_list=None, show_base=False, reprocess=False):
279
"""Merge changes into a tree.
282
list(path, revno) Base for three-way merge.
283
If [None, None] then a base will be automatically determined.
285
list(path, revno) Other revision for three-way merge.
287
Directory to merge changes into; '.' by default.
289
If true, this_dir must have no uncommitted changes before the
291
ignore_zero - If true, suppress the "zero conflicts" message when
292
there are no conflicts; should be set when doing something we expect
293
to complete perfectly.
294
file_list - If supplied, merge only changes to selected files.
296
All available ancestors of other_revision and base_revision are
297
automatically pulled into the branch.
299
The revno may be -1 to indicate the last revision on the branch, which is
302
This function is intended for use from the command line; programmatic
303
clients might prefer to call merge_inner(), which has less magic behavior.
307
this_branch = Branch.open_containing(this_dir)[0]
308
if show_base and not merge_type is ApplyMerge3:
309
raise BzrCommandError("Show-base is not supported for this merge"
310
" type. %s" % merge_type)
311
if reprocess and not merge_type is ApplyMerge3:
312
raise BzrCommandError("Reprocess is not supported for this merge"
313
" type. %s" % merge_type)
314
if reprocess and show_base:
315
raise BzrCommandError("Cannot reprocess and show base.")
316
merger = Merger(this_branch)
317
merger.check_basis(check_clean)
318
merger.set_other(other_revision)
319
merger.set_base(base_revision)
320
merger.backup_files = backup_files
321
merger.merge_type = merge_type
322
merger.set_interesting_files(file_list)
323
merger.show_base = show_base
324
merger.reprocess = reprocess
325
merger.conflict_handler = MergeConflictHandler(merger.this_tree,
328
ignore_zero=ignore_zero)
329
conflicts = merger.do_merge()
333
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
334
backup_files=False, merge_type=ApplyMerge3,
335
interesting_ids=None, show_base=False, reprocess=False):
336
"""Primary interface for merging.
338
typical use is probably
339
'merge_inner(branch, branch.get_revision_tree(other_revision),
340
branch.get_revision_tree(base_revision))'
342
merger = Merger(this_branch, other_tree, base_tree)
343
merger.backup_files = False
344
merger.merge_type = ApplyMerge3
345
merger.interesting_ids = interesting_ids
346
merger.show_base = show_base
347
merger.reprocess = reprocess
348
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
350
ignore_zero=ignore_zero)
351
return merger.do_merge()
354
class Merger(object):
355
def __init__(self, this_branch, other_tree=None, base_tree=None):
356
object.__init__(self)
357
self.this_branch = this_branch
358
self.this_basis = this_branch.last_revision()
359
self.this_rev_id = None
360
self.this_tree = this_branch.working_tree()
361
self.this_revision_tree = None
362
self.other_tree = other_tree
363
self.base_tree = base_tree
364
self.ignore_zero = False
365
self.backup_files = False
366
self.interesting_ids = None
367
self.show_base = False
368
self.reprocess = False
369
self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree,
372
def revision_tree(self, revision_id):
373
return self.this_branch.revision_tree(revision_id)
375
def ensure_revision_trees(self):
376
if self.this_revision_tree is None:
377
if self.this_rev_id is None:
379
if self.this_rev_id is None:
380
raise WorkingTreeNotRevision(self.this_tree)
381
self.this_revision_tree = self.this_branch.revision_tree(
384
if self.other_rev_id is None:
385
other_basis_tree = self.revision_tree(self.other_basis)
386
changes = compare_trees(self.other_tree, other_basis_tree)
387
if changes.has_changed():
388
raise WorkingTreeNotRevision(self.this_tree)
389
other_rev_id = other_basis
390
self.other_tree = other_basis_tree
393
def file_revisions(self, file_id):
394
self.ensure_revision_trees()
395
def get_id(tree, file_id):
396
revision_id = tree.inventory[file_id].revision
397
assert revision_id is not None
399
trees = (self.this_revision_tree, self.other_tree)
400
return [get_id(tree, file_id) for tree in trees]
403
def merge_factory(self, file_id, base, other):
404
if self.merge_type.history_based:
405
t_revid, o_revid = self.file_revisions(file_id)
406
weave = self.this_revision_tree.get_weave(file_id)
407
contents_change = self.merge_type(weave, t_revid, o_revid)
409
if self.show_base is True or self.reprocess is True:
410
contents_change = self.merge_type(file_id, base, other,
411
show_base=self.show_base,
412
reprocess=self.reprocess)
414
contents_change = self.merge_type(file_id, base, other)
415
if self.backup_files:
416
contents_change = BackupBeforeChange(contents_change)
417
return contents_change
419
def check_basis(self, check_clean):
420
if self.this_basis is None:
421
raise BzrCommandError("This branch has no commits")
424
if self.this_basis != self.this_rev_id:
425
raise BzrCommandError("Working tree has uncommitted changes.")
427
def compare_basis(self):
428
changes = compare_trees(self.this_branch.working_tree(),
429
self.this_branch.basis_tree(), False)
430
if not changes.has_changed():
431
self.this_rev_id = self.this_basis
433
def set_interesting_files(self, file_list):
434
if file_list is None:
435
self.interesting_ids = None
438
interesting_ids = set()
439
for fname in file_list:
440
path = self.this_tree.relpath(fname)
442
for tree in (self.this_tree, self.base_tree, self.other_tree):
443
file_id = tree.inventory.path2id(path)
444
if file_id is not None:
445
interesting_ids.add(file_id)
448
raise BzrCommandError("%s is not a source file in any"
450
self.interesting_ids = interesting_ids
452
def set_pending(self):
453
if not self.base_is_ancestor:
455
if self.other_rev_id is None:
457
if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
459
self.this_branch.add_pending_merge(self.other_rev_id)
461
def set_other(self, other_revision):
462
other_branch, self.other_tree = get_tree(other_revision,
464
if other_revision[1] == -1:
465
self.other_rev_id = other_branch.last_revision()
466
if self.other_rev_id is None:
467
raise NoCommits(other_branch)
468
self.other_basis = self.other_rev_id
469
elif other_revision[1] is not None:
470
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
471
self.other_basis = self.other_rev_id
473
self.other_rev_id = None
474
self.other_basis = other_branch.last_revision()
475
if self.other_basis is None:
476
raise NoCommits(other_branch)
477
fetch(from_branch=other_branch, to_branch=self.this_branch,
478
last_revision=self.other_basis)
480
def set_base(self, base_revision):
481
mutter("doing merge() with no base_revision specified")
482
if base_revision == [None, None]:
484
self.base_rev_id = common_ancestor(self.this_basis,
487
except NoCommonAncestor:
488
raise UnrelatedBranches()
489
self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
491
self.base_is_ancestor = True
493
base_branch, self.base_tree = get_tree(base_revision)
494
if base_revision[1] == -1:
495
self.base_rev_id = base_branch.last_revision()
496
elif base_revision[1] is None:
497
self.base_rev_id = None
499
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
500
fetch(from_branch=base_branch, to_branch=self.this_branch)
501
self.base_is_ancestor = is_ancestor(self.this_basis,
506
def get_inventory(tree):
507
return tree.inventory
509
inv_changes = merge_flex(self.this_tree, self.base_tree,
511
generate_changeset, get_inventory,
512
self.conflict_handler,
513
merge_factory=self.merge_factory,
514
interesting_ids=self.interesting_ids)
517
for id, path in inv_changes.iteritems():
522
assert path.startswith('.' + os.sep), "path is %s" % path
524
adjust_ids.append((path, id))
525
if len(adjust_ids) > 0:
526
self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
527
conflicts = self.conflict_handler.conflicts
528
self.conflict_handler.finalize()
531
def regen_inventory(self, new_entries):
532
old_entries = self.this_branch.read_working_inventory()
536
for path, file_id in new_entries:
539
new_entries_map[file_id] = path
541
def id2path(file_id):
542
path = new_entries_map.get(file_id)
545
entry = old_entries[file_id]
546
if entry.parent_id is None:
548
return os.path.join(id2path(entry.parent_id), entry.name)
550
for file_id in old_entries:
551
entry = old_entries[file_id]
552
path = id2path(file_id)
553
new_inventory[file_id] = (path, file_id, entry.parent_id,
555
by_path[path] = file_id
560
for path, file_id in new_entries:
562
del new_inventory[file_id]
565
new_path_list.append((path, file_id))
566
if file_id not in old_entries:
568
# Ensure no file is added before its parent
570
for path, file_id in new_path_list:
574
parent = by_path[os.path.dirname(path)]
575
abspath = os.path.join(self.this_tree.basedir, path)
576
kind = bzrlib.osutils.file_kind(abspath)
577
new_inventory[file_id] = (path, file_id, parent, kind)
578
by_path[path] = file_id
580
# Get a list in insertion order
581
new_inventory_list = new_inventory.values()
582
mutter ("""Inventory regeneration:
583
old length: %i insertions: %i deletions: %i new_length: %i"""\
584
% (len(old_entries), insertions, deletions,
585
len(new_inventory_list)))
586
assert len(new_inventory_list) == len(old_entries) + insertions\
588
new_inventory_list.sort()
589
return new_inventory_list
591
merge_types = { "merge3": (ApplyMerge3, "Native diff3-style merge"),
592
"diff3": (Diff3Merge, "Merge using external diff3"),
593
'weave': (WeaveMerge, "Weave-based merge")