22
from fetch import greedy_fetch
24
23
import bzrlib.osutils
25
24
import bzrlib.revision
26
25
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
27
26
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
28
from bzrlib.changeset import Inventory, Diff3Merge
27
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
29
28
from bzrlib.branch import Branch
30
29
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
31
30
from bzrlib.errors import NoCommits
32
31
from bzrlib.delta import compare_trees
33
from bzrlib.trace import mutter, warning
34
from bzrlib.fetch import greedy_fetch
32
from bzrlib.trace import mutter, warning, note
33
from bzrlib.fetch import greedy_fetch, fetch
35
34
from bzrlib.revision import is_ancestor
35
from bzrlib.osutils import rename
36
from bzrlib.revision import common_ancestor, MultipleRevisionSources
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
42
# NOTABUG: merge is a helper for commandline functions. merge_inner is the
43
# the core functionality.
37
45
# comments from abentley on irc: merge happens in two stages, each
38
46
# of which generates a changeset object
47
55
conflict that are not explicitly handled cause an exception and
48
56
terminate the merge.
50
def __init__(self, ignore_zero=False):
58
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
51
59
ExceptionConflictHandler.__init__(self)
53
61
self.ignore_zero = ignore_zero
62
self.this_tree = this_tree
63
self.base_tree = base_tree
64
self.other_tree = other_tree
55
66
def copy(self, source, dest):
56
67
"""Copy the text and mode of a file
142
def rem_contents_conflict(self, filename, this_contents, base_contents):
143
base_contents(filename+".BASE", self, False)
144
this_contents(filename+".THIS", self, False)
145
return ReplaceContents(this_contents, None)
147
def rem_contents_conflict(self, filename, this_contents, base_contents):
148
base_contents(filename+".BASE", self, False)
149
this_contents(filename+".THIS", self, False)
150
self.conflict("Other branch deleted locally modified file %s" %
152
return ReplaceContents(this_contents, None)
154
def abs_this_path(self, file_id):
155
"""Return the absolute path for a file_id in the this tree."""
156
relpath = self.this_tree.id2path(file_id)
157
return self.this_tree.tree.abspath(relpath)
159
def add_missing_parents(self, file_id, tree):
160
"""If some of the parents for file_id are missing, add them."""
161
entry = tree.tree.inventory[file_id]
162
if entry.parent_id not in self.this_tree:
163
return self.create_all_missing(entry.parent_id, tree)
165
return self.abs_this_path(entry.parent_id)
167
def create_all_missing(self, file_id, tree):
168
"""Add contents for a file_id and all its parents to a tree."""
169
entry = tree.tree.inventory[file_id]
170
if entry.parent_id is not None and entry.parent_id not in self.this_tree:
171
abspath = self.create_all_missing(entry.parent_id, tree)
173
abspath = self.abs_this_path(entry.parent_id)
174
entry_path = os.path.join(abspath, entry.name)
175
if not os.path.isdir(entry_path):
176
self.create(file_id, entry_path, tree)
179
def create(self, file_id, path, tree, reverse=False):
180
"""Uses tree data to create a filesystem object for the file_id"""
181
from merge_core import get_id_contents
182
get_id_contents(file_id, tree)(path, self, reverse)
184
def missing_for_merge(self, file_id, other_path):
185
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
186
self.conflict("Other branch modified locally deleted file %s" %
188
parent_dir = self.add_missing_parents(file_id, self.other_tree)
189
stem = os.path.join(parent_dir, os.path.basename(other_path))
190
self.create(file_id, stem+".OTHER", self.other_tree)
191
self.create(file_id, stem+".BASE", self.base_tree)
131
193
def finalize(self):
132
194
if not self.ignore_zero:
133
print "%d conflicts encountered.\n" % self.conflicts
195
note("%d conflicts encountered.\n" % self.conflicts)
135
197
def get_tree(treespec, temp_root, label, local_branch=None):
136
198
location, revno = treespec
208
274
if self.root is not None:
209
275
return self.tree.abspath(self.tree.id2path(id))
211
if self.tree.inventory[id].kind in ("directory", "root_directory"):
277
kind = self.tree.inventory[id].kind
278
if kind in ("directory", "root_directory"):
212
279
return self.tempdir
213
280
if not self.cached.has_key(id):
214
path = os.path.join(self.tempdir, "texts", id)
215
outfile = file(path, "wb")
216
outfile.write(self.tree.get_file(id).read())
217
assert(os.path.exists(path))
282
path = os.path.join(self.tempdir, "texts", id)
283
outfile = file(path, "wb")
284
outfile.write(self.tree.get_file(id).read())
285
assert(bzrlib.osutils.lexists(path))
286
if self.tree.is_executable(id):
289
assert kind == "symlink"
290
path = os.path.join(self.tempdir, "symlinks", id)
291
target = self.tree.get_symlink_target(id)
292
os.symlink(target, path)
218
293
self.cached[id] = path
219
294
return self.cached[id]
297
def build_working_dir(to_dir):
298
"""Build a working directory in an empty directory.
300
to_dir is a directory containing branch metadata but no working files,
301
typically constructed by cloning an existing branch.
303
This is split out as a special idiomatic case of merge. It could
304
eventually be done by just building the tree directly calling into
305
lower-level code (e.g. constructing a changeset).
307
merge((to_dir, -1), (to_dir, 0), this_dir=to_dir,
308
check_clean=False, ignore_zero=True)
223
311
def merge(other_revision, base_revision,
224
312
check_clean=True, ignore_zero=False,
236
324
If true, this_dir must have no uncommitted changes before the
238
all available ancestors of other_revision and base_revision are
326
ignore_zero - If true, suppress the "zero conflicts" message when
327
there are no conflicts; should be set when doing something we expect
328
to complete perfectly.
330
All available ancestors of other_revision and base_revision are
239
331
automatically pulled into the branch.
241
from bzrlib.revision import common_ancestor, MultipleRevisionSources
242
from bzrlib.errors import NoSuchRevision
243
333
tempdir = tempfile.mkdtemp(prefix="bzr-")
245
335
if this_dir is None:
247
337
this_branch = Branch.open_containing(this_dir)
248
this_rev_id = this_branch.last_patch()
338
this_rev_id = this_branch.last_revision()
249
339
if this_rev_id is None:
250
340
raise BzrCommandError("This branch has no commits")
281
371
base_branch, base_tree = get_tree(base_revision, tempdir, "base")
282
372
if base_revision[1] == -1:
283
base_rev_id = base_branch.last_patch()
373
base_rev_id = base_branch.last_revision()
284
374
elif base_revision[1] is None:
285
375
base_rev_id = None
287
377
base_rev_id = base_branch.get_rev_id(base_revision[1])
288
multi_source = MultipleRevisionSources(this_branch, base_branch)
378
fetch(from_branch=base_branch, to_branch=this_branch)
289
379
base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
291
381
if file_list is None:
292
382
interesting_ids = None
322
412
source_file.interesting = source_file.id in interesting_ids
325
def generate_cset_optimized(tree_a, tree_b, interesting_ids=None):
326
"""Generate a changeset. If interesting_ids is supplied, only changes
327
to those files will be shown. Metadata changes are stripped.
329
cset = generate_changeset(tree_a, tree_b, interesting_ids)
330
for entry in cset.entries.itervalues():
331
entry.metadata_change = None
335
415
def merge_inner(this_branch, other_tree, base_tree, tempdir,
336
416
ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
337
417
interesting_ids=None):
348
428
return tree.tree.inventory
350
430
inv_changes = merge_flex(this_tree, base_tree, other_tree,
351
generate_cset_optimized, get_inventory,
352
MergeConflictHandler(ignore_zero=ignore_zero),
431
generate_changeset, get_inventory,
432
MergeConflictHandler(this_tree, base_tree,
433
other_tree, ignore_zero=ignore_zero),
353
434
merge_factory=merge_factory,
354
435
interesting_ids=interesting_ids)