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
27
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
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
32
from bzrlib.trace import mutter, warning
34
from bzrlib.fetch import greedy_fetch
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
37
43
# comments from abentley on irc: merge happens in two stages, each
38
44
# of which generates a changeset object
47
53
conflict that are not explicitly handled cause an exception and
48
54
terminate the merge.
50
def __init__(self, dir, ignore_zero=False):
51
ExceptionConflictHandler.__init__(self, dir)
56
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
57
ExceptionConflictHandler.__init__(self)
53
59
self.ignore_zero = ignore_zero
60
self.this_tree = this_tree
61
self.base_tree = base_tree
62
self.other_tree = other_tree
55
64
def copy(self, source, dest):
56
65
"""Copy the text and mode of a file
108
117
self.add_suffix(this_path, ".THIS")
109
118
self.dump(base_lines, this_path+".BASE")
110
119
self.dump(other_lines, this_path+".OTHER")
111
os.rename(new_file, this_path)
120
rename(new_file, this_path)
112
121
self.conflict("Diff3 conflict encountered in %s" % this_path)
114
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)
131
186
def finalize(self):
132
187
if not self.ignore_zero:
133
188
print "%d conflicts encountered.\n" % self.conflicts
135
190
def get_tree(treespec, temp_root, label, local_branch=None):
136
191
location, revno = treespec
137
branch = find_branch(location)
192
branch = Branch.open_containing(location)
138
193
if revno is None:
140
195
elif revno == -1:
141
revision = branch.last_patch()
196
revision = branch.last_revision()
143
revision = branch.lookup_revision(revno)
198
revision = branch.get_rev_id(revno)
144
199
return branch, get_revid_tree(branch, revision, temp_root, label,
208
267
if self.root is not None:
209
268
return self.tree.abspath(self.tree.id2path(id))
211
if self.tree.inventory[id].kind in ("directory", "root_directory"):
270
kind = self.tree.inventory[id].kind
271
if kind in ("directory", "root_directory"):
212
272
return self.tempdir
213
273
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))
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)
218
286
self.cached[id] = path
219
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)
223
304
def merge(other_revision, base_revision,
224
305
check_clean=True, ignore_zero=False,
236
317
If true, this_dir must have no uncommitted changes before the
238
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
239
324
automatically pulled into the branch.
241
from bzrlib.revision import common_ancestor, MultipleRevisionSources
242
from bzrlib.errors import NoSuchRevision
243
326
tempdir = tempfile.mkdtemp(prefix="bzr-")
245
328
if this_dir is None:
247
this_branch = find_branch(this_dir)
248
this_rev_id = this_branch.last_patch()
330
this_branch = Branch.open_containing(this_dir)
331
this_rev_id = this_branch.last_revision()
249
332
if this_rev_id is None:
250
333
raise BzrCommandError("This branch has no commits")
256
339
other_branch, other_tree = get_tree(other_revision, tempdir, "other",
258
341
if other_revision[1] == -1:
259
other_rev_id = other_branch.last_patch()
342
other_rev_id = other_branch.last_revision()
260
343
if other_rev_id is None:
261
344
raise NoCommits(other_branch)
262
345
other_basis = other_rev_id
263
346
elif other_revision[1] is not None:
264
other_rev_id = other_branch.lookup_revision(other_revision[1])
347
other_rev_id = other_branch.get_rev_id(other_revision[1])
265
348
other_basis = other_rev_id
267
350
other_rev_id = None
268
other_basis = other_branch.last_patch()
351
other_basis = other_branch.last_revision()
269
352
if other_basis is None:
270
353
raise NoCommits(other_branch)
271
354
if base_revision == [None, None]:
281
364
base_branch, base_tree = get_tree(base_revision, tempdir, "base")
282
365
if base_revision[1] == -1:
283
base_rev_id = base_branch.last_patch()
366
base_rev_id = base_branch.last_revision()
284
367
elif base_revision[1] is None:
285
368
base_rev_id = None
287
base_rev_id = base_branch.lookup_revision(base_revision[1])
288
if base_rev_id is not None:
289
base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
290
MultipleRevisionSources(this_branch,
293
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,
294
374
if file_list is None:
295
375
interesting_ids = None
353
433
inv_changes = merge_flex(this_tree, base_tree, other_tree,
354
434
generate_cset_optimized, get_inventory,
355
MergeConflictHandler(base_tree.root,
356
ignore_zero=ignore_zero),
435
MergeConflictHandler(this_tree, base_tree,
436
other_tree, ignore_zero=ignore_zero),
357
437
merge_factory=merge_factory,
358
438
interesting_ids=interesting_ids)