22
from fetch import greedy_fetch
23
24
import bzrlib.osutils
24
25
import bzrlib.revision
25
26
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
26
27
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
27
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
28
from bzrlib.changeset import Inventory, Diff3Merge
28
29
from bzrlib.branch import Branch
29
30
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
30
31
from bzrlib.errors import NoCommits
31
32
from bzrlib.delta import compare_trees
32
from bzrlib.trace import mutter, warning, note
33
from bzrlib.fetch import greedy_fetch, fetch
33
from bzrlib.trace import mutter, warning
34
from bzrlib.fetch import greedy_fetch
34
35
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.
45
37
# comments from abentley on irc: merge happens in two stages, each
46
38
# of which generates a changeset object
55
47
conflict that are not explicitly handled cause an exception and
56
48
terminate the merge.
58
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
50
def __init__(self, ignore_zero=False):
59
51
ExceptionConflictHandler.__init__(self)
61
53
self.ignore_zero = ignore_zero
62
self.this_tree = this_tree
63
self.base_tree = base_tree
64
self.other_tree = other_tree
66
55
def copy(self, source, dest):
67
56
"""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)
193
131
def finalize(self):
194
132
if not self.ignore_zero:
195
note("%d conflicts encountered.\n" % self.conflicts)
133
print "%d conflicts encountered.\n" % self.conflicts
197
135
def get_tree(treespec, temp_root, label, local_branch=None):
198
136
location, revno = treespec
199
branch = Branch.open_containing(location)
137
branch = Branch.open(location)
200
138
if revno is None:
202
140
elif revno == -1:
203
revision = branch.last_revision()
141
revision = branch.last_patch()
205
143
revision = branch.get_rev_id(revno)
206
144
return branch, get_revid_tree(branch, revision, temp_root, label,
274
208
if self.root is not None:
275
209
return self.tree.abspath(self.tree.id2path(id))
277
kind = self.tree.inventory[id].kind
278
if kind in ("directory", "root_directory"):
211
if self.tree.inventory[id].kind in ("directory", "root_directory"):
279
212
return self.tempdir
280
213
if not self.cached.has_key(id):
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)
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))
293
218
self.cached[id] = path
294
219
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)
311
223
def merge(other_revision, base_revision,
312
224
check_clean=True, ignore_zero=False,
324
236
If true, this_dir must have no uncommitted changes before the
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
238
all available ancestors of other_revision and base_revision are
331
239
automatically pulled into the branch.
241
from bzrlib.revision import common_ancestor, MultipleRevisionSources
242
from bzrlib.errors import NoSuchRevision
333
243
tempdir = tempfile.mkdtemp(prefix="bzr-")
335
245
if this_dir is None:
337
247
this_branch = Branch.open_containing(this_dir)
338
this_rev_id = this_branch.last_revision()
248
this_rev_id = this_branch.last_patch()
339
249
if this_rev_id is None:
340
250
raise BzrCommandError("This branch has no commits")
371
281
base_branch, base_tree = get_tree(base_revision, tempdir, "base")
372
282
if base_revision[1] == -1:
373
base_rev_id = base_branch.last_revision()
283
base_rev_id = base_branch.last_patch()
374
284
elif base_revision[1] is None:
375
285
base_rev_id = None
377
287
base_rev_id = base_branch.get_rev_id(base_revision[1])
378
fetch(from_branch=base_branch, to_branch=this_branch)
288
multi_source = MultipleRevisionSources(this_branch, base_branch)
379
289
base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
381
291
if file_list is None:
382
292
interesting_ids = None
412
322
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
415
335
def merge_inner(this_branch, other_tree, base_tree, tempdir,
416
336
ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
417
337
interesting_ids=None):
428
348
return tree.tree.inventory
430
350
inv_changes = merge_flex(this_tree, base_tree, other_tree,
431
generate_changeset, get_inventory,
432
MergeConflictHandler(this_tree, base_tree,
433
other_tree, ignore_zero=ignore_zero),
351
generate_cset_optimized, get_inventory,
352
MergeConflictHandler(ignore_zero=ignore_zero),
434
353
merge_factory=merge_factory,
435
354
interesting_ids=interesting_ids)