26
27
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
27
28
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
28
29
from bzrlib.branch import Branch
29
from bzrlib.errors import (BzrCommandError,
33
WorkingTreeNotRevision,
30
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
31
from bzrlib.errors import NoCommits, WorkingTreeNotRevision, NotBranchError
37
32
from bzrlib.delta import compare_trees
38
33
from bzrlib.trace import mutter, warning, note
39
34
from bzrlib.fetch import greedy_fetch, fetch
40
35
from bzrlib.revision import is_ancestor, NULL_REVISION
41
from bzrlib.osutils import rename, pathjoin
36
from bzrlib.osutils import rename
42
37
from bzrlib.revision import common_ancestor, MultipleRevisionSources
43
38
from bzrlib.errors import NoSuchRevision
114
109
if file_id is not None:
115
110
new_path = self.this_tree.relpath(new_name)
116
111
rename(new_name, name)
117
self.this_tree.rename_one(relpath, new_path)
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
118
115
assert self.this_tree.id2path(file_id) == new_path
119
116
except OSError, e:
120
117
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
148
145
Handle weave conflicts by producing a .THIS, and .OTHER. The
149
146
main file will be a version with diff3-style conflicts.
151
self.add_suffix(filename, ".THIS", fix_inventory=False)
148
self.add_suffix(filename, ".THIS")
152
149
out_file.commit()
153
150
self.dump(weave.get_iter(other_i), filename+".OTHER")
154
151
self.conflict("Text conflict encountered in %s" % filename)
173
170
def rem_contents_conflict(self, filename, this_contents, base_contents):
174
base_contents(filename+".BASE", self)
175
this_contents(filename+".THIS", self)
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)
176
178
self.conflict("Other branch deleted locally modified file %s" %
178
180
return ReplaceContents(this_contents, None)
196
198
abspath = self.create_all_missing(entry.parent_id, tree)
198
200
abspath = self.abs_this_path(entry.parent_id)
199
entry_path = pathjoin(abspath, entry.name)
201
entry_path = os.path.join(abspath, entry.name)
200
202
if not os.path.isdir(entry_path):
201
203
self.create(file_id, entry_path, tree)
202
204
return entry_path
204
def create(self, file_id, path, tree):
206
def create(self, file_id, path, tree, reverse=False):
205
207
"""Uses tree data to create a filesystem object for the file_id"""
206
208
from changeset import get_contents
207
get_contents(tree, file_id)(path, self)
209
get_contents(tree, file_id)(path, self, reverse)
209
211
def missing_for_merge(self, file_id, other_path):
210
212
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
211
213
self.conflict("Other branch modified locally deleted file %s" %
213
215
parent_dir = self.add_missing_parents(file_id, self.other_tree)
214
stem = pathjoin(parent_dir, os.path.basename(other_path))
216
stem = os.path.join(parent_dir, os.path.basename(other_path))
215
217
self.create(file_id, stem+".OTHER", self.other_tree)
216
218
self.create(file_id, stem+".BASE", self.base_tree)
220
222
self.conflict("Three-way conflict merging %s" % filename)
222
224
def finalize(self):
223
if self.conflicts == 0:
224
if not self.ignore_zero:
225
note("All changes applied successfully.")
227
note("%d conflicts encountered." % self.conflicts)
225
if not self.ignore_zero:
226
note("%d conflicts encountered.\n" % self.conflicts)
229
228
def get_tree(treespec, local_branch=None):
230
229
location, revno = treespec
266
265
lower-level code (e.g. constructing a changeset).
268
267
# RBC 20051019 is this not just 'export' ?
269
# AB Well, export doesn't take care of inventory...
268
# Well, export doesn't take care of inventory...
270
269
this_branch = Branch.open_containing(to_dir)[0]
271
270
transform_tree(this_branch.working_tree(), this_branch.basis_tree())
274
def transform_tree(from_tree, to_tree, interesting_ids=None):
275
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
276
interesting_ids=interesting_ids)
272
def transform_tree(from_tree, to_tree):
273
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True)
279
275
def merge(other_revision, base_revision,
280
276
check_clean=True, ignore_zero=False,
307
303
clients might prefer to call merge_inner(), which has less magic behavior.
309
305
if this_dir is None:
311
307
this_branch = Branch.open_containing(this_dir)[0]
312
308
if show_base and not merge_type is ApplyMerge3:
313
309
raise BzrCommandError("Show-base is not supported for this merge"
321
317
merger.check_basis(check_clean)
322
318
merger.set_other(other_revision)
323
319
merger.set_base(base_revision)
324
if merger.base_rev_id == merger.other_rev_id:
325
note('Nothing to do.')
327
320
merger.backup_files = backup_files
328
321
merger.merge_type = merge_type
329
322
merger.set_interesting_files(file_list)
340
333
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
342
merge_type=ApplyMerge3,
343
interesting_ids=None,
347
interesting_files=None):
334
backup_files=False, merge_type=ApplyMerge3,
335
interesting_ids=None, show_base=False, reprocess=False):
348
336
"""Primary interface for merging.
350
338
typical use is probably
352
340
branch.get_revision_tree(base_revision))'
354
342
merger = Merger(this_branch, other_tree, base_tree)
355
merger.backup_files = backup_files
356
merger.merge_type = merge_type
343
merger.backup_files = False
344
merger.merge_type = ApplyMerge3
357
345
merger.interesting_ids = interesting_ids
358
if interesting_files:
359
assert not interesting_ids, ('Only supply interesting_ids'
360
' or interesting_files')
361
merger._set_interesting_files(interesting_files)
362
346
merger.show_base = show_base
363
347
merger.reprocess = reprocess
364
348
merger.conflict_handler = MergeConflictHandler(merger.this_tree, base_tree,
366
350
ignore_zero=ignore_zero)
367
merger.other_rev_id = other_rev_id
368
merger.other_basis = other_rev_id
369
351
return merger.do_merge()
377
359
self.this_rev_id = None
378
360
self.this_tree = this_branch.working_tree()
379
361
self.this_revision_tree = None
380
self.this_basis_tree = None
381
362
self.other_tree = other_tree
382
363
self.base_tree = base_tree
383
364
self.ignore_zero = False
394
375
def ensure_revision_trees(self):
395
376
if self.this_revision_tree is None:
396
self.this_basis_tree = self.this_branch.revision_tree(
398
if self.this_basis == self.this_rev_id:
399
self.this_revision_tree = self.this_basis_tree
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(
402
384
if self.other_rev_id is None:
403
385
other_basis_tree = self.revision_tree(self.other_basis)
414
396
revision_id = tree.inventory[file_id].revision
415
397
assert revision_id is not None
416
398
return revision_id
417
if self.this_rev_id is None:
418
if self.this_basis_tree.get_file_sha1(file_id) != \
419
self.this_tree.get_file_sha1(file_id):
420
raise WorkingTreeNotRevision(self.this_tree)
422
trees = (self.this_basis_tree, self.other_tree)
399
trees = (self.this_revision_tree, self.other_tree)
423
400
return [get_id(tree, file_id) for tree in trees]
426
403
def merge_factory(self, file_id, base, other):
427
404
if self.merge_type.history_based:
428
if self.show_base is True:
429
raise BzrError("Cannot show base for hisory-based merges")
430
if self.reprocess is True:
431
raise BzrError("Cannot reprocess history-based merges")
433
405
t_revid, o_revid = self.file_revisions(file_id)
434
weave = self.this_basis_tree.get_weave(file_id)
406
weave = self.this_revision_tree.get_weave(file_id)
435
407
contents_change = self.merge_type(weave, t_revid, o_revid)
437
409
if self.show_base is True or self.reprocess is True:
459
431
self.this_rev_id = self.this_basis
461
433
def set_interesting_files(self, file_list):
463
self._set_interesting_files(file_list)
464
except NotVersionedError, e:
465
raise BzrCommandError("%s is not a source file in any"
468
def _set_interesting_files(self, file_list):
469
"""Set the list of interesting ids from a list of files."""
470
434
if file_list is None:
471
435
self.interesting_ids = None
492
457
if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
494
self.this_branch.working_tree().add_pending_merge(self.other_rev_id)
459
self.this_branch.add_pending_merge(self.other_rev_id)
496
461
def set_other(self, other_revision):
497
462
other_branch, self.other_tree = get_tree(other_revision,
552
517
for id, path in inv_changes.iteritems():
553
518
if path is not None:
557
assert path.startswith('.' + '/') or path.startswith('.' + '\\'), "path is %s" % path
522
assert path.startswith('.' + os.sep), "path is %s" % path
559
524
adjust_ids.append((path, id))
560
525
if len(adjust_ids) > 0:
561
self.this_branch.working_tree().set_inventory(self.regen_inventory(adjust_ids))
526
self.this_branch.set_inventory(self.regen_inventory(adjust_ids))
562
527
conflicts = self.conflict_handler.conflicts
563
528
self.conflict_handler.finalize()
566
531
def regen_inventory(self, new_entries):
567
old_entries = self.this_branch.working_tree().read_working_inventory()
532
old_entries = self.this_branch.read_working_inventory()
568
533
new_inventory = {}
570
535
new_entries_map = {}
580
545
entry = old_entries[file_id]
581
546
if entry.parent_id is None:
582
547
return entry.name
583
return pathjoin(id2path(entry.parent_id), entry.name)
548
return os.path.join(id2path(entry.parent_id), entry.name)
585
550
for file_id in old_entries:
586
551
entry = old_entries[file_id]
609
574
parent = by_path[os.path.dirname(path)]
610
abspath = pathjoin(self.this_tree.basedir, path)
575
abspath = os.path.join(self.this_tree.basedir, path)
611
576
kind = bzrlib.osutils.file_kind(abspath)
612
577
new_inventory[file_id] = (path, file_id, parent, kind)
613
578
by_path[path] = file_id