24
24
import bzrlib.revision
25
25
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
26
26
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
27
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
27
from bzrlib.changeset import Inventory, Diff3Merge
28
28
from bzrlib.branch import Branch
29
29
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
30
30
from bzrlib.errors import NoCommits
36
36
from bzrlib.revision import common_ancestor, MultipleRevisionSources
37
37
from bzrlib.errors import NoSuchRevision
39
# TODO: build_working_dir can be built on something simpler than merge()
41
# FIXME: merge() parameters seem oriented towards the command line
43
40
# comments from abentley on irc: merge happens in two stages, each
44
41
# of which generates a changeset object
53
50
conflict that are not explicitly handled cause an exception and
54
51
terminate the merge.
56
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
53
def __init__(self, ignore_zero=False):
57
54
ExceptionConflictHandler.__init__(self)
59
56
self.ignore_zero = ignore_zero
60
self.this_tree = this_tree
61
self.base_tree = base_tree
62
self.other_tree = other_tree
64
58
def copy(self, source, dest):
65
59
"""Copy the text and mode of a file
140
def rem_contents_conflict(self, filename, this_contents, base_contents):
141
base_contents(filename+".BASE", self, False)
142
this_contents(filename+".THIS", self, False)
143
self.conflict("Other branch deleted locally modified file %s" %
145
return ReplaceContents(this_contents, None)
147
def abs_this_path(self, file_id):
148
"""Return the absolute path for a file_id in the this tree."""
149
relpath = self.this_tree.id2path(file_id)
150
return self.this_tree.tree.abspath(relpath)
152
def add_missing_parents(self, file_id, tree):
153
"""If some of the parents for file_id are missing, add them."""
154
entry = tree.tree.inventory[file_id]
155
if entry.parent_id not in self.this_tree:
156
return self.create_all_missing(entry.parent_id, tree)
158
return self.abs_this_path(entry.parent_id)
160
def create_all_missing(self, file_id, tree):
161
"""Add contents for a file_id and all its parents to a tree."""
162
entry = tree.tree.inventory[file_id]
163
if entry.parent_id is not None and entry.parent_id not in self.this_tree:
164
abspath = self.create_all_missing(entry.parent_id, tree)
166
abspath = self.abs_this_path(entry.parent_id)
167
entry_path = os.path.join(abspath, entry.name)
168
if not os.path.isdir(entry_path):
169
self.create(file_id, entry_path, tree)
172
def create(self, file_id, path, tree, reverse=False):
173
"""Uses tree data to create a filesystem object for the file_id"""
174
from merge_core import get_id_contents
175
get_id_contents(file_id, tree)(path, self, reverse)
177
def missing_for_merge(self, file_id, other_path):
178
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
179
self.conflict("Other branch modified locally deleted file %s" %
181
parent_dir = self.add_missing_parents(file_id, self.other_tree)
182
stem = os.path.join(parent_dir, os.path.basename(other_path))
183
self.create(file_id, stem+".OTHER", self.other_tree)
184
self.create(file_id, stem+".BASE", self.base_tree)
186
134
def finalize(self):
187
135
if not self.ignore_zero:
188
136
print "%d conflicts encountered.\n" % self.conflicts
267
211
if self.root is not None:
268
212
return self.tree.abspath(self.tree.id2path(id))
270
kind = self.tree.inventory[id].kind
271
if kind in ("directory", "root_directory"):
214
if self.tree.inventory[id].kind in ("directory", "root_directory"):
272
215
return self.tempdir
273
216
if not self.cached.has_key(id):
275
path = os.path.join(self.tempdir, "texts", id)
276
outfile = file(path, "wb")
277
outfile.write(self.tree.get_file(id).read())
278
assert(bzrlib.osutils.lexists(path))
279
if self.tree.is_executable(id):
282
assert kind == "symlink"
283
path = os.path.join(self.tempdir, "symlinks", id)
284
target = self.tree.get_symlink_target(id)
285
os.symlink(target, path)
217
path = os.path.join(self.tempdir, "texts", id)
218
outfile = file(path, "wb")
219
outfile.write(self.tree.get_file(id).read())
220
assert(os.path.exists(path))
286
221
self.cached[id] = path
287
222
return self.cached[id]
290
def build_working_dir(to_dir):
291
"""Build a working directory in an empty directory.
293
to_dir is a directory containing branch metadata but no working files,
294
typically constructed by cloning an existing branch.
296
This is split out as a special idiomatic case of merge. It could
297
eventually be done by just building the tree directly calling into
298
lower-level code (e.g. constructing a changeset).
300
merge((to_dir, -1), (to_dir, 0), this_dir=to_dir,
301
check_clean=False, ignore_zero=True)
304
226
def merge(other_revision, base_revision,
305
227
check_clean=True, ignore_zero=False,
433
352
inv_changes = merge_flex(this_tree, base_tree, other_tree,
434
353
generate_cset_optimized, get_inventory,
435
MergeConflictHandler(this_tree, base_tree,
436
other_tree, ignore_zero=ignore_zero),
354
MergeConflictHandler(ignore_zero=ignore_zero),
437
355
merge_factory=merge_factory,
438
356
interesting_ids=interesting_ids)