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.read_working_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", fix_inventory=False)
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, interesting_ids=None):
273
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
274
interesting_ids=interesting_ids)
276
def merge(other_revision, base_revision,
277
check_clean=True, ignore_zero=False,
278
this_dir=None, backup_files=False, merge_type=ApplyMerge3,
279
file_list=None, show_base=False, reprocess=False):
280
"""Merge changes into a tree.
283
list(path, revno) Base for three-way merge.
284
If [None, None] then a base will be automatically determined.
286
list(path, revno) Other revision for three-way merge.
288
Directory to merge changes into; '.' by default.
290
If true, this_dir must have no uncommitted changes before the
292
ignore_zero - If true, suppress the "zero conflicts" message when
293
there are no conflicts; should be set when doing something we expect
294
to complete perfectly.
295
file_list - If supplied, merge only changes to selected files.
297
All available ancestors of other_revision and base_revision are
298
automatically pulled into the branch.
300
The revno may be -1 to indicate the last revision on the branch, which is
303
This function is intended for use from the command line; programmatic
304
clients might prefer to call merge_inner(), which has less magic behavior.
308
this_branch = Branch.open_containing(this_dir)[0]
309
if show_base and not merge_type is ApplyMerge3:
310
raise BzrCommandError("Show-base is not supported for this merge"
311
" type. %s" % merge_type)
312
if reprocess and not merge_type is ApplyMerge3:
313
raise BzrCommandError("Reprocess is not supported for this merge"
314
" type. %s" % merge_type)
315
if reprocess and show_base:
316
raise BzrCommandError("Cannot reprocess and show base.")
317
merger = Merger(this_branch)
318
merger.check_basis(check_clean)
319
merger.set_other(other_revision)
320
merger.set_base(base_revision)
321
if merger.base_rev_id == merger.other_rev_id:
322
note('Nothing to do.')
324
merger.backup_files = backup_files
325
merger.merge_type = merge_type
326
merger.set_interesting_files(file_list)
327
merger.show_base = show_base
328
merger.reprocess = reprocess
329
merger.conflict_handler = MergeConflictHandler(merger.this_tree,
332
ignore_zero=ignore_zero)
333
conflicts = merger.do_merge()
337
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
338
backup_files=False, merge_type=ApplyMerge3,
339
interesting_ids=None, show_base=False, reprocess=False,
341
"""Primary interface for merging.
343
typical use is probably
344
'merge_inner(branch, branch.get_revision_tree(other_revision),
345
branch.get_revision_tree(base_revision))'
347
merger = Merger(this_branch, other_tree, base_tree)
348
merger.backup_files = False
349
merger.merge_type = merge_type
350
merger.interesting_ids = interesting_ids
351
merger.show_base = show_base
352
merger.reprocess = reprocess
353
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
355
ignore_zero=ignore_zero)
356
merger.other_rev_id = other_rev_id
357
merger.other_basis = other_rev_id
358
return merger.do_merge()
361
class Merger(object):
362
def __init__(self, this_branch, other_tree=None, base_tree=None):
363
object.__init__(self)
364
self.this_branch = this_branch
365
self.this_basis = this_branch.last_revision()
366
self.this_rev_id = None
367
self.this_tree = this_branch.working_tree()
368
self.this_revision_tree = None
369
self.this_basis_tree = None
370
self.other_tree = other_tree
371
self.base_tree = base_tree
372
self.ignore_zero = False
373
self.backup_files = False
374
self.interesting_ids = None
375
self.show_base = False
376
self.reprocess = False
377
self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree,
380
def revision_tree(self, revision_id):
381
return self.this_branch.revision_tree(revision_id)
383
def ensure_revision_trees(self):
384
if self.this_revision_tree is None:
385
self.this_basis_tree = self.this_branch.revision_tree(
387
if self.this_basis == self.this_rev_id:
388
self.this_revision_tree = self.this_basis_tree
391
if self.other_rev_id is None:
392
other_basis_tree = self.revision_tree(self.other_basis)
393
changes = compare_trees(self.other_tree, other_basis_tree)
394
if changes.has_changed():
395
raise WorkingTreeNotRevision(self.this_tree)
396
other_rev_id = other_basis
397
self.other_tree = other_basis_tree
400
def file_revisions(self, file_id):
401
self.ensure_revision_trees()
402
def get_id(tree, file_id):
403
revision_id = tree.inventory[file_id].revision
404
assert revision_id is not None
406
if self.this_rev_id is None:
407
if self.this_basis_tree.get_file_sha1(file_id) != \
408
self.this_tree.get_file_sha1(file_id):
409
raise WorkingTreeNotRevision(self.this_tree)
411
trees = (self.this_basis_tree, self.other_tree)
412
return [get_id(tree, file_id) for tree in trees]
415
def merge_factory(self, file_id, base, other):
416
if self.merge_type.history_based:
417
if self.show_base is True:
418
raise BzrError("Cannot show base for hisory-based merges")
419
if self.reprocess is True:
420
raise BzrError("Cannot reprocess history-based merges")
422
t_revid, o_revid = self.file_revisions(file_id)
423
weave = self.this_basis_tree.get_weave(file_id)
424
contents_change = self.merge_type(weave, t_revid, o_revid)
426
if self.show_base is True or self.reprocess is True:
427
contents_change = self.merge_type(file_id, base, other,
428
show_base=self.show_base,
429
reprocess=self.reprocess)
431
contents_change = self.merge_type(file_id, base, other)
432
if self.backup_files:
433
contents_change = BackupBeforeChange(contents_change)
434
return contents_change
436
def check_basis(self, check_clean):
437
if self.this_basis is None:
438
raise BzrCommandError("This branch has no commits")
441
if self.this_basis != self.this_rev_id:
442
raise BzrCommandError("Working tree has uncommitted changes.")
444
def compare_basis(self):
445
changes = compare_trees(self.this_branch.working_tree(),
446
self.this_branch.basis_tree(), False)
447
if not changes.has_changed():
448
self.this_rev_id = self.this_basis
450
def set_interesting_files(self, file_list):
451
if file_list is None:
452
self.interesting_ids = None
455
interesting_ids = set()
456
for fname in file_list:
457
path = self.this_tree.relpath(fname)
459
for tree in (self.this_tree, self.base_tree, self.other_tree):
460
file_id = tree.inventory.path2id(path)
461
if file_id is not None:
462
interesting_ids.add(file_id)
465
raise BzrCommandError("%s is not a source file in any"
467
self.interesting_ids = interesting_ids
469
def set_pending(self):
470
if not self.base_is_ancestor:
472
if self.other_rev_id is None:
474
if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
476
self.this_branch.add_pending_merge(self.other_rev_id)
478
def set_other(self, other_revision):
479
other_branch, self.other_tree = get_tree(other_revision,
481
if other_revision[1] == -1:
482
self.other_rev_id = other_branch.last_revision()
483
if self.other_rev_id is None:
484
raise NoCommits(other_branch)
485
self.other_basis = self.other_rev_id
486
elif other_revision[1] is not None:
487
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
488
self.other_basis = self.other_rev_id
490
self.other_rev_id = None
491
self.other_basis = other_branch.last_revision()
492
if self.other_basis is None:
493
raise NoCommits(other_branch)
494
fetch(from_branch=other_branch, to_branch=self.this_branch,
495
last_revision=self.other_basis)
497
def set_base(self, base_revision):
498
mutter("doing merge() with no base_revision specified")
499
if base_revision == [None, None]:
501
self.base_rev_id = common_ancestor(self.this_basis,
504
except NoCommonAncestor:
505
raise UnrelatedBranches()
506
self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,
508
self.base_is_ancestor = True
510
base_branch, self.base_tree = get_tree(base_revision)
511
if base_revision[1] == -1:
512
self.base_rev_id = base_branch.last_revision()
513
elif base_revision[1] is None:
514
self.base_rev_id = None
516
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
517
fetch(from_branch=base_branch, to_branch=self.this_branch)
518
self.base_is_ancestor = is_ancestor(self.this_basis,
523
def get_inventory(tree):
524
return tree.inventory
526
inv_changes = merge_flex(self.this_tree, self.base_tree,
528
generate_changeset, get_inventory,
529
self.conflict_handler,
530
merge_factory=self.merge_factory,
531
interesting_ids=self.interesting_ids)
534
for id, path in inv_changes.iteritems():
539
assert path.startswith('.' + os.sep), "path is %s" % path
541
adjust_ids.append((path, id))
542
if len(adjust_ids) > 0:
543
self.this_branch.working_tree().set_inventory(self.regen_inventory(adjust_ids))
544
conflicts = self.conflict_handler.conflicts
545
self.conflict_handler.finalize()
548
def regen_inventory(self, new_entries):
549
old_entries = self.this_branch.working_tree().read_working_inventory()
553
for path, file_id in new_entries:
556
new_entries_map[file_id] = path
558
def id2path(file_id):
559
path = new_entries_map.get(file_id)
562
entry = old_entries[file_id]
563
if entry.parent_id is None:
565
return os.path.join(id2path(entry.parent_id), entry.name)
567
for file_id in old_entries:
568
entry = old_entries[file_id]
569
path = id2path(file_id)
570
new_inventory[file_id] = (path, file_id, entry.parent_id,
572
by_path[path] = file_id
577
for path, file_id in new_entries:
579
del new_inventory[file_id]
582
new_path_list.append((path, file_id))
583
if file_id not in old_entries:
585
# Ensure no file is added before its parent
587
for path, file_id in new_path_list:
591
parent = by_path[os.path.dirname(path)]
592
abspath = os.path.join(self.this_tree.basedir, path)
593
kind = bzrlib.osutils.file_kind(abspath)
594
new_inventory[file_id] = (path, file_id, parent, kind)
595
by_path[path] = file_id
597
# Get a list in insertion order
598
new_inventory_list = new_inventory.values()
599
mutter ("""Inventory regeneration:
600
old length: %i insertions: %i deletions: %i new_length: %i"""\
601
% (len(old_entries), insertions, deletions,
602
len(new_inventory_list)))
603
assert len(new_inventory_list) == len(old_entries) + insertions\
605
new_inventory_list.sort()
606
return new_inventory_list
608
merge_types = { "merge3": (ApplyMerge3, "Native diff3-style merge"),
609
"diff3": (Diff3Merge, "Merge using external diff3"),
610
'weave': (WeaveMerge, "Weave-based merge")