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):
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
merger = Merger(this_branch)
312
merger.check_basis(check_clean)
313
merger.set_other(other_revision)
314
merger.set_base(base_revision)
315
merger.backup_files = backup_files
316
merger.merge_type = merge_type
317
merger.set_interesting_files(file_list)
318
merger.show_base = show_base
319
merger.conflict_handler = MergeConflictHandler(merger.this_tree,
322
ignore_zero=ignore_zero)
323
conflicts = merger.do_merge()
327
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
328
backup_files=False, merge_type=ApplyMerge3,
329
interesting_ids=None, show_base=False):
330
"""Primary interface for merging.
332
typical use is probably
333
'merge_inner(branch, branch.get_revision_tree(other_revision),
334
branch.get_revision_tree(base_revision))'
336
merger = Merger(this_branch, other_tree, base_tree)
337
merger.backup_files = False
338
merger.merge_type = ApplyMerge3
339
merger.interesting_ids = interesting_ids
340
merger.show_base = show_base
341
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
343
ignore_zero=ignore_zero)
344
return merger.do_merge()
347
class Merger(object):
348
def __init__(self, this_branch, other_tree=None, base_tree=None):
349
object.__init__(self)
350
self.this_branch = this_branch
351
self.this_basis = this_branch.last_revision()
352
self.this_rev_id = None
353
self.this_tree = this_branch.working_tree()
354
self.this_revision_tree = None
355
self.other_tree = other_tree
356
self.base_tree = base_tree
357
self.ignore_zero = False
358
self.backup_files = False
359
self.interesting_ids = None
360
self.show_base = False
361
self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree,
364
def revision_tree(self, revision_id):
365
return self.this_branch.revision_tree(revision_id)
367
def ensure_revision_trees(self):
368
if self.this_revision_tree is None:
369
if self.this_rev_id is None:
371
if self.this_rev_id is None:
372
raise WorkingTreeNotRevision(self.this_tree)
373
self.this_revision_tree = self.this_branch.revision_tree(
376
if self.other_rev_id is None:
377
other_basis_tree = self.revision_tree(self.other_basis)
378
changes = compare_trees(self.other_tree, other_basis_tree)
379
if changes.has_changed():
380
raise WorkingTreeNotRevision(self.this_tree)
381
other_rev_id = other_basis
382
self.other_tree = other_basis_tree
385
def file_revisions(self, file_id):
386
self.ensure_revision_trees()
387
def get_id(tree, file_id):
388
revision_id = tree.inventory[file_id].revision
389
assert revision_id is not None
391
trees = (self.this_revision_tree, self.other_tree)
392
return [get_id(tree, file_id) for tree in trees]
395
def merge_factory(self, file_id, base, other):
396
if self.merge_type.history_based:
397
t_revid, o_revid = self.file_revisions(file_id)
398
weave = self.this_revision_tree.get_weave(file_id)
399
contents_change = self.merge_type(weave, t_revid, o_revid)
401
if self.show_base is True:
402
contents_change = self.merge_type(file_id, base, other,
405
contents_change = self.merge_type(file_id, base, other)
406
if self.backup_files:
407
contents_change = BackupBeforeChange(contents_change)
408
return contents_change
410
def check_basis(self, check_clean):
411
if self.this_basis is None:
412
raise BzrCommandError("This branch has no commits")
415
if self.this_basis != self.this_rev_id:
416
raise BzrCommandError("Working tree has uncommitted changes.")
418
def compare_basis(self):
419
changes = compare_trees(self.this_branch.working_tree(),
420
self.this_branch.basis_tree(), False)
421
if not changes.has_changed():
422
self.this_rev_id = self.this_basis
424
def set_interesting_files(self, file_list):
425
if file_list is None:
426
self.interesting_ids = None
429
interesting_ids = set()
430
for fname in file_list:
431
path = self.this_tree.relpath(fname)
433
for tree in (self.this_tree, self.base_tree, self.other_tree):
434
file_id = tree.inventory.path2id(path)
435
if file_id is not None:
436
interesting_ids.add(file_id)
439
raise BzrCommandError("%s is not a source file in any"
441
self.interesting_ids = interesting_ids
443
def set_pending(self):
444
if not self.base_is_ancestor:
446
if self.other_rev_id is None:
448
if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
450
self.this_branch.add_pending_merge(self.other_rev_id)
452
def set_other(self, other_revision):
453
other_branch, self.other_tree = get_tree(other_revision,
455
if other_revision[1] == -1:
456
self.other_rev_id = other_branch.last_revision()
457
if self.other_rev_id is None:
458
raise NoCommits(other_branch)
459
self.other_basis = self.other_rev_id
460
elif other_revision[1] is not None:
461
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
462
self.other_basis = self.other_rev_id
464
self.other_rev_id = None
465
self.other_basis = other_branch.last_revision()
466
if self.other_basis is None:
467
raise NoCommits(other_branch)
468
fetch(from_branch=other_branch, to_branch=self.this_branch,
469
last_revision=self.other_basis)
471
def set_base(self, base_revision):
472
mutter("doing merge() with no base_revision specified")
473
if base_revision == [None, None]:
475
self.base_rev_id = common_ancestor(self.this_basis,
478
except NoCommonAncestor:
479
raise UnrelatedBranches()
480
self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
482
self.base_is_ancestor = True
484
base_branch, self.base_tree = get_tree(base_revision)
485
if base_revision[1] == -1:
486
self.base_rev_id = base_branch.last_revision()
487
elif base_revision[1] is None:
488
self.base_rev_id = None
490
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
491
fetch(from_branch=base_branch, to_branch=self.this_branch)
492
self.base_is_ancestor = is_ancestor(self.this_basis,
497
def get_inventory(tree):
498
return tree.inventory
500
inv_changes = merge_flex(self.this_tree, self.base_tree,
502
generate_changeset, get_inventory,
503
self.conflict_handler,
504
merge_factory=self.merge_factory,
505
interesting_ids=self.interesting_ids)
508
for id, path in inv_changes.iteritems():
513
assert path.startswith('.' + os.sep), "path is %s" % path
515
adjust_ids.append((path, id))
516
if len(adjust_ids) > 0:
517
self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
518
conflicts = self.conflict_handler.conflicts
519
self.conflict_handler.finalize()
522
def regen_inventory(self, new_entries):
523
old_entries = self.this_branch.read_working_inventory()
527
for path, file_id in new_entries:
530
new_entries_map[file_id] = path
532
def id2path(file_id):
533
path = new_entries_map.get(file_id)
536
entry = old_entries[file_id]
537
if entry.parent_id is None:
539
return os.path.join(id2path(entry.parent_id), entry.name)
541
for file_id in old_entries:
542
entry = old_entries[file_id]
543
path = id2path(file_id)
544
new_inventory[file_id] = (path, file_id, entry.parent_id,
546
by_path[path] = file_id
551
for path, file_id in new_entries:
553
del new_inventory[file_id]
556
new_path_list.append((path, file_id))
557
if file_id not in old_entries:
559
# Ensure no file is added before its parent
561
for path, file_id in new_path_list:
565
parent = by_path[os.path.dirname(path)]
566
abspath = os.path.join(self.this_tree.basedir, path)
567
kind = bzrlib.osutils.file_kind(abspath)
568
new_inventory[file_id] = (path, file_id, parent, kind)
569
by_path[path] = file_id
571
# Get a list in insertion order
572
new_inventory_list = new_inventory.values()
573
mutter ("""Inventory regeneration:
574
old length: %i insertions: %i deletions: %i new_length: %i"""\
575
% (len(old_entries), insertions, deletions,
576
len(new_inventory_list)))
577
assert len(new_inventory_list) == len(old_entries) + insertions\
579
new_inventory_list.sort()
580
return new_inventory_list
582
merge_types = { "merge3": (ApplyMerge3, "Native diff3-style merge"),
583
"diff3": (Diff3Merge, "Merge using external diff3"),
584
'weave': (WeaveMerge, "Weave-based merge")