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):
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)
103
relpath = self.this_tree.relpath(name)
104
except NotBranchError:
106
if relpath is not None:
107
file_id = self.this_tree.path2id(relpath)
108
if file_id is not None:
109
new_path = self.this_tree.relpath(new_name)
110
rename(new_name, name)
111
self.this_tree.branch.rename_one(relpath, new_path)
112
assert self.this_tree.id2path(file_id) == relpath
113
self.this_tree._inventory = self.this_tree.branch.inventory
114
assert self.this_tree.id2path(file_id) == new_path
116
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
118
return self.add_suffix(name, suffix, last_new_name=new_name)
121
def conflict(self, text):
126
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
128
Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER. The
129
main file will be a version with diff3 conflicts.
130
:param new_file: Path to the output file with diff3 markers
131
:param this_path: Path to the file text for the THIS tree
132
:param base_path: Path to the file text for the BASE tree
133
:param other_path: Path to the file text for the OTHER tree
135
self.add_suffix(this_path, ".THIS")
136
self.dump(base_lines, this_path+".BASE")
137
self.dump(other_lines, this_path+".OTHER")
138
rename(new_file, this_path)
139
self.conflict("Diff3 conflict encountered in %s" % this_path)
141
def weave_merge_conflict(self, filename, weave, other_i, out_file):
143
Handle weave conflicts by producing a .THIS, and .OTHER. The
144
main file will be a version with diff3-style conflicts.
146
self.add_suffix(filename, ".THIS")
148
self.dump(weave.get_iter(other_i), filename+".OTHER")
149
self.conflict("Text conflict encountered in %s" % filename)
151
def new_contents_conflict(self, filename, other_contents):
152
"""Conflicting contents for newly added file."""
153
other_contents(filename + ".OTHER", self, False)
154
self.conflict("Conflict in newly added file %s" % filename)
157
def target_exists(self, entry, target, old_path):
158
"""Handle the case when the target file or dir exists"""
159
moved_path = self.add_suffix(target, ".moved")
160
self.conflict("Moved existing %s to %s" % (target, moved_path))
162
def rmdir_non_empty(self, filename):
163
"""Handle the case where the dir to be removed still has contents"""
164
self.conflict("Directory %s not removed because it is not empty"\
168
def rem_contents_conflict(self, filename, this_contents, base_contents):
169
base_contents(filename+".BASE", self, False)
170
this_contents(filename+".THIS", self, False)
171
return ReplaceContents(this_contents, None)
173
def rem_contents_conflict(self, filename, this_contents, base_contents):
174
base_contents(filename+".BASE", self, False)
175
this_contents(filename+".THIS", self, False)
176
self.conflict("Other branch deleted locally modified file %s" %
178
return ReplaceContents(this_contents, None)
180
def abs_this_path(self, file_id):
181
"""Return the absolute path for a file_id in the this tree."""
182
return self.this_tree.id2abspath(file_id)
184
def add_missing_parents(self, file_id, tree):
185
"""If some of the parents for file_id are missing, add them."""
186
entry = tree.inventory[file_id]
187
if entry.parent_id not in self.this_tree:
188
return self.create_all_missing(entry.parent_id, tree)
190
return self.abs_this_path(entry.parent_id)
192
def create_all_missing(self, file_id, tree):
193
"""Add contents for a file_id and all its parents to a tree."""
194
entry = tree.inventory[file_id]
195
if entry.parent_id is not None and entry.parent_id not in self.this_tree:
196
abspath = self.create_all_missing(entry.parent_id, tree)
198
abspath = self.abs_this_path(entry.parent_id)
199
entry_path = os.path.join(abspath, entry.name)
200
if not os.path.isdir(entry_path):
201
self.create(file_id, entry_path, tree)
204
def create(self, file_id, path, tree, reverse=False):
205
"""Uses tree data to create a filesystem object for the file_id"""
206
from changeset import get_contents
207
get_contents(tree, file_id)(path, self, reverse)
209
def missing_for_merge(self, file_id, other_path):
210
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
211
self.conflict("Other branch modified locally deleted file %s" %
213
parent_dir = self.add_missing_parents(file_id, self.other_tree)
214
stem = os.path.join(parent_dir, os.path.basename(other_path))
215
self.create(file_id, stem+".OTHER", self.other_tree)
216
self.create(file_id, stem+".BASE", self.base_tree)
218
def threeway_contents_conflict(filename, this_contents, base_contents,
220
self.conflict("Three-way conflict merging %s" % filename)
223
if not self.ignore_zero:
224
note("%d conflicts encountered.\n" % self.conflicts)
226
def get_tree(treespec, local_branch=None):
227
location, revno = treespec
228
branch = Branch.open_containing(location)[0]
232
revision = branch.last_revision()
234
revision = branch.get_rev_id(revno)
236
revision = NULL_REVISION
237
return branch, get_revid_tree(branch, revision, local_branch)
239
def get_revid_tree(branch, revision, local_branch):
241
base_tree = branch.working_tree()
243
if local_branch is not None:
244
greedy_fetch(local_branch, branch, revision)
245
base_tree = local_branch.revision_tree(revision)
247
base_tree = branch.revision_tree(revision)
251
def file_exists(tree, file_id):
252
return tree.has_filename(tree.id2path(file_id))
255
def build_working_dir(to_dir):
256
"""Build a working directory in an empty directory.
258
to_dir is a directory containing branch metadata but no working files,
259
typically constructed by cloning an existing branch.
261
This is split out as a special idiomatic case of merge. It could
262
eventually be done by just building the tree directly calling into
263
lower-level code (e.g. constructing a changeset).
265
# RBC 20051019 is this not just 'export' ?
266
# Well, export doesn't take care of inventory...
267
this_branch = Branch.open_containing(to_dir)[0]
268
transform_tree(this_branch.working_tree(), this_branch.basis_tree())
270
def transform_tree(from_tree, to_tree):
271
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True)
273
def merge(other_revision, base_revision,
274
check_clean=True, ignore_zero=False,
275
this_dir=None, backup_files=False, merge_type=ApplyMerge3,
276
file_list=None, show_base=False):
277
"""Merge changes into a tree.
280
list(path, revno) Base for three-way merge.
281
If [None, None] then a base will be automatically determined.
283
list(path, revno) Other revision for three-way merge.
285
Directory to merge changes into; '.' by default.
287
If true, this_dir must have no uncommitted changes before the
289
ignore_zero - If true, suppress the "zero conflicts" message when
290
there are no conflicts; should be set when doing something we expect
291
to complete perfectly.
292
file_list - If supplied, merge only changes to selected files.
294
All available ancestors of other_revision and base_revision are
295
automatically pulled into the branch.
297
The revno may be -1 to indicate the last revision on the branch, which is
300
This function is intended for use from the command line; programmatic
301
clients might prefer to call merge_inner(), which has less magic behavior.
305
this_branch = Branch.open_containing(this_dir)[0]
306
if show_base and not merge_type is ApplyMerge3:
307
raise BzrCommandError("Show-base is not supported for this merge"
308
" type. %s" % merge_type)
309
merger = Merger(this_branch)
310
merger.check_basis(check_clean)
311
merger.set_other(other_revision)
312
merger.set_base(base_revision)
313
merger.backup_files = backup_files
314
merger.merge_type = merge_type
315
merger.set_interesting_files(file_list)
316
merger.show_base = show_base
317
merger.conflict_handler = MergeConflictHandler(merger.this_tree,
320
ignore_zero=ignore_zero)
321
conflicts = merger.do_merge()
325
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
326
backup_files=False, merge_type=ApplyMerge3,
327
interesting_ids=None, show_base=False):
328
"""Primary interface for merging.
330
typical use is probably
331
'merge_inner(branch, branch.get_revision_tree(other_revision),
332
branch.get_revision_tree(base_revision))'
334
merger = Merger(this_branch, other_tree, base_tree)
335
merger.backup_files = False
336
merger.merge_type = ApplyMerge3
337
merger.interesting_ids = interesting_ids
338
merger.show_base = show_base
339
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
341
ignore_zero=ignore_zero)
342
return merger.do_merge()
345
class Merger(object):
346
def __init__(self, this_branch, other_tree=None, base_tree=None):
347
object.__init__(self)
348
self.this_branch = this_branch
349
self.this_basis = this_branch.last_revision()
350
self.this_rev_id = None
351
self.this_tree = this_branch.working_tree()
352
self.this_revision_tree = None
353
self.other_tree = other_tree
354
self.base_tree = base_tree
355
self.ignore_zero = False
356
self.backup_files = False
357
self.interesting_ids = None
358
self.show_base = False
359
self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree,
362
def revision_tree(self, revision_id):
363
return self.this_branch.revision_tree(revision_id)
365
def ensure_revision_trees(self):
366
if self.this_revision_tree is None:
367
if self.this_rev_id is None:
369
if self.this_rev_id is None:
370
raise WorkingTreeNotRevision(self.this_tree)
371
self.this_revision_tree = self.this_branch.revision_tree(
374
if self.other_rev_id is None:
375
other_basis_tree = self.revision_tree(self.other_basis)
376
changes = compare_trees(self.other_tree, other_basis_tree)
377
if changes.has_changed():
378
raise WorkingTreeNotRevision(self.this_tree)
379
other_rev_id = other_basis
380
self.other_tree = other_basis_tree
383
def file_revisions(self, file_id):
384
self.ensure_revision_trees()
385
def get_id(tree, file_id):
386
revision_id = tree.inventory[file_id].revision
387
assert revision_id is not None
389
trees = (self.this_revision_tree, self.other_tree)
390
return [get_id(tree, file_id) for tree in trees]
393
def merge_factory(self, file_id, base, other):
394
if self.merge_type.history_based:
395
t_revid, o_revid = self.file_revisions(file_id)
396
weave = self.this_revision_tree.get_weave(file_id)
397
contents_change = self.merge_type(weave, t_revid, o_revid)
399
if self.show_base is True:
400
contents_change = self.merge_type(file_id, base, other,
403
contents_change = self.merge_type(file_id, base, other)
404
if self.backup_files:
405
contents_change = BackupBeforeChange(contents_change)
406
return contents_change
408
def check_basis(self, check_clean):
409
if self.this_basis is None:
410
raise BzrCommandError("This branch has no commits")
413
if self.this_basis != self.this_rev_id:
414
raise BzrCommandError("Working tree has uncommitted changes.")
416
def compare_basis(self):
417
changes = compare_trees(self.this_branch.working_tree(),
418
self.this_branch.basis_tree(), False)
419
if not changes.has_changed():
420
self.this_rev_id = self.this_basis
422
def set_interesting_files(self, file_list):
423
if file_list is None:
424
self.interesting_ids = None
427
interesting_ids = set()
428
for fname in file_list:
429
path = self.this_tree.relpath(fname)
431
for tree in (self.this_tree, self.base_tree, self.other_tree):
432
file_id = tree.inventory.path2id(path)
433
if file_id is not None:
434
interesting_ids.add(file_id)
437
raise BzrCommandError("%s is not a source file in any"
439
self.interesting_ids = interesting_ids
441
def set_pending(self):
442
if not self.base_is_ancestor:
444
if self.other_rev_id is None:
446
if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
448
self.this_branch.add_pending_merge(self.other_rev_id)
450
def set_other(self, other_revision):
451
other_branch, self.other_tree = get_tree(other_revision,
453
if other_revision[1] == -1:
454
self.other_rev_id = other_branch.last_revision()
455
if self.other_rev_id is None:
456
raise NoCommits(other_branch)
457
self.other_basis = self.other_rev_id
458
elif other_revision[1] is not None:
459
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
460
self.other_basis = self.other_rev_id
462
self.other_rev_id = None
463
self.other_basis = other_branch.last_revision()
464
if self.other_basis is None:
465
raise NoCommits(other_branch)
466
fetch(from_branch=other_branch, to_branch=self.this_branch,
467
last_revision=self.other_basis)
469
def set_base(self, base_revision):
470
mutter("doing merge() with no base_revision specified")
471
if base_revision == [None, None]:
473
self.base_rev_id = common_ancestor(self.this_basis,
476
except NoCommonAncestor:
477
raise UnrelatedBranches()
478
self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
480
self.base_is_ancestor = True
482
base_branch, self.base_tree = get_tree(base_revision)
483
if base_revision[1] == -1:
484
self.base_rev_id = base_branch.last_revision()
485
elif base_revision[1] is None:
486
self.base_rev_id = None
488
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
489
fetch(from_branch=base_branch, to_branch=self.this_branch)
490
self.base_is_ancestor = is_ancestor(self.this_basis,
495
def get_inventory(tree):
496
return tree.inventory
498
inv_changes = merge_flex(self.this_tree, self.base_tree,
500
generate_changeset, get_inventory,
501
self.conflict_handler,
502
merge_factory=self.merge_factory,
503
interesting_ids=self.interesting_ids)
506
for id, path in inv_changes.iteritems():
511
assert path.startswith('.' + os.sep), "path is %s" % path
513
adjust_ids.append((path, id))
514
if len(adjust_ids) > 0:
515
self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
516
conflicts = self.conflict_handler.conflicts
517
self.conflict_handler.finalize()
520
def regen_inventory(self, new_entries):
521
old_entries = self.this_branch.read_working_inventory()
525
for path, file_id in new_entries:
528
new_entries_map[file_id] = path
530
def id2path(file_id):
531
path = new_entries_map.get(file_id)
534
entry = old_entries[file_id]
535
if entry.parent_id is None:
537
return os.path.join(id2path(entry.parent_id), entry.name)
539
for file_id in old_entries:
540
entry = old_entries[file_id]
541
path = id2path(file_id)
542
new_inventory[file_id] = (path, file_id, entry.parent_id,
544
by_path[path] = file_id
549
for path, file_id in new_entries:
551
del new_inventory[file_id]
554
new_path_list.append((path, file_id))
555
if file_id not in old_entries:
557
# Ensure no file is added before its parent
559
for path, file_id in new_path_list:
563
parent = by_path[os.path.dirname(path)]
564
abspath = os.path.join(self.this_tree.basedir, path)
565
kind = bzrlib.osutils.file_kind(abspath)
566
new_inventory[file_id] = (path, file_id, parent, kind)
567
by_path[path] = file_id
569
# Get a list in insertion order
570
new_inventory_list = new_inventory.values()
571
mutter ("""Inventory regeneration:
572
old length: %i insertions: %i deletions: %i new_length: %i"""\
573
% (len(old_entries), insertions, deletions,
574
len(new_inventory_list)))
575
assert len(new_inventory_list) == len(old_entries) + insertions\
577
new_inventory_list.sort()
578
return new_inventory_list
580
merge_types = { "merge3": (ApplyMerge3, "Native diff3-style merge"),
581
"diff3": (Diff3Merge, "Merge using external diff3"),
582
'weave': (WeaveMerge, "Weave-based merge")