1092.2.24
by Robert Collins
merge from martins newformat branch - brings in transport abstraction |
1 |
import os.path |
2 |
||
493
by Martin Pool
- Merge aaron's merge command |
3 |
import changeset |
4 |
from changeset import Inventory, apply_changeset, invert_dict |
|
1092.2.24
by Robert Collins
merge from martins newformat branch - brings in transport abstraction |
5 |
from bzrlib.osutils import backup_file, rename |
6 |
from bzrlib.merge3 import Merge3 |
|
7 |
import bzrlib |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
8 |
|
9 |
class ApplyMerge3: |
|
10 |
"""Contents-change wrapper around merge3.Merge3"""
|
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
11 |
def __init__(self, file_id, base, other): |
12 |
self.file_id = file_id |
|
13 |
self.base = base |
|
14 |
self.other = other |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
15 |
|
16 |
def __eq__(self, other): |
|
17 |
if not isinstance(other, ApplyMerge3): |
|
18 |
return False |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
19 |
return (self.base == other.base and |
20 |
self.other == other.other and self.file_id == other.file_id) |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
21 |
|
22 |
def __ne__(self, other): |
|
23 |
return not (self == other) |
|
24 |
||
25 |
def apply(self, filename, conflict_handler, reverse=False): |
|
26 |
new_file = filename+".new" |
|
27 |
if not reverse: |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
28 |
base = self.base |
29 |
other = self.other |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
30 |
else: |
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
31 |
base = self.other |
32 |
other = self.base |
|
33 |
def get_lines(tree): |
|
34 |
if self.file_id not in tree: |
|
35 |
raise Exception("%s not in tree" % self.file_id) |
|
36 |
return () |
|
37 |
return tree.get_file(self.file_id).readlines() |
|
1448
by Robert Collins
revert symlinks correctly |
38 |
### garh.
|
39 |
other_entry = other.tree.inventory[self.file_id] |
|
40 |
if other_entry.kind == 'symlink': |
|
41 |
self.apply_symlink(other_entry, base, other, filename) |
|
42 |
return
|
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
43 |
base_lines = get_lines(base) |
44 |
other_lines = get_lines(other) |
|
45 |
m3 = Merge3(base_lines, file(filename, "rb").readlines(), other_lines) |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
46 |
|
47 |
new_conflicts = False |
|
48 |
output_file = file(new_file, "wb") |
|
49 |
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE" |
|
50 |
for line in m3.merge_lines(name_a = "TREE", name_b = "MERGE-SOURCE", |
|
51 |
start_marker=start_marker): |
|
52 |
if line.startswith(start_marker): |
|
53 |
new_conflicts = True |
|
974.1.45
by aaron.bentley at utoronto
Shortened conflict markers to 7 characters, to please smerge |
54 |
output_file.write(line.replace(start_marker, '<' * 7)) |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
55 |
else: |
56 |
output_file.write(line) |
|
57 |
output_file.close() |
|
58 |
if not new_conflicts: |
|
59 |
os.chmod(new_file, os.stat(filename).st_mode) |
|
1185.1.40
by Robert Collins
Merge what applied of Alexander Belchenko's win32 patch. |
60 |
rename(new_file, filename) |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
61 |
return
|
62 |
else: |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
63 |
conflict_handler.merge_conflict(new_file, filename, base_lines, |
64 |
other_lines) |
|
493
by Martin Pool
- Merge aaron's merge command |
65 |
|
1448
by Robert Collins
revert symlinks correctly |
66 |
def apply_symlink(self, other_entry, base, other, filename): |
67 |
if self.file_id in base: |
|
68 |
base_entry = base.tree.inventory[self.file_id] |
|
69 |
base_entry._read_tree_state(base.tree) |
|
70 |
else: |
|
71 |
base_entry = None |
|
72 |
other_entry._read_tree_state(other.tree) |
|
73 |
if not base_entry or other_entry.detect_changes(base_entry): |
|
74 |
other_change = True |
|
75 |
else: |
|
76 |
other_change = False |
|
77 |
this_link = os.readlink(filename) |
|
78 |
if not base_entry or base_entry.symlink_target != this_link: |
|
79 |
this_change = True |
|
80 |
else: |
|
81 |
this_change = False |
|
82 |
if this_change and not other_change: |
|
83 |
pass
|
|
84 |
elif not this_change and other_change: |
|
85 |
os.unlink(filename) |
|
86 |
os.symlink(other_entry.symlink_target, filename) |
|
87 |
elif this_change and other_change: |
|
88 |
# conflict
|
|
89 |
os.unlink(filename) |
|
90 |
os.symlink(other_entry.symlink_target, filename + '.OTHER') |
|
91 |
os.symlink(this_link, filename + '.THIS') |
|
92 |
if base_entry is not None: |
|
93 |
os.symlink(other_entry.symlink_target, filename + '.BASE') |
|
94 |
note("merge3 conflict in '%s'.\n", filename) |
|
95 |
||
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
96 |
|
97 |
class BackupBeforeChange: |
|
98 |
"""Contents-change wrapper to back up file first"""
|
|
99 |
def __init__(self, contents_change): |
|
100 |
self.contents_change = contents_change |
|
101 |
||
102 |
def __eq__(self, other): |
|
103 |
if not isinstance(other, BackupBeforeChange): |
|
104 |
return False |
|
105 |
return (self.contents_change == other.contents_change) |
|
106 |
||
107 |
def __ne__(self, other): |
|
108 |
return not (self == other) |
|
109 |
||
110 |
def apply(self, filename, conflict_handler, reverse=False): |
|
111 |
backup_file(filename) |
|
112 |
self.contents_change.apply(filename, conflict_handler, reverse) |
|
113 |
||
114 |
||
493
by Martin Pool
- Merge aaron's merge command |
115 |
def invert_invent(inventory): |
116 |
invert_invent = {} |
|
974.1.19
by Aaron Bentley
Removed MergeTree.inventory |
117 |
for file_id in inventory: |
118 |
path = inventory.id2path(file_id) |
|
119 |
if path == '': |
|
120 |
path = './.' |
|
121 |
else: |
|
122 |
path = './' + path |
|
123 |
invert_invent[file_id] = path |
|
493
by Martin Pool
- Merge aaron's merge command |
124 |
return invert_invent |
125 |
||
126 |
||
127 |
def merge_flex(this, base, other, changeset_function, inventory_function, |
|
974.1.17
by Aaron Bentley
Switched to using a set of interesting file_ids instead of SourceFile attribute |
128 |
conflict_handler, merge_factory, interesting_ids): |
974.1.19
by Aaron Bentley
Removed MergeTree.inventory |
129 |
cset = changeset_function(base, other, interesting_ids) |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
130 |
new_cset = make_merge_changeset(cset, this, base, other, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
131 |
conflict_handler, merge_factory) |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
132 |
result = apply_changeset(new_cset, invert_invent(this.tree.inventory), |
622
by Martin Pool
Updated merge patch from Aaron |
133 |
this.root, conflict_handler, False) |
134 |
conflict_handler.finalize() |
|
135 |
return result |
|
493
by Martin Pool
- Merge aaron's merge command |
136 |
|
137 |
||
138 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
139 |
def make_merge_changeset(cset, this, base, other, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
140 |
conflict_handler, merge_factory): |
493
by Martin Pool
- Merge aaron's merge command |
141 |
new_cset = changeset.Changeset() |
142 |
def get_this_contents(id): |
|
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
143 |
path = this.readonly_path(id) |
493
by Martin Pool
- Merge aaron's merge command |
144 |
if os.path.isdir(path): |
145 |
return changeset.dir_create |
|
146 |
else: |
|
147 |
return changeset.FileCreate(file(path, "rb").read()) |
|
148 |
||
149 |
for entry in cset.entries.itervalues(): |
|
150 |
if entry.is_boring(): |
|
151 |
new_cset.add_entry(entry) |
|
152 |
else: |
|
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
153 |
new_entry = make_merged_entry(entry, this, base, other, |
154 |
conflict_handler) |
|
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
155 |
new_contents = make_merged_contents(entry, this, base, other, |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
156 |
conflict_handler, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
157 |
merge_factory) |
850
by Martin Pool
- Merge merge updates from aaron |
158 |
new_entry.contents_change = new_contents |
159 |
new_entry.metadata_change = make_merged_metadata(entry, base, other) |
|
160 |
new_cset.add_entry(new_entry) |
|
161 |
||
493
by Martin Pool
- Merge aaron's merge command |
162 |
return new_cset |
163 |
||
974.1.22
by Aaron Bentley
Refactored code |
164 |
class ThreeWayConflict(Exception): |
165 |
def __init__(self, this, base, other): |
|
166 |
self.this = this |
|
167 |
self.base = base |
|
168 |
self.other = other |
|
169 |
msg = "Conflict merging %s %s and %s" % (this, base, other) |
|
170 |
Exception.__init__(self, msg) |
|
171 |
||
172 |
def threeway_select(this, base, other): |
|
173 |
"""Returns a value selected by the three-way algorithm.
|
|
174 |
Raises ThreewayConflict if the algorithm yields a conflict"""
|
|
175 |
if base == other: |
|
176 |
return this |
|
177 |
elif base == this: |
|
178 |
return other |
|
179 |
elif other == this: |
|
180 |
return this |
|
181 |
else: |
|
182 |
raise ThreeWayConflict(this, base, other) |
|
183 |
||
184 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
185 |
def make_merged_entry(entry, this, base, other, conflict_handler): |
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
186 |
from bzrlib.trace import mutter |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
187 |
def entry_data(file_id, tree): |
188 |
assert hasattr(tree, "__contains__"), "%s" % tree |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
189 |
if not tree.has_or_had_id(file_id): |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
190 |
return (None, None, "") |
191 |
entry = tree.tree.inventory[file_id] |
|
192 |
my_dir = tree.id2path(entry.parent_id) |
|
193 |
if my_dir is None: |
|
194 |
my_dir = "" |
|
195 |
return entry.name, entry.parent_id, my_dir |
|
196 |
this_name, this_parent, this_dir = entry_data(entry.id, this) |
|
197 |
base_name, base_parent, base_dir = entry_data(entry.id, base) |
|
198 |
other_name, other_parent, other_dir = entry_data(entry.id, other) |
|
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
199 |
mutter("Dirs: this, base, other %r %r %r" % (this_dir, base_dir, other_dir)) |
200 |
mutter("Names: this, base, other %r %r %r" % (this_name, base_name, other_name)) |
|
974.1.22
by Aaron Bentley
Refactored code |
201 |
old_name = this_name |
202 |
try: |
|
203 |
new_name = threeway_select(this_name, base_name, other_name) |
|
204 |
except ThreeWayConflict: |
|
205 |
new_name = conflict_handler.rename_conflict(entry.id, this_name, |
|
206 |
base_name, other_name) |
|
207 |
||
208 |
old_parent = this_parent |
|
209 |
try: |
|
210 |
new_parent = threeway_select(this_parent, base_parent, other_parent) |
|
211 |
except ThreeWayConflict: |
|
212 |
new_parent = conflict_handler.move_conflict(entry.id, this_dir, |
|
213 |
base_dir, other_dir) |
|
214 |
def get_path(name, parent): |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
215 |
if name is not None: |
216 |
if name == "": |
|
217 |
assert parent is None |
|
218 |
return './.' |
|
974.1.22
by Aaron Bentley
Refactored code |
219 |
parent_dir = {this_parent: this_dir, other_parent: other_dir, |
220 |
base_parent: base_dir} |
|
221 |
directory = parent_dir[parent] |
|
222 |
return os.path.join(directory, name) |
|
223 |
else: |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
224 |
assert parent is None |
974.1.22
by Aaron Bentley
Refactored code |
225 |
return None |
226 |
||
227 |
old_path = get_path(old_name, old_parent) |
|
228 |
||
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
229 |
new_entry = changeset.ChangesetEntry(entry.id, old_parent, old_path) |
974.1.22
by Aaron Bentley
Refactored code |
230 |
new_entry.new_path = get_path(new_name, new_parent) |
493
by Martin Pool
- Merge aaron's merge command |
231 |
new_entry.new_parent = new_parent |
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
232 |
mutter(repr(new_entry)) |
850
by Martin Pool
- Merge merge updates from aaron |
233 |
return new_entry |
234 |
||
235 |
||
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
236 |
def get_contents(entry, tree): |
1185.10.10
by Aaron Bentley
Handled modified files missing from THIS |
237 |
return get_id_contents(entry.id, tree) |
238 |
||
239 |
def get_id_contents(file_id, tree): |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
240 |
"""Get a contents change element suitable for use with ReplaceContents
|
241 |
"""
|
|
1185.10.10
by Aaron Bentley
Handled modified files missing from THIS |
242 |
tree_entry = tree.tree.inventory[file_id] |
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
243 |
if tree_entry.kind == "file": |
1185.10.10
by Aaron Bentley
Handled modified files missing from THIS |
244 |
return changeset.FileCreate(tree.get_file(file_id).read()) |
1092.3.3
by Robert Collins
abently suggested a patch to symlink support |
245 |
elif tree_entry.kind == "symlink": |
1092.2.24
by Robert Collins
merge from martins newformat branch - brings in transport abstraction |
246 |
return changeset.SymlinkCreate(tree.get_symlink_target(file_id)) |
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
247 |
else: |
248 |
assert tree_entry.kind in ("root_directory", "directory") |
|
249 |
return changeset.dir_create |
|
250 |
||
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
251 |
def make_merged_contents(entry, this, base, other, conflict_handler, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
252 |
merge_factory): |
850
by Martin Pool
- Merge merge updates from aaron |
253 |
contents = entry.contents_change |
254 |
if contents is None: |
|
255 |
return None |
|
256 |
this_path = this.readonly_path(entry.id) |
|
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
257 |
def make_merge(): |
850
by Martin Pool
- Merge merge updates from aaron |
258 |
if this_path is None: |
974.1.20
by Aaron Bentley
Eliminated ThreeWayInventory |
259 |
return conflict_handler.missing_for_merge(entry.id, |
260 |
other.id2path(entry.id)) |
|
974.1.23
by Aaron Bentley
Avoided unnecessary temp files |
261 |
return merge_factory(entry.id, base, other) |
850
by Martin Pool
- Merge merge updates from aaron |
262 |
|
263 |
if isinstance(contents, changeset.ReplaceContents): |
|
264 |
if contents.old_contents is None and contents.new_contents is None: |
|
265 |
return None |
|
266 |
if contents.new_contents is None: |
|
1185.10.8
by Aaron Bentley
Conflict handling where OTHER is deleted |
267 |
this_contents = get_contents(entry, this) |
1092.2.24
by Robert Collins
merge from martins newformat branch - brings in transport abstraction |
268 |
if this_path is not None and bzrlib.osutils.lexists(this_path): |
1185.10.8
by Aaron Bentley
Conflict handling where OTHER is deleted |
269 |
if this_contents != contents.old_contents: |
270 |
return conflict_handler.rem_contents_conflict(this_path, |
|
271 |
this_contents, contents.old_contents) |
|
850
by Martin Pool
- Merge merge updates from aaron |
272 |
return contents |
273 |
else: |
|
274 |
return None |
|
275 |
elif contents.old_contents is None: |
|
1092.2.24
by Robert Collins
merge from martins newformat branch - brings in transport abstraction |
276 |
if this_path is None or not bzrlib.osutils.lexists(this_path): |
850
by Martin Pool
- Merge merge updates from aaron |
277 |
return contents |
278 |
else: |
|
974.1.47
by Aaron Bentley
Merged changes from the merge4 branch |
279 |
this_contents = get_contents(entry, this) |
850
by Martin Pool
- Merge merge updates from aaron |
280 |
if this_contents == contents.new_contents: |
281 |
return None |
|
282 |
else: |
|
283 |
other_path = other.readonly_path(entry.id) |
|
284 |
conflict_handler.new_contents_conflict(this_path, |
|
285 |
other_path) |
|
1448
by Robert Collins
revert symlinks correctly |
286 |
elif (isinstance(contents.old_contents, changeset.FileCreate) |
287 |
and isinstance(contents.new_contents, changeset.FileCreate)): |
|
288 |
return make_merge() |
|
289 |
elif (isinstance(contents.old_contents, changeset.SymlinkCreate) |
|
290 |
and isinstance(contents.new_contents, changeset.SymlinkCreate)): |
|
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
291 |
return make_merge() |
850
by Martin Pool
- Merge merge updates from aaron |
292 |
else: |
293 |
raise Exception("Unhandled merge scenario") |
|
294 |
||
295 |
def make_merged_metadata(entry, base, other): |
|
1398
by Robert Collins
integrate in Gustavos x-bit patch |
296 |
metadata = entry.metadata_change |
297 |
if metadata is None: |
|
298 |
return None |
|
1434
by Robert Collins
merge Gustavos executable2 patch |
299 |
if isinstance(metadata, changeset.ChangeExecFlag): |
300 |
if metadata.new_exec_flag is None: |
|
1398
by Robert Collins
integrate in Gustavos x-bit patch |
301 |
return None |
1434
by Robert Collins
merge Gustavos executable2 patch |
302 |
elif metadata.old_exec_flag is None: |
1398
by Robert Collins
integrate in Gustavos x-bit patch |
303 |
return metadata |
304 |
else: |
|
305 |
base_path = base.readonly_path(entry.id) |
|
306 |
other_path = other.readonly_path(entry.id) |
|
1434
by Robert Collins
merge Gustavos executable2 patch |
307 |
return ExecFlagMerge(base_path, other_path) |
493
by Martin Pool
- Merge aaron's merge command |
308 |
|
309 |
||
1434
by Robert Collins
merge Gustavos executable2 patch |
310 |
class ExecFlagMerge(object): |
493
by Martin Pool
- Merge aaron's merge command |
311 |
def __init__(self, base_path, other_path): |
312 |
self.base_path = base_path |
|
313 |
self.other_path = other_path |
|
314 |
||
315 |
def apply(self, filename, conflict_handler, reverse=False): |
|
316 |
if not reverse: |
|
317 |
base = self.base_path |
|
318 |
other = self.other_path |
|
319 |
else: |
|
320 |
base = self.other_path |
|
321 |
other = self.base_path |
|
1434
by Robert Collins
merge Gustavos executable2 patch |
322 |
base_mode = os.stat(base).st_mode |
323 |
base_exec_flag = bool(base_mode & 0111) |
|
324 |
other_mode = os.stat(other).st_mode |
|
325 |
other_exec_flag = bool(other_mode & 0111) |
|
326 |
this_mode = os.stat(filename).st_mode |
|
327 |
this_exec_flag = bool(this_mode & 0111) |
|
328 |
if (base_exec_flag != other_exec_flag and |
|
329 |
this_exec_flag != other_exec_flag): |
|
330 |
assert this_exec_flag == base_exec_flag |
|
331 |
current_mode = os.stat(filename).st_mode |
|
332 |
if other_exec_flag: |
|
333 |
umask = os.umask(0) |
|
334 |
os.umask(umask) |
|
335 |
to_mode = current_mode | (0100 & ~umask) |
|
336 |
# Enable x-bit for others only if they can read it.
|
|
337 |
if current_mode & 0004: |
|
338 |
to_mode |= 0001 & ~umask |
|
339 |
if current_mode & 0040: |
|
340 |
to_mode |= 0010 & ~umask |
|
341 |
else: |
|
342 |
to_mode = current_mode & ~0111 |
|
343 |
os.chmod(filename, to_mode) |
|
344 |