52
49
# stage 1: generate OLD->OTHER,
53
50
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
55
class _MergeConflictHandler(ExceptionConflictHandler):
56
"""Handle conflicts encountered while merging.
58
This subclasses ExceptionConflictHandler, so that any types of
59
conflict that are not explicitly handled cause an exception and
62
def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
63
ExceptionConflictHandler.__init__(self)
65
self.ignore_zero = ignore_zero
66
self.this_tree = this_tree
67
self.base_tree = base_tree
68
self.other_tree = other_tree
70
def copy(self, source, dest):
71
"""Copy the text and mode of a file
72
:param source: The path of the file to copy
73
:param dest: The distination file to create
75
s_file = file(source, "rb")
76
d_file = file(dest, "wb")
79
os.chmod(dest, 0777 & os.stat(source).st_mode)
81
def dump(self, lines, dest):
82
"""Copy the text and mode of a file
83
:param source: The path of the file to copy
84
:param dest: The distination file to create
86
d_file = file(dest, "wb")
90
def add_suffix(self, name, suffix, last_new_name=None, fix_inventory=True):
91
"""Rename a file to append a suffix. If the new name exists, the
92
suffix is added repeatedly until a non-existant name is found
94
:param name: The path of the file
95
:param suffix: The suffix to append
96
:param last_new_name: (used for recursive calls) the last name tried
98
if last_new_name is None:
100
new_name = last_new_name+suffix
102
rename(name, new_name)
103
if fix_inventory is True:
105
relpath = self.this_tree.relpath(name)
106
except NotBranchError:
108
if relpath is not None:
109
file_id = self.this_tree.path2id(relpath)
110
if file_id is not None:
111
new_path = self.this_tree.relpath(new_name)
112
rename(new_name, name)
113
self.this_tree.rename_one(relpath, new_path)
114
assert self.this_tree.id2path(file_id) == new_path
116
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
118
return self.add_suffix(name, suffix, last_new_name=new_name,
119
fix_inventory=fix_inventory)
122
def conflict(self, text):
127
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
129
Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER. The
130
main file will be a version with diff3 conflicts.
131
:param new_file: Path to the output file with diff3 markers
132
:param this_path: Path to the file text for the THIS tree
133
:param base_path: Path to the file text for the BASE tree
134
:param other_path: Path to the file text for the OTHER tree
136
self.add_suffix(this_path, ".THIS", fix_inventory=False)
137
self.dump(base_lines, this_path+".BASE")
138
self.dump(other_lines, this_path+".OTHER")
139
rename(new_file, this_path)
140
self.conflict("Diff3 conflict encountered in %s" % this_path)
142
def weave_merge_conflict(self, filename, weave, other_i, out_file):
144
Handle weave conflicts by producing a .THIS, and .OTHER. The
145
main file will be a version with diff3-style conflicts.
147
self.add_suffix(filename, ".THIS", fix_inventory=False)
149
self.dump(weave.get_iter(other_i), filename+".OTHER")
150
self.conflict("Text conflict encountered in %s" % filename)
152
def new_contents_conflict(self, filename, other_contents):
153
"""Conflicting contents for newly added file."""
154
other_contents(filename + ".OTHER", self, False)
155
self.conflict("Conflict in newly added file %s" % filename)
158
def target_exists(self, entry, target, old_path):
159
"""Handle the case when the target file or dir exists"""
160
moved_path = self.add_suffix(target, ".moved")
161
self.conflict("Moved existing %s to %s" % (target, moved_path))
163
def rmdir_non_empty(self, filename):
164
"""Handle the case where the dir to be removed still has contents"""
165
self.conflict("Directory %s not removed because it is not empty"\
169
def rem_contents_conflict(self, filename, this_contents, base_contents):
170
base_contents(filename+".BASE", self)
171
this_contents(filename+".THIS", self)
172
self.conflict("Other branch deleted locally modified file %s" %
174
return ReplaceContents(this_contents, None)
176
def abs_this_path(self, file_id):
177
"""Return the absolute path for a file_id in the this tree."""
178
return self.this_tree.id2abspath(file_id)
180
def add_missing_parents(self, file_id, tree):
181
"""If some of the parents for file_id are missing, add them."""
182
entry = tree.inventory[file_id]
183
if entry.parent_id not in self.this_tree:
184
return self.create_all_missing(entry.parent_id, tree)
186
return self.abs_this_path(entry.parent_id)
188
def create_all_missing(self, file_id, tree):
189
"""Add contents for a file_id and all its parents to a tree."""
190
entry = tree.inventory[file_id]
191
if entry.parent_id is not None and entry.parent_id not in self.this_tree:
192
abspath = self.create_all_missing(entry.parent_id, tree)
194
abspath = self.abs_this_path(entry.parent_id)
195
entry_path = pathjoin(abspath, entry.name)
196
if not os.path.isdir(entry_path):
197
self.create(file_id, entry_path, tree)
200
def create(self, file_id, path, tree):
201
"""Uses tree data to create a filesystem object for the file_id"""
202
from bzrlib._changeset import get_contents
203
get_contents(tree, file_id)(path, self)
205
def missing_for_merge(self, file_id, other_path):
206
"""The file_id doesn't exist in THIS, but does in OTHER and BASE"""
207
self.conflict("Other branch modified locally deleted file %s" %
209
parent_dir = self.add_missing_parents(file_id, self.other_tree)
210
stem = pathjoin(parent_dir, os.path.basename(other_path))
211
self.create(file_id, stem+".OTHER", self.other_tree)
212
self.create(file_id, stem+".BASE", self.base_tree)
214
def threeway_contents_conflict(filename, this_contents, base_contents,
216
self.conflict("Three-way conflict merging %s" % filename)
219
if self.conflicts == 0:
220
if not self.ignore_zero:
221
note("All changes applied successfully.")
223
note("%d conflicts encountered." % self.conflicts)
225
52
def _get_tree(treespec, local_branch=None):
226
53
location, revno = treespec
227
54
branch = Branch.open_containing(location)[0]