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
29
from bzrlib.branch import find_branch
30
from bzrlib.errors import BzrCommandError, UnrelatedBranches
27
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
28
from bzrlib.branch import Branch
29
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
30
from bzrlib.errors import NoCommits
31
31
from bzrlib.delta import compare_trees
32
32
from bzrlib.trace import mutter, warning
33
from bzrlib.fetch import greedy_fetch
33
from bzrlib.fetch import greedy_fetch, fetch
34
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
36
43
# comments from abentley on irc: merge happens in two stages, each
37
44
# of which generates a changeset object
46
53
conflict that are not explicitly handled cause an exception and
47
54
terminate the merge.
49
def __init__(self, dir, ignore_zero=False):
50
ExceptionConflictHandler.__init__(self, dir)
56
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
57
ExceptionConflictHandler.__init__(self)
52
59
self.ignore_zero = ignore_zero
60
self.this_tree = this_tree
61
self.base_tree = base_tree
62
self.other_tree = other_tree
54
64
def copy(self, source, dest):
55
65
"""Copy the text and mode of a file
107
117
self.add_suffix(this_path, ".THIS")
108
118
self.dump(base_lines, this_path+".BASE")
109
119
self.dump(other_lines, this_path+".OTHER")
110
os.rename(new_file, this_path)
120
rename(new_file, this_path)
111
121
self.conflict("Diff3 conflict encountered in %s" % this_path)
113
123
def new_contents_conflict(self, filename, other_contents):
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)
130
186
def finalize(self):
131
187
if not self.ignore_zero:
132
188
print "%d conflicts encountered.\n" % self.conflicts
134
190
def get_tree(treespec, temp_root, label, local_branch=None):
135
191
location, revno = treespec
136
branch = find_branch(location)
192
branch = Branch.open_containing(location)
137
193
if revno is None:
139
195
elif revno == -1:
140
revision = branch.last_patch()
196
revision = branch.last_revision()
142
revision = branch.lookup_revision(revno)
198
revision = branch.get_rev_id(revno)
143
199
return branch, get_revid_tree(branch, revision, temp_root, label,
207
267
if self.root is not None:
208
268
return self.tree.abspath(self.tree.id2path(id))
210
if self.tree.inventory[id].kind in ("directory", "root_directory"):
270
kind = self.tree.inventory[id].kind
271
if kind in ("directory", "root_directory"):
211
272
return self.tempdir
212
273
if not self.cached.has_key(id):
213
path = os.path.join(self.tempdir, "texts", id)
214
outfile = file(path, "wb")
215
outfile.write(self.tree.get_file(id).read())
216
assert(os.path.exists(path))
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
286
self.cached[id] = path
218
287
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)
222
304
def merge(other_revision, base_revision,
223
305
check_clean=True, ignore_zero=False,
235
317
If true, this_dir must have no uncommitted changes before the
237
all available ancestors of other_revision and base_revision are
319
ignore_zero - If true, suppress the "zero conflicts" message when
320
there are no conflicts; should be set when doing something we expect
321
to complete perfectly.
323
All available ancestors of other_revision and base_revision are
238
324
automatically pulled into the branch.
240
from bzrlib.revision import common_ancestor, MultipleRevisionSources
241
from bzrlib.errors import NoSuchRevision
242
326
tempdir = tempfile.mkdtemp(prefix="bzr-")
244
328
if this_dir is None:
246
this_branch = find_branch(this_dir)
247
this_rev_id = this_branch.last_patch()
330
this_branch = Branch.open_containing(this_dir)
331
this_rev_id = this_branch.last_revision()
248
332
if this_rev_id is None:
249
333
raise BzrCommandError("This branch has no commits")
255
339
other_branch, other_tree = get_tree(other_revision, tempdir, "other",
257
341
if other_revision[1] == -1:
258
other_rev_id = other_branch.last_patch()
342
other_rev_id = other_branch.last_revision()
343
if other_rev_id is None:
344
raise NoCommits(other_branch)
259
345
other_basis = other_rev_id
260
346
elif other_revision[1] is not None:
261
other_rev_id = other_branch.lookup_revision(other_revision[1])
347
other_rev_id = other_branch.get_rev_id(other_revision[1])
262
348
other_basis = other_rev_id
264
350
other_rev_id = None
265
other_basis = other_branch.last_patch()
351
other_basis = other_branch.last_revision()
352
if other_basis is None:
353
raise NoCommits(other_branch)
266
354
if base_revision == [None, None]:
267
base_rev_id = common_ancestor(this_rev_id, other_basis,
269
if base_rev_id is None:
356
base_rev_id = common_ancestor(this_rev_id, other_basis,
358
except NoCommonAncestor:
270
359
raise UnrelatedBranches()
271
360
base_tree = get_revid_tree(this_branch, base_rev_id, tempdir,
275
364
base_branch, base_tree = get_tree(base_revision, tempdir, "base")
276
365
if base_revision[1] == -1:
277
base_rev_id = base_branch.last_patch()
366
base_rev_id = base_branch.last_revision()
278
367
elif base_revision[1] is None:
279
368
base_rev_id = None
281
base_rev_id = base_branch.lookup_revision(base_revision[1])
282
if base_rev_id is not None:
283
base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
284
MultipleRevisionSources(this_branch,
287
base_is_ancestor = False
370
base_rev_id = base_branch.get_rev_id(base_revision[1])
371
fetch(from_branch=base_branch, to_branch=this_branch)
372
base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
288
374
if file_list is None:
289
375
interesting_ids = None
347
433
inv_changes = merge_flex(this_tree, base_tree, other_tree,
348
434
generate_cset_optimized, get_inventory,
349
MergeConflictHandler(base_tree.root,
350
ignore_zero=ignore_zero),
435
MergeConflictHandler(this_tree, base_tree,
436
other_tree, ignore_zero=ignore_zero),
351
437
merge_factory=merge_factory,
352
438
interesting_ids=interesting_ids)