493
by Martin Pool
- Merge aaron's merge command |
1 |
import changeset |
2 |
from changeset import Inventory, apply_changeset, invert_dict |
|
3 |
import os.path |
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
4 |
from osutils import backup_file |
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
5 |
from merge3 import Merge3 |
6 |
||
7 |
class ApplyMerge3: |
|
8 |
"""Contents-change wrapper around merge3.Merge3"""
|
|
1069
by Martin Pool
- merge merge improvements from aaron |
9 |
def __init__(self, file_id, base, other): |
10 |
self.file_id = file_id |
|
11 |
self.base = base |
|
12 |
self.other = other |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
13 |
|
14 |
def __eq__(self, other): |
|
15 |
if not isinstance(other, ApplyMerge3): |
|
16 |
return False |
|
1069
by Martin Pool
- merge merge improvements from aaron |
17 |
return (self.base == other.base and |
18 |
self.other == other.other and self.file_id == other.file_id) |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
19 |
|
20 |
def __ne__(self, other): |
|
21 |
return not (self == other) |
|
22 |
||
23 |
||
24 |
def apply(self, filename, conflict_handler, reverse=False): |
|
25 |
new_file = filename+".new" |
|
26 |
if not reverse: |
|
1069
by Martin Pool
- merge merge improvements from aaron |
27 |
base = self.base |
28 |
other = self.other |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
29 |
else: |
1069
by Martin Pool
- merge merge improvements from aaron |
30 |
base = self.other |
31 |
other = self.base |
|
32 |
def get_lines(tree): |
|
33 |
if self.file_id not in tree: |
|
34 |
raise Exception("%s not in tree" % self.file_id) |
|
35 |
return () |
|
36 |
return tree.get_file(self.file_id).readlines() |
|
37 |
base_lines = get_lines(base) |
|
38 |
other_lines = get_lines(other) |
|
39 |
m3 = Merge3(base_lines, file(filename, "rb").readlines(), other_lines) |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
40 |
|
41 |
new_conflicts = False |
|
42 |
output_file = file(new_file, "wb") |
|
43 |
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE" |
|
44 |
for line in m3.merge_lines(name_a = "TREE", name_b = "MERGE-SOURCE", |
|
45 |
start_marker=start_marker): |
|
46 |
if line.startswith(start_marker): |
|
47 |
new_conflicts = True |
|
48 |
output_file.write(line.replace(start_marker, '<<<<<<<<')) |
|
49 |
else: |
|
50 |
output_file.write(line) |
|
51 |
output_file.close() |
|
52 |
if not new_conflicts: |
|
53 |
os.chmod(new_file, os.stat(filename).st_mode) |
|
54 |
os.rename(new_file, filename) |
|
55 |
return
|
|
56 |
else: |
|
1069
by Martin Pool
- merge merge improvements from aaron |
57 |
conflict_handler.merge_conflict(new_file, filename, base_lines, |
58 |
other_lines) |
|
493
by Martin Pool
- Merge aaron's merge command |
59 |
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
60 |
|
61 |
class BackupBeforeChange: |
|
62 |
"""Contents-change wrapper to back up file first"""
|
|
63 |
def __init__(self, contents_change): |
|
64 |
self.contents_change = contents_change |
|
65 |
||
66 |
def __eq__(self, other): |
|
67 |
if not isinstance(other, BackupBeforeChange): |
|
68 |
return False |
|
69 |
return (self.contents_change == other.contents_change) |
|
70 |
||
71 |
def __ne__(self, other): |
|
72 |
return not (self == other) |
|
73 |
||
74 |
def apply(self, filename, conflict_handler, reverse=False): |
|
75 |
backup_file(filename) |
|
76 |
self.contents_change.apply(filename, conflict_handler, reverse) |
|
77 |
||
78 |
||
493
by Martin Pool
- Merge aaron's merge command |
79 |
def invert_invent(inventory): |
80 |
invert_invent = {} |
|
1069
by Martin Pool
- merge merge improvements from aaron |
81 |
for file_id in inventory: |
82 |
path = inventory.id2path(file_id) |
|
83 |
if path == '': |
|
84 |
path = './.' |
|
85 |
else: |
|
86 |
path = './' + path |
|
87 |
invert_invent[file_id] = path |
|
493
by Martin Pool
- Merge aaron's merge command |
88 |
return invert_invent |
89 |
||
90 |
||
91 |
def merge_flex(this, base, other, changeset_function, inventory_function, |
|
1069
by Martin Pool
- merge merge improvements from aaron |
92 |
conflict_handler, merge_factory, interesting_ids): |
93 |
cset = changeset_function(base, other, interesting_ids) |
|
94 |
new_cset = make_merge_changeset(cset, this, base, other, |
|
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
95 |
conflict_handler, merge_factory) |
1069
by Martin Pool
- merge merge improvements from aaron |
96 |
result = apply_changeset(new_cset, invert_invent(this.tree.inventory), |
622
by Martin Pool
Updated merge patch from Aaron |
97 |
this.root, conflict_handler, False) |
98 |
conflict_handler.finalize() |
|
99 |
return result |
|
493
by Martin Pool
- Merge aaron's merge command |
100 |
|
101 |
||
102 |
||
1069
by Martin Pool
- merge merge improvements from aaron |
103 |
def make_merge_changeset(cset, this, base, other, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
104 |
conflict_handler, merge_factory): |
493
by Martin Pool
- Merge aaron's merge command |
105 |
new_cset = changeset.Changeset() |
106 |
def get_this_contents(id): |
|
1069
by Martin Pool
- merge merge improvements from aaron |
107 |
path = this.readonly_path(id) |
493
by Martin Pool
- Merge aaron's merge command |
108 |
if os.path.isdir(path): |
109 |
return changeset.dir_create |
|
110 |
else: |
|
111 |
return changeset.FileCreate(file(path, "rb").read()) |
|
112 |
||
113 |
for entry in cset.entries.itervalues(): |
|
114 |
if entry.is_boring(): |
|
115 |
new_cset.add_entry(entry) |
|
116 |
else: |
|
1069
by Martin Pool
- merge merge improvements from aaron |
117 |
new_entry = make_merged_entry(entry, this, base, other, |
118 |
conflict_handler) |
|
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
119 |
new_contents = make_merged_contents(entry, this, base, other, |
1069
by Martin Pool
- merge merge improvements from aaron |
120 |
conflict_handler, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
121 |
merge_factory) |
850
by Martin Pool
- Merge merge updates from aaron |
122 |
new_entry.contents_change = new_contents |
123 |
new_entry.metadata_change = make_merged_metadata(entry, base, other) |
|
124 |
new_cset.add_entry(new_entry) |
|
125 |
||
493
by Martin Pool
- Merge aaron's merge command |
126 |
return new_cset |
127 |
||
1069
by Martin Pool
- merge merge improvements from aaron |
128 |
class ThreeWayConflict(Exception): |
129 |
def __init__(self, this, base, other): |
|
130 |
self.this = this |
|
131 |
self.base = base |
|
132 |
self.other = other |
|
133 |
msg = "Conflict merging %s %s and %s" % (this, base, other) |
|
134 |
Exception.__init__(self, msg) |
|
135 |
||
136 |
def threeway_select(this, base, other): |
|
137 |
"""Returns a value selected by the three-way algorithm.
|
|
138 |
Raises ThreewayConflict if the algorithm yields a conflict"""
|
|
139 |
if base == other: |
|
140 |
return this |
|
141 |
elif base == this: |
|
142 |
return other |
|
143 |
elif other == this: |
|
144 |
return this |
|
145 |
else: |
|
146 |
raise ThreeWayConflict(this, base, other) |
|
147 |
||
148 |
||
149 |
def make_merged_entry(entry, this, base, other, conflict_handler): |
|
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
150 |
from bzrlib.trace import mutter |
1069
by Martin Pool
- merge merge improvements from aaron |
151 |
def entry_data(file_id, tree): |
152 |
assert hasattr(tree, "__contains__"), "%s" % tree |
|
1094
by Martin Pool
- merge aaron's merge improvements 999..1008 |
153 |
if not tree.has_or_had_id(file_id): |
1069
by Martin Pool
- merge merge improvements from aaron |
154 |
return (None, None, "") |
155 |
entry = tree.tree.inventory[file_id] |
|
156 |
my_dir = tree.id2path(entry.parent_id) |
|
157 |
if my_dir is None: |
|
158 |
my_dir = "" |
|
159 |
return entry.name, entry.parent_id, my_dir |
|
160 |
this_name, this_parent, this_dir = entry_data(entry.id, this) |
|
161 |
base_name, base_parent, base_dir = entry_data(entry.id, base) |
|
162 |
other_name, other_parent, other_dir = entry_data(entry.id, other) |
|
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
163 |
mutter("Dirs: this, base, other %r %r %r" % (this_dir, base_dir, other_dir)) |
164 |
mutter("Names: this, base, other %r %r %r" % (this_name, base_name, other_name)) |
|
1069
by Martin Pool
- merge merge improvements from aaron |
165 |
old_name = this_name |
166 |
try: |
|
167 |
new_name = threeway_select(this_name, base_name, other_name) |
|
168 |
except ThreeWayConflict: |
|
169 |
new_name = conflict_handler.rename_conflict(entry.id, this_name, |
|
170 |
base_name, other_name) |
|
171 |
||
172 |
old_parent = this_parent |
|
173 |
try: |
|
174 |
new_parent = threeway_select(this_parent, base_parent, other_parent) |
|
175 |
except ThreeWayConflict: |
|
176 |
new_parent = conflict_handler.move_conflict(entry.id, this_dir, |
|
177 |
base_dir, other_dir) |
|
178 |
def get_path(name, parent): |
|
1094
by Martin Pool
- merge aaron's merge improvements 999..1008 |
179 |
if name is not None: |
180 |
if name == "": |
|
181 |
assert parent is None |
|
182 |
return './.' |
|
1069
by Martin Pool
- merge merge improvements from aaron |
183 |
parent_dir = {this_parent: this_dir, other_parent: other_dir, |
184 |
base_parent: base_dir} |
|
185 |
directory = parent_dir[parent] |
|
186 |
return os.path.join(directory, name) |
|
187 |
else: |
|
1094
by Martin Pool
- merge aaron's merge improvements 999..1008 |
188 |
assert parent is None |
1069
by Martin Pool
- merge merge improvements from aaron |
189 |
return None |
190 |
||
191 |
old_path = get_path(old_name, old_parent) |
|
192 |
||
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
193 |
new_entry = changeset.ChangesetEntry(entry.id, old_parent, old_path) |
1069
by Martin Pool
- merge merge improvements from aaron |
194 |
new_entry.new_path = get_path(new_name, new_parent) |
493
by Martin Pool
- Merge aaron's merge command |
195 |
new_entry.new_parent = new_parent |
909.1.2
by aaron.bentley at utoronto
Fixed rename handling in merge |
196 |
mutter(repr(new_entry)) |
850
by Martin Pool
- Merge merge updates from aaron |
197 |
return new_entry |
198 |
||
199 |
||
1094
by Martin Pool
- merge aaron's merge improvements 999..1008 |
200 |
def get_contents(entry, tree): |
201 |
"""Get a contents change element suitable for use with ReplaceContents
|
|
202 |
"""
|
|
203 |
tree_entry = tree.tree.inventory[entry.id] |
|
204 |
if tree_entry.kind == "file": |
|
205 |
return changeset.FileCreate(tree.get_file(entry.id).read()) |
|
206 |
else: |
|
207 |
assert tree_entry.kind in ("root_directory", "directory") |
|
208 |
return changeset.dir_create |
|
209 |
||
210 |
||
1069
by Martin Pool
- merge merge improvements from aaron |
211 |
def make_merged_contents(entry, this, base, other, conflict_handler, |
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
212 |
merge_factory): |
850
by Martin Pool
- Merge merge updates from aaron |
213 |
contents = entry.contents_change |
214 |
if contents is None: |
|
215 |
return None |
|
216 |
this_path = this.readonly_path(entry.id) |
|
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
217 |
def make_merge(): |
850
by Martin Pool
- Merge merge updates from aaron |
218 |
if this_path is None: |
1069
by Martin Pool
- merge merge improvements from aaron |
219 |
return conflict_handler.missing_for_merge(entry.id, |
220 |
other.id2path(entry.id)) |
|
221 |
return merge_factory(entry.id, base, other) |
|
850
by Martin Pool
- Merge merge updates from aaron |
222 |
|
223 |
if isinstance(contents, changeset.ReplaceContents): |
|
224 |
if contents.old_contents is None and contents.new_contents is None: |
|
225 |
return None |
|
226 |
if contents.new_contents is None: |
|
227 |
if this_path is not None and os.path.exists(this_path): |
|
228 |
return contents |
|
229 |
else: |
|
230 |
return None |
|
231 |
elif contents.old_contents is None: |
|
232 |
if this_path is None or not os.path.exists(this_path): |
|
233 |
return contents |
|
234 |
else: |
|
1094
by Martin Pool
- merge aaron's merge improvements 999..1008 |
235 |
this_contents = get_contents(entry, this) |
850
by Martin Pool
- Merge merge updates from aaron |
236 |
if this_contents == contents.new_contents: |
237 |
return None |
|
238 |
else: |
|
239 |
other_path = other.readonly_path(entry.id) |
|
240 |
conflict_handler.new_contents_conflict(this_path, |
|
241 |
other_path) |
|
242 |
elif isinstance(contents.old_contents, changeset.FileCreate) and \ |
|
243 |
isinstance(contents.new_contents, changeset.FileCreate): |
|
974.1.3
by Aaron Bentley
Added merge_factory parameter to merge_flex |
244 |
return make_merge() |
850
by Martin Pool
- Merge merge updates from aaron |
245 |
else: |
246 |
raise Exception("Unhandled merge scenario") |
|
247 |
||
248 |
def make_merged_metadata(entry, base, other): |
|
249 |
if entry.metadata_change is not None: |
|
250 |
base_path = base.readonly_path(entry.id) |
|
251 |
other_path = other.readonly_path(entry.id) |
|
252 |
return PermissionsMerge(base_path, other_path) |
|
493
by Martin Pool
- Merge aaron's merge command |
253 |
|
254 |
||
558
by Martin Pool
- All top-level classes inherit from object |
255 |
class PermissionsMerge(object): |
493
by Martin Pool
- Merge aaron's merge command |
256 |
def __init__(self, base_path, other_path): |
257 |
self.base_path = base_path |
|
258 |
self.other_path = other_path |
|
259 |
||
260 |
def apply(self, filename, conflict_handler, reverse=False): |
|
261 |
if not reverse: |
|
262 |
base = self.base_path |
|
263 |
other = self.other_path |
|
264 |
else: |
|
265 |
base = self.other_path |
|
266 |
other = self.base_path |
|
267 |
base_stat = os.stat(base).st_mode |
|
268 |
other_stat = os.stat(other).st_mode |
|
269 |
this_stat = os.stat(filename).st_mode |
|
270 |
if base_stat &0777 == other_stat &0777: |
|
271 |
return
|
|
272 |
elif this_stat &0777 == other_stat &0777: |
|
273 |
return
|
|
274 |
elif this_stat &0777 == base_stat &0777: |
|
275 |
os.chmod(filename, other_stat) |
|
276 |
else: |
|
277 |
conflict_handler.permission_conflict(filename, base, other) |
|
278 |
||
279 |
||
280 |
import unittest |
|
281 |
import tempfile |
|
282 |
import shutil |
|
1069
by Martin Pool
- merge merge improvements from aaron |
283 |
from bzrlib.inventory import InventoryEntry, RootEntry |
284 |
from osutils import file_kind |
|
285 |
class FalseTree(object): |
|
286 |
def __init__(self, realtree): |
|
287 |
self._realtree = realtree |
|
288 |
self.inventory = self |
|
289 |
||
290 |
def __getitem__(self, file_id): |
|
291 |
entry = self.make_inventory_entry(file_id) |
|
292 |
if entry is None: |
|
293 |
raise KeyError(file_id) |
|
294 |
return entry |
|
295 |
||
296 |
def make_inventory_entry(self, file_id): |
|
297 |
path = self._realtree.inventory.get(file_id) |
|
298 |
if path is None: |
|
299 |
return None |
|
300 |
if path == "": |
|
301 |
return RootEntry(file_id) |
|
302 |
dir, name = os.path.split(path) |
|
303 |
kind = file_kind(self._realtree.abs_path(path)) |
|
304 |
for parent_id, path in self._realtree.inventory.iteritems(): |
|
305 |
if path == dir: |
|
306 |
break
|
|
307 |
if path != dir: |
|
308 |
raise Exception("Can't find parent for %s" % name) |
|
309 |
return InventoryEntry(file_id, name, kind, parent_id) |
|
310 |
||
311 |
||
558
by Martin Pool
- All top-level classes inherit from object |
312 |
class MergeTree(object): |
493
by Martin Pool
- Merge aaron's merge command |
313 |
def __init__(self, dir): |
314 |
self.dir = dir; |
|
315 |
os.mkdir(dir) |
|
316 |
self.inventory = {'0': ""} |
|
1069
by Martin Pool
- merge merge improvements from aaron |
317 |
self.tree = FalseTree(self) |
493
by Martin Pool
- Merge aaron's merge command |
318 |
|
319 |
def child_path(self, parent, name): |
|
320 |
return os.path.join(self.inventory[parent], name) |
|
321 |
||
322 |
def add_file(self, id, parent, name, contents, mode): |
|
323 |
path = self.child_path(parent, name) |
|
324 |
full_path = self.abs_path(path) |
|
325 |
assert not os.path.exists(full_path) |
|
326 |
file(full_path, "wb").write(contents) |
|
327 |
os.chmod(self.abs_path(path), mode) |
|
328 |
self.inventory[id] = path |
|
329 |
||
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
330 |
def remove_file(self, id): |
331 |
os.unlink(self.full_path(id)) |
|
332 |
del self.inventory[id] |
|
333 |
||
493
by Martin Pool
- Merge aaron's merge command |
334 |
def add_dir(self, id, parent, name, mode): |
335 |
path = self.child_path(parent, name) |
|
336 |
full_path = self.abs_path(path) |
|
337 |
assert not os.path.exists(full_path) |
|
338 |
os.mkdir(self.abs_path(path)) |
|
339 |
os.chmod(self.abs_path(path), mode) |
|
340 |
self.inventory[id] = path |
|
341 |
||
342 |
def abs_path(self, path): |
|
343 |
return os.path.join(self.dir, path) |
|
344 |
||
345 |
def full_path(self, id): |
|
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
346 |
try: |
347 |
tree_path = self.inventory[id] |
|
348 |
except KeyError: |
|
349 |
return None |
|
350 |
return self.abs_path(tree_path) |
|
493
by Martin Pool
- Merge aaron's merge command |
351 |
|
850
by Martin Pool
- Merge merge updates from aaron |
352 |
def readonly_path(self, id): |
353 |
return self.full_path(id) |
|
354 |
||
1069
by Martin Pool
- merge merge improvements from aaron |
355 |
def __contains__(self, file_id): |
356 |
return file_id in self.inventory |
|
357 |
||
1094
by Martin Pool
- merge aaron's merge improvements 999..1008 |
358 |
def has_or_had_id(self, file_id): |
359 |
return file_id in self |
|
360 |
||
1069
by Martin Pool
- merge merge improvements from aaron |
361 |
def get_file(self, file_id): |
362 |
path = self.readonly_path(file_id) |
|
363 |
return file(path, "rb") |
|
364 |
||
365 |
def id2path(self, file_id): |
|
366 |
return self.inventory[file_id] |
|
367 |
||
493
by Martin Pool
- Merge aaron's merge command |
368 |
def change_path(self, id, path): |
369 |
new = os.path.join(self.dir, self.inventory[id]) |
|
370 |
os.rename(self.abs_path(self.inventory[id]), self.abs_path(path)) |
|
371 |
self.inventory[id] = path |
|
372 |
||
558
by Martin Pool
- All top-level classes inherit from object |
373 |
class MergeBuilder(object): |
493
by Martin Pool
- Merge aaron's merge command |
374 |
def __init__(self): |
375 |
self.dir = tempfile.mkdtemp(prefix="BaZing") |
|
376 |
self.base = MergeTree(os.path.join(self.dir, "base")) |
|
377 |
self.this = MergeTree(os.path.join(self.dir, "this")) |
|
378 |
self.other = MergeTree(os.path.join(self.dir, "other")) |
|
379 |
||
380 |
self.cset = changeset.Changeset() |
|
381 |
self.cset.add_entry(changeset.ChangesetEntry("0", |
|
382 |
changeset.NULL_ID, "./.")) |
|
383 |
def get_cset_path(self, parent, name): |
|
384 |
if name is None: |
|
385 |
assert (parent is None) |
|
386 |
return None |
|
387 |
return os.path.join(self.cset.entries[parent].path, name) |
|
388 |
||
389 |
def add_file(self, id, parent, name, contents, mode): |
|
390 |
self.base.add_file(id, parent, name, contents, mode) |
|
391 |
self.this.add_file(id, parent, name, contents, mode) |
|
392 |
self.other.add_file(id, parent, name, contents, mode) |
|
393 |
path = self.get_cset_path(parent, name) |
|
394 |
self.cset.add_entry(changeset.ChangesetEntry(id, parent, path)) |
|
395 |
||
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
396 |
def remove_file(self, id, base=False, this=False, other=False): |
397 |
for option, tree in ((base, self.base), (this, self.this), |
|
398 |
(other, self.other)): |
|
399 |
if option: |
|
400 |
tree.remove_file(id) |
|
401 |
if other or base: |
|
402 |
change = self.cset.entries[id].contents_change |
|
1069
by Martin Pool
- merge merge improvements from aaron |
403 |
if change is None: |
404 |
change = changeset.ReplaceContents(None, None) |
|
405 |
self.cset.entries[id].contents_change = change |
|
406 |
def create_file(tree): |
|
407 |
return changeset.FileCreate(tree.get_file(id).read()) |
|
408 |
if not other: |
|
409 |
change.new_contents = create_file(self.other) |
|
410 |
if not base: |
|
411 |
change.old_contents = create_file(self.base) |
|
412 |
else: |
|
413 |
assert isinstance(change, changeset.ReplaceContents) |
|
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
414 |
if other: |
415 |
change.new_contents=None |
|
416 |
if base: |
|
417 |
change.old_contents=None |
|
418 |
if change.old_contents is None and change.new_contents is None: |
|
419 |
change = None |
|
420 |
||
421 |
||
493
by Martin Pool
- Merge aaron's merge command |
422 |
def add_dir(self, id, parent, name, mode): |
423 |
path = self.get_cset_path(parent, name) |
|
424 |
self.base.add_dir(id, parent, name, mode) |
|
425 |
self.cset.add_entry(changeset.ChangesetEntry(id, parent, path)) |
|
426 |
self.this.add_dir(id, parent, name, mode) |
|
427 |
self.other.add_dir(id, parent, name, mode) |
|
428 |
||
429 |
||
430 |
def change_name(self, id, base=None, this=None, other=None): |
|
431 |
if base is not None: |
|
432 |
self.change_name_tree(id, self.base, base) |
|
433 |
self.cset.entries[id].name = base |
|
434 |
||
435 |
if this is not None: |
|
436 |
self.change_name_tree(id, self.this, this) |
|
437 |
||
438 |
if other is not None: |
|
439 |
self.change_name_tree(id, self.other, other) |
|
440 |
self.cset.entries[id].new_name = other |
|
441 |
||
442 |
def change_parent(self, id, base=None, this=None, other=None): |
|
443 |
if base is not None: |
|
444 |
self.change_parent_tree(id, self.base, base) |
|
445 |
self.cset.entries[id].parent = base |
|
446 |
self.cset.entries[id].dir = self.cset.entries[base].path |
|
447 |
||
448 |
if this is not None: |
|
449 |
self.change_parent_tree(id, self.this, this) |
|
450 |
||
451 |
if other is not None: |
|
452 |
self.change_parent_tree(id, self.other, other) |
|
453 |
self.cset.entries[id].new_parent = other |
|
454 |
self.cset.entries[id].new_dir = \ |
|
455 |
self.cset.entries[other].new_path |
|
456 |
||
457 |
def change_contents(self, id, base=None, this=None, other=None): |
|
458 |
if base is not None: |
|
459 |
self.change_contents_tree(id, self.base, base) |
|
460 |
||
461 |
if this is not None: |
|
462 |
self.change_contents_tree(id, self.this, this) |
|
463 |
||
464 |
if other is not None: |
|
465 |
self.change_contents_tree(id, self.other, other) |
|
466 |
||
467 |
if base is not None or other is not None: |
|
468 |
old_contents = file(self.base.full_path(id)).read() |
|
469 |
new_contents = file(self.other.full_path(id)).read() |
|
470 |
contents = changeset.ReplaceFileContents(old_contents, |
|
471 |
new_contents) |
|
472 |
self.cset.entries[id].contents_change = contents |
|
473 |
||
474 |
def change_perms(self, id, base=None, this=None, other=None): |
|
475 |
if base is not None: |
|
476 |
self.change_perms_tree(id, self.base, base) |
|
477 |
||
478 |
if this is not None: |
|
479 |
self.change_perms_tree(id, self.this, this) |
|
480 |
||
481 |
if other is not None: |
|
482 |
self.change_perms_tree(id, self.other, other) |
|
483 |
||
484 |
if base is not None or other is not None: |
|
485 |
old_perms = os.stat(self.base.full_path(id)).st_mode &077 |
|
486 |
new_perms = os.stat(self.other.full_path(id)).st_mode &077 |
|
487 |
contents = changeset.ChangeUnixPermissions(old_perms, |
|
488 |
new_perms) |
|
489 |
self.cset.entries[id].metadata_change = contents |
|
490 |
||
491 |
def change_name_tree(self, id, tree, name): |
|
492 |
new_path = tree.child_path(self.cset.entries[id].parent, name) |
|
493 |
tree.change_path(id, new_path) |
|
494 |
||
495 |
def change_parent_tree(self, id, tree, parent): |
|
496 |
new_path = tree.child_path(parent, self.cset.entries[id].name) |
|
497 |
tree.change_path(id, new_path) |
|
498 |
||
499 |
def change_contents_tree(self, id, tree, contents): |
|
500 |
path = tree.full_path(id) |
|
501 |
mode = os.stat(path).st_mode |
|
502 |
file(path, "w").write(contents) |
|
503 |
os.chmod(path, mode) |
|
504 |
||
505 |
def change_perms_tree(self, id, tree, mode): |
|
506 |
os.chmod(tree.full_path(id), mode) |
|
507 |
||
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
508 |
def merge_changeset(self, merge_factory): |
493
by Martin Pool
- Merge aaron's merge command |
509 |
conflict_handler = changeset.ExceptionConflictHandler(self.this.dir) |
1069
by Martin Pool
- merge merge improvements from aaron |
510 |
return make_merge_changeset(self.cset, self.this, self.base, |
511 |
self.other, conflict_handler, |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
512 |
merge_factory) |
850
by Martin Pool
- Merge merge updates from aaron |
513 |
|
514 |
def apply_inv_change(self, inventory_change, orig_inventory): |
|
515 |
orig_inventory_by_path = {} |
|
516 |
for file_id, path in orig_inventory.iteritems(): |
|
517 |
orig_inventory_by_path[path] = file_id |
|
518 |
||
519 |
def parent_id(file_id): |
|
520 |
try: |
|
521 |
parent_dir = os.path.dirname(orig_inventory[file_id]) |
|
522 |
except: |
|
523 |
print file_id |
|
524 |
raise
|
|
525 |
if parent_dir == "": |
|
526 |
return None |
|
527 |
return orig_inventory_by_path[parent_dir] |
|
528 |
||
529 |
def new_path(file_id): |
|
530 |
if inventory_change.has_key(file_id): |
|
531 |
return inventory_change[file_id] |
|
532 |
else: |
|
533 |
parent = parent_id(file_id) |
|
534 |
if parent is None: |
|
535 |
return orig_inventory[file_id] |
|
536 |
dirname = new_path(parent) |
|
537 |
return os.path.join(dirname, orig_inventory[file_id]) |
|
538 |
||
539 |
new_inventory = {} |
|
540 |
for file_id in orig_inventory.iterkeys(): |
|
541 |
path = new_path(file_id) |
|
542 |
if path is None: |
|
543 |
continue
|
|
544 |
new_inventory[file_id] = path |
|
545 |
||
546 |
for file_id, path in inventory_change.iteritems(): |
|
547 |
if orig_inventory.has_key(file_id): |
|
548 |
continue
|
|
549 |
new_inventory[file_id] = path |
|
550 |
return new_inventory |
|
551 |
||
552 |
||
553 |
||
493
by Martin Pool
- Merge aaron's merge command |
554 |
def apply_changeset(self, cset, conflict_handler=None, reverse=False): |
850
by Martin Pool
- Merge merge updates from aaron |
555 |
inventory_change = changeset.apply_changeset(cset, |
556 |
self.this.inventory, |
|
557 |
self.this.dir, |
|
558 |
conflict_handler, reverse) |
|
559 |
self.this.inventory = self.apply_inv_change(inventory_change, |
|
560 |
self.this.inventory) |
|
561 |
||
562 |
||
563 |
||
564 |
||
493
by Martin Pool
- Merge aaron's merge command |
565 |
|
566 |
def cleanup(self): |
|
567 |
shutil.rmtree(self.dir) |
|
568 |
||
569 |
||
570 |
class MergeTest(unittest.TestCase): |
|
571 |
def test_change_name(self): |
|
572 |
"""Test renames"""
|
|
573 |
builder = MergeBuilder() |
|
574 |
builder.add_file("1", "0", "name1", "hello1", 0755) |
|
575 |
builder.change_name("1", other="name2") |
|
576 |
builder.add_file("2", "0", "name3", "hello2", 0755) |
|
577 |
builder.change_name("2", base="name4") |
|
578 |
builder.add_file("3", "0", "name5", "hello3", 0755) |
|
579 |
builder.change_name("3", this="name6") |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
580 |
cset = builder.merge_changeset(ApplyMerge3) |
493
by Martin Pool
- Merge aaron's merge command |
581 |
assert(cset.entries["2"].is_boring()) |
582 |
assert(cset.entries["1"].name == "name1") |
|
583 |
assert(cset.entries["1"].new_name == "name2") |
|
584 |
assert(cset.entries["3"].is_boring()) |
|
585 |
for tree in (builder.this, builder.other, builder.base): |
|
586 |
assert(tree.dir != builder.dir and |
|
587 |
tree.dir.startswith(builder.dir)) |
|
588 |
for path in tree.inventory.itervalues(): |
|
589 |
fullpath = tree.abs_path(path) |
|
590 |
assert(fullpath.startswith(tree.dir)) |
|
591 |
assert(not path.startswith(tree.dir)) |
|
592 |
assert os.path.exists(fullpath) |
|
593 |
builder.apply_changeset(cset) |
|
594 |
builder.cleanup() |
|
595 |
builder = MergeBuilder() |
|
596 |
builder.add_file("1", "0", "name1", "hello1", 0644) |
|
597 |
builder.change_name("1", other="name2", this="name3") |
|
598 |
self.assertRaises(changeset.RenameConflict, |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
599 |
builder.merge_changeset, ApplyMerge3) |
493
by Martin Pool
- Merge aaron's merge command |
600 |
builder.cleanup() |
601 |
||
602 |
def test_file_moves(self): |
|
603 |
"""Test moves"""
|
|
604 |
builder = MergeBuilder() |
|
605 |
builder.add_dir("1", "0", "dir1", 0755) |
|
606 |
builder.add_dir("2", "0", "dir2", 0755) |
|
607 |
builder.add_file("3", "1", "file1", "hello1", 0644) |
|
608 |
builder.add_file("4", "1", "file2", "hello2", 0644) |
|
609 |
builder.add_file("5", "1", "file3", "hello3", 0644) |
|
610 |
builder.change_parent("3", other="2") |
|
611 |
assert(Inventory(builder.other.inventory).get_parent("3") == "2") |
|
612 |
builder.change_parent("4", this="2") |
|
613 |
assert(Inventory(builder.this.inventory).get_parent("4") == "2") |
|
614 |
builder.change_parent("5", base="2") |
|
615 |
assert(Inventory(builder.base.inventory).get_parent("5") == "2") |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
616 |
cset = builder.merge_changeset(ApplyMerge3) |
493
by Martin Pool
- Merge aaron's merge command |
617 |
for id in ("1", "2", "4", "5"): |
618 |
assert(cset.entries[id].is_boring()) |
|
619 |
assert(cset.entries["3"].parent == "1") |
|
620 |
assert(cset.entries["3"].new_parent == "2") |
|
621 |
builder.apply_changeset(cset) |
|
622 |
builder.cleanup() |
|
623 |
||
624 |
builder = MergeBuilder() |
|
625 |
builder.add_dir("1", "0", "dir1", 0755) |
|
626 |
builder.add_dir("2", "0", "dir2", 0755) |
|
627 |
builder.add_dir("3", "0", "dir3", 0755) |
|
628 |
builder.add_file("4", "1", "file1", "hello1", 0644) |
|
629 |
builder.change_parent("4", other="2", this="3") |
|
630 |
self.assertRaises(changeset.MoveConflict, |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
631 |
builder.merge_changeset, ApplyMerge3) |
493
by Martin Pool
- Merge aaron's merge command |
632 |
builder.cleanup() |
633 |
||
634 |
def test_contents_merge(self): |
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
635 |
"""Test merge3 merging"""
|
636 |
self.do_contents_test(ApplyMerge3) |
|
637 |
||
638 |
def test_contents_merge2(self): |
|
493
by Martin Pool
- Merge aaron's merge command |
639 |
"""Test diff3 merging"""
|
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
640 |
self.do_contents_test(changeset.Diff3Merge) |
641 |
||
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
642 |
def test_contents_merge3(self): |
643 |
"""Test diff3 merging"""
|
|
1069
by Martin Pool
- merge merge improvements from aaron |
644 |
def backup_merge(file_id, base, other): |
645 |
return BackupBeforeChange(ApplyMerge3(file_id, base, other)) |
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
646 |
builder = self.contents_test_success(backup_merge) |
647 |
def backup_exists(file_id): |
|
648 |
return os.path.exists(builder.this.full_path(file_id)+"~") |
|
649 |
assert backup_exists("1") |
|
650 |
assert backup_exists("2") |
|
651 |
assert not backup_exists("3") |
|
652 |
builder.cleanup() |
|
653 |
||
974.1.4
by Aaron Bentley
Implemented merge3 as the default text merge |
654 |
def do_contents_test(self, merge_factory): |
655 |
"""Test merging with specified ContentsChange factory"""
|
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
656 |
builder = self.contents_test_success(merge_factory) |
657 |
builder.cleanup() |
|
658 |
self.contents_test_conflicts(merge_factory) |
|
659 |
||
660 |
def contents_test_success(self, merge_factory): |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
661 |
from inspect import isclass |
493
by Martin Pool
- Merge aaron's merge command |
662 |
builder = MergeBuilder() |
663 |
builder.add_file("1", "0", "name1", "text1", 0755) |
|
664 |
builder.change_contents("1", other="text4") |
|
665 |
builder.add_file("2", "0", "name3", "text2", 0655) |
|
666 |
builder.change_contents("2", base="text5") |
|
667 |
builder.add_file("3", "0", "name5", "text3", 0744) |
|
1069
by Martin Pool
- merge merge improvements from aaron |
668 |
builder.add_file("4", "0", "name6", "text4", 0744) |
669 |
builder.remove_file("4", base=True) |
|
670 |
assert not builder.cset.entries["4"].is_boring() |
|
493
by Martin Pool
- Merge aaron's merge command |
671 |
builder.change_contents("3", this="text6") |
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
672 |
cset = builder.merge_changeset(merge_factory) |
493
by Martin Pool
- Merge aaron's merge command |
673 |
assert(cset.entries["1"].contents_change is not None) |
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
674 |
if isclass(merge_factory): |
675 |
assert(isinstance(cset.entries["1"].contents_change, |
|
676 |
merge_factory)) |
|
677 |
assert(isinstance(cset.entries["2"].contents_change, |
|
678 |
merge_factory)) |
|
493
by Martin Pool
- Merge aaron's merge command |
679 |
assert(cset.entries["3"].is_boring()) |
1069
by Martin Pool
- merge merge improvements from aaron |
680 |
assert(cset.entries["4"].is_boring()) |
493
by Martin Pool
- Merge aaron's merge command |
681 |
builder.apply_changeset(cset) |
682 |
assert(file(builder.this.full_path("1"), "rb").read() == "text4" ) |
|
683 |
assert(file(builder.this.full_path("2"), "rb").read() == "text2" ) |
|
684 |
assert(os.stat(builder.this.full_path("1")).st_mode &0777 == 0755) |
|
685 |
assert(os.stat(builder.this.full_path("2")).st_mode &0777 == 0655) |
|
686 |
assert(os.stat(builder.this.full_path("3")).st_mode &0777 == 0744) |
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
687 |
return builder |
493
by Martin Pool
- Merge aaron's merge command |
688 |
|
974.1.8
by Aaron Bentley
Added default backups for merge-revert |
689 |
def contents_test_conflicts(self, merge_factory): |
493
by Martin Pool
- Merge aaron's merge command |
690 |
builder = MergeBuilder() |
691 |
builder.add_file("1", "0", "name1", "text1", 0755) |
|
692 |
builder.change_contents("1", other="text4", this="text3") |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
693 |
cset = builder.merge_changeset(merge_factory) |
493
by Martin Pool
- Merge aaron's merge command |
694 |
self.assertRaises(changeset.MergeConflict, builder.apply_changeset, |
695 |
cset) |
|
696 |
builder.cleanup() |
|
697 |
||
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
698 |
builder = MergeBuilder() |
699 |
builder.add_file("1", "0", "name1", "text1", 0755) |
|
700 |
builder.change_contents("1", other="text4", base="text3") |
|
701 |
builder.remove_file("1", base=True) |
|
702 |
self.assertRaises(changeset.NewContentsConflict, |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
703 |
builder.merge_changeset, merge_factory) |
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
704 |
builder.cleanup() |
705 |
||
706 |
builder = MergeBuilder() |
|
707 |
builder.add_file("1", "0", "name1", "text1", 0755) |
|
708 |
builder.change_contents("1", other="text4", base="text3") |
|
709 |
builder.remove_file("1", this=True) |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
710 |
self.assertRaises(changeset.MissingForMerge, builder.merge_changeset, |
711 |
merge_factory) |
|
909.1.4
by Aaron Bentley
Fixed conflict handling for missing merge targets |
712 |
builder.cleanup() |
713 |
||
493
by Martin Pool
- Merge aaron's merge command |
714 |
def test_perms_merge(self): |
715 |
builder = MergeBuilder() |
|
716 |
builder.add_file("1", "0", "name1", "text1", 0755) |
|
717 |
builder.change_perms("1", other=0655) |
|
718 |
builder.add_file("2", "0", "name2", "text2", 0755) |
|
719 |
builder.change_perms("2", base=0655) |
|
720 |
builder.add_file("3", "0", "name3", "text3", 0755) |
|
721 |
builder.change_perms("3", this=0655) |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
722 |
cset = builder.merge_changeset(ApplyMerge3) |
493
by Martin Pool
- Merge aaron's merge command |
723 |
assert(cset.entries["1"].metadata_change is not None) |
724 |
assert(isinstance(cset.entries["1"].metadata_change, |
|
725 |
PermissionsMerge)) |
|
726 |
assert(isinstance(cset.entries["2"].metadata_change, |
|
727 |
PermissionsMerge)) |
|
728 |
assert(cset.entries["3"].is_boring()) |
|
729 |
builder.apply_changeset(cset) |
|
730 |
assert(os.stat(builder.this.full_path("1")).st_mode &0777 == 0655) |
|
731 |
assert(os.stat(builder.this.full_path("2")).st_mode &0777 == 0755) |
|
732 |
assert(os.stat(builder.this.full_path("3")).st_mode &0777 == 0655) |
|
733 |
builder.cleanup(); |
|
734 |
builder = MergeBuilder() |
|
735 |
builder.add_file("1", "0", "name1", "text1", 0755) |
|
736 |
builder.change_perms("1", other=0655, base=0555) |
|
974.1.7
by Aaron Bentley
Fixed handling of merge factory in test suite |
737 |
cset = builder.merge_changeset(ApplyMerge3) |
493
by Martin Pool
- Merge aaron's merge command |
738 |
self.assertRaises(changeset.MergePermissionConflict, |
739 |
builder.apply_changeset, cset) |
|
740 |
builder.cleanup() |
|
741 |
||
742 |
def test(): |
|
743 |
changeset_suite = unittest.makeSuite(MergeTest, 'test_') |
|
744 |
runner = unittest.TextTestRunner() |
|
745 |
runner.run(changeset_suite) |
|
746 |
||
747 |
if __name__ == "__main__": |
|
748 |
test() |