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
22
from bzrlib._changeset import generate_changeset, ExceptionConflictHandler
23
from bzrlib._changeset import Inventory, Diff3Merge, ReplaceContents
24
from bzrlib._merge_core import WeaveMerge
25
from bzrlib._merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
29
26
from bzrlib.branch import Branch
27
from bzrlib.delta import compare_trees
30
28
from bzrlib.errors import (BzrCommandError,
34
WorkingTreeNotRevision,
38
from bzrlib.delta import compare_trees
36
WorkingTreeNotRevision,
38
from bzrlib.fetch import greedy_fetch, fetch
40
from bzrlib.osutils import rename, pathjoin
41
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
39
42
from bzrlib.trace import mutter, warning, note
40
from bzrlib.fetch import greedy_fetch, fetch
41
from bzrlib.revision import is_ancestor, NULL_REVISION
42
from bzrlib.osutils import rename
43
from bzrlib.revision import common_ancestor, MultipleRevisionSources
44
from bzrlib.errors import NoSuchRevision
46
44
# TODO: Report back as changes are merged in
48
# TODO: build_working_dir can be built on something simpler than merge()
50
# FIXME: merge() parameters seem oriented towards the command line
51
# NOTABUG: merge is a helper for commandline functions. merge_inner is the
52
# the core functionality.
54
46
# comments from abentley on irc: merge happens in two stages, each
55
47
# of which generates a changeset object
57
49
# stage 1: generate OLD->OTHER,
58
50
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
60
class MergeConflictHandler(ExceptionConflictHandler):
52
class _MergeConflictHandler(ExceptionConflictHandler):
61
53
"""Handle conflicts encountered while merging.
63
55
This subclasses ExceptionConflictHandler, so that any types of
176
166
def rem_contents_conflict(self, filename, this_contents, base_contents):
177
base_contents(filename+".BASE", self, False)
178
this_contents(filename+".THIS", self, False)
179
return ReplaceContents(this_contents, None)
181
def rem_contents_conflict(self, filename, this_contents, base_contents):
182
base_contents(filename+".BASE", self, False)
183
this_contents(filename+".THIS", self, False)
167
base_contents(filename+".BASE", self)
168
this_contents(filename+".THIS", self)
184
169
self.conflict("Other branch deleted locally modified file %s" %
186
171
return ReplaceContents(this_contents, None)
204
189
abspath = self.create_all_missing(entry.parent_id, tree)
206
191
abspath = self.abs_this_path(entry.parent_id)
207
entry_path = os.path.join(abspath, entry.name)
192
entry_path = pathjoin(abspath, entry.name)
208
193
if not os.path.isdir(entry_path):
209
194
self.create(file_id, entry_path, tree)
210
195
return entry_path
212
def create(self, file_id, path, tree, reverse=False):
197
def create(self, file_id, path, tree):
213
198
"""Uses tree data to create a filesystem object for the file_id"""
214
from changeset import get_contents
215
get_contents(tree, file_id)(path, self, reverse)
199
from bzrlib._changeset import get_contents
200
get_contents(tree, file_id)(path, self)
217
202
def missing_for_merge(self, file_id, other_path):
218
203
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
219
204
self.conflict("Other branch modified locally deleted file %s" %
221
206
parent_dir = self.add_missing_parents(file_id, self.other_tree)
222
stem = os.path.join(parent_dir, os.path.basename(other_path))
207
stem = pathjoin(parent_dir, os.path.basename(other_path))
223
208
self.create(file_id, stem+".OTHER", self.other_tree)
224
209
self.create(file_id, stem+".BASE", self.base_tree)
242
230
revision = branch.get_rev_id(revno)
243
231
if revision is None:
244
232
revision = NULL_REVISION
245
return branch, get_revid_tree(branch, revision, local_branch)
247
def get_revid_tree(branch, revision, local_branch):
233
return branch, _get_revid_tree(branch, revision, local_branch)
236
def _get_revid_tree(branch, revision, local_branch):
248
237
if revision is None:
249
base_tree = branch.working_tree()
238
base_tree = branch.bzrdir.open_workingtree()
251
240
if local_branch is not None:
252
greedy_fetch(local_branch, branch, revision)
253
base_tree = local_branch.revision_tree(revision)
241
if local_branch.base != branch.base:
242
greedy_fetch(local_branch, branch, revision)
243
base_tree = local_branch.repository.revision_tree(revision)
255
base_tree = branch.revision_tree(revision)
245
base_tree = branch.repository.revision_tree(revision)
259
def file_exists(tree, file_id):
260
return tree.has_filename(tree.id2path(file_id))
263
def build_working_dir(to_dir):
264
"""Build a working directory in an empty directory.
266
to_dir is a directory containing branch metadata but no working files,
267
typically constructed by cloning an existing branch.
269
This is split out as a special idiomatic case of merge. It could
270
eventually be done by just building the tree directly calling into
271
lower-level code (e.g. constructing a changeset).
273
# RBC 20051019 is this not just 'export' ?
274
# AB Well, export doesn't take care of inventory...
275
this_branch = Branch.open_containing(to_dir)[0]
276
transform_tree(this_branch.working_tree(), this_branch.basis_tree())
279
249
def transform_tree(from_tree, to_tree, interesting_ids=None):
280
250
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
281
251
interesting_ids=interesting_ids)
284
def merge(other_revision, base_revision,
285
check_clean=True, ignore_zero=False,
286
this_dir=None, backup_files=False, merge_type=ApplyMerge3,
287
file_list=None, show_base=False, reprocess=False):
288
"""Merge changes into a tree.
291
list(path, revno) Base for three-way merge.
292
If [None, None] then a base will be automatically determined.
294
list(path, revno) Other revision for three-way merge.
296
Directory to merge changes into; '.' by default.
298
If true, this_dir must have no uncommitted changes before the
300
ignore_zero - If true, suppress the "zero conflicts" message when
301
there are no conflicts; should be set when doing something we expect
302
to complete perfectly.
303
file_list - If supplied, merge only changes to selected files.
305
All available ancestors of other_revision and base_revision are
306
automatically pulled into the branch.
308
The revno may be -1 to indicate the last revision on the branch, which is
311
This function is intended for use from the command line; programmatic
312
clients might prefer to call merge_inner(), which has less magic behavior.
316
this_branch = Branch.open_containing(this_dir)[0]
317
if show_base and not merge_type is ApplyMerge3:
318
raise BzrCommandError("Show-base is not supported for this merge"
319
" type. %s" % merge_type)
320
if reprocess and not merge_type is ApplyMerge3:
321
raise BzrCommandError("Reprocess is not supported for this merge"
322
" type. %s" % merge_type)
323
if reprocess and show_base:
324
raise BzrCommandError("Cannot reprocess and show base.")
325
merger = Merger(this_branch)
326
merger.check_basis(check_clean)
327
merger.set_other(other_revision)
328
merger.set_base(base_revision)
329
if merger.base_rev_id == merger.other_rev_id:
330
note('Nothing to do.')
332
merger.backup_files = backup_files
333
merger.merge_type = merge_type
334
merger.set_interesting_files(file_list)
335
merger.show_base = show_base
336
merger.reprocess = reprocess
337
merger.conflict_handler = MergeConflictHandler(merger.this_tree,
340
ignore_zero=ignore_zero)
341
conflicts = merger.do_merge()
345
254
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
346
255
backup_files=False,
347
256
merge_type=ApplyMerge3,
366
278
merger._set_interesting_files(interesting_files)
367
279
merger.show_base = show_base
368
280
merger.reprocess = reprocess
369
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
371
ignore_zero=ignore_zero)
281
merger.conflict_handler = _MergeConflictHandler(merger.this_tree,
282
base_tree, other_tree,
283
ignore_zero=ignore_zero)
372
284
merger.other_rev_id = other_rev_id
373
285
merger.other_basis = other_rev_id
374
286
return merger.do_merge()
377
289
class Merger(object):
378
def __init__(self, this_branch, other_tree=None, base_tree=None):
290
def __init__(self, this_branch, other_tree=None, base_tree=None, this_tree=None):
379
291
object.__init__(self)
292
assert this_tree is not None, "this_tree is required"
380
293
self.this_branch = this_branch
381
294
self.this_basis = this_branch.last_revision()
382
295
self.this_rev_id = None
383
self.this_tree = this_branch.working_tree()
296
self.this_tree = this_tree
384
297
self.this_revision_tree = None
385
298
self.this_basis_tree = None
386
299
self.other_tree = other_tree
390
303
self.interesting_ids = None
391
304
self.show_base = False
392
305
self.reprocess = False
393
self.conflict_handler = MergeConflictHandler(self.this_tree, base_tree,
306
self.conflict_handler = _MergeConflictHandler(self.this_tree,
307
base_tree, other_tree)
396
309
def revision_tree(self, revision_id):
397
return self.this_branch.revision_tree(revision_id)
310
return self.this_branch.repository.revision_tree(revision_id)
399
312
def ensure_revision_trees(self):
400
313
if self.this_revision_tree is None:
401
self.this_basis_tree = self.this_branch.revision_tree(
314
self.this_basis_tree = self.this_branch.repository.revision_tree(
403
316
if self.this_basis == self.this_rev_id:
404
317
self.this_revision_tree = self.this_basis_tree
407
319
if self.other_rev_id is None:
408
320
other_basis_tree = self.revision_tree(self.other_basis)
409
321
changes = compare_trees(self.other_tree, other_basis_tree)