Merge lp:~abentley/bzr/disk-transform into lp:~bzr/bzr/trunk-old
- disk-transform
- Merge into trunk-old
Proposed by
Aaron Bentley
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~abentley/bzr/disk-transform |
Merge into: | lp:~bzr/bzr/trunk-old |
Diff against target: | 776 lines |
To merge this branch: | bzr merge lp:~abentley/bzr/disk-transform |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ian Clatworthy | Approve | ||
Review via email: mp+6635@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote : | # |
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote : | # |
All looks good to me. Two minor things:
1. Please tweak the docstring for TreeTransformBase so it doesn't claim to be a base class for itself. :-)
2. You're changing the constructor interface for TreeTransformBase which is technically public (but practically private). I think we should therefore mention this refactoring in the Internals sections of NEWS.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-05-23 04:55:52 +0000 |
3 | +++ NEWS 2009-05-28 08:35:37 +0000 |
4 | @@ -34,6 +34,10 @@ |
5 | |
6 | * Added osutils.parent_directories(). (Ian Clatworthy) |
7 | |
8 | +* TreeTransformBase no longer assumes that limbo is provided via disk. |
9 | + DiskTreeTransform now provides disk functionality. (Aaron Bentley) |
10 | + |
11 | + |
12 | Internals |
13 | ********* |
14 | |
15 | |
16 | === modified file 'bzrlib/transform.py' |
17 | --- bzrlib/transform.py 2009-05-23 04:55:52 +0000 |
18 | +++ bzrlib/transform.py 2009-05-28 08:35:37 +0000 |
19 | @@ -76,24 +76,20 @@ |
20 | |
21 | |
22 | class TreeTransformBase(object): |
23 | - """The base class for TreeTransform and TreeTransformBase""" |
24 | + """The base class for TreeTransform and its kin.""" |
25 | |
26 | - def __init__(self, tree, limbodir, pb=DummyProgress(), |
27 | + def __init__(self, tree, pb=DummyProgress(), |
28 | case_sensitive=True): |
29 | """Constructor. |
30 | |
31 | :param tree: The tree that will be transformed, but not necessarily |
32 | the output tree. |
33 | - :param limbodir: A directory where new files can be stored until |
34 | - they are installed in their proper places |
35 | :param pb: A ProgressBar indicating how much progress is being made |
36 | :param case_sensitive: If True, the target of the transform is |
37 | case sensitive, not just case preserving. |
38 | """ |
39 | object.__init__(self) |
40 | self._tree = tree |
41 | - self._limbodir = limbodir |
42 | - self._deletiondir = None |
43 | self._id_number = 0 |
44 | # mapping of trans_id -> new basename |
45 | self._new_name = {} |
46 | @@ -101,15 +97,6 @@ |
47 | self._new_parent = {} |
48 | # mapping of trans_id with new contents -> new file_kind |
49 | self._new_contents = {} |
50 | - # A mapping of transform ids to their limbo filename |
51 | - self._limbo_files = {} |
52 | - # A mapping of transform ids to a set of the transform ids of children |
53 | - # that their limbo directory has |
54 | - self._limbo_children = {} |
55 | - # Map transform ids to maps of child filename to child transform id |
56 | - self._limbo_children_names = {} |
57 | - # List of transform ids that need to be renamed from limbo into place |
58 | - self._needs_rename = set() |
59 | # Set of trans_ids whose contents will be removed |
60 | self._removed_contents = set() |
61 | # Mapping of trans_id -> new execute-bit value |
62 | @@ -128,10 +115,6 @@ |
63 | self._tree_path_ids = {} |
64 | # Mapping trans_id -> path in old tree |
65 | self._tree_id_paths = {} |
66 | - # Cache of realpath results, to speed up canonical_path |
67 | - self._realpaths = {} |
68 | - # Cache of relpath results, to speed up canonical_path |
69 | - self._relpaths = {} |
70 | # The trans_id that will be used as the tree root |
71 | root_id = tree.get_root_id() |
72 | if root_id is not None: |
73 | @@ -147,42 +130,22 @@ |
74 | # A counter of how many files have been renamed |
75 | self.rename_count = 0 |
76 | |
77 | + def finalize(self): |
78 | + """Release the working tree lock, if held. |
79 | + |
80 | + This is required if apply has not been invoked, but can be invoked |
81 | + even after apply. |
82 | + """ |
83 | + if self._tree is None: |
84 | + return |
85 | + self._tree.unlock() |
86 | + self._tree = None |
87 | + |
88 | def __get_root(self): |
89 | return self._new_root |
90 | |
91 | root = property(__get_root) |
92 | |
93 | - def finalize(self): |
94 | - """Release the working tree lock, if held, clean up limbo dir. |
95 | - |
96 | - This is required if apply has not been invoked, but can be invoked |
97 | - even after apply. |
98 | - """ |
99 | - if self._tree is None: |
100 | - return |
101 | - try: |
102 | - entries = [(self._limbo_name(t), t, k) for t, k in |
103 | - self._new_contents.iteritems()] |
104 | - entries.sort(reverse=True) |
105 | - for path, trans_id, kind in entries: |
106 | - if kind == "directory": |
107 | - os.rmdir(path) |
108 | - else: |
109 | - os.unlink(path) |
110 | - try: |
111 | - os.rmdir(self._limbodir) |
112 | - except OSError: |
113 | - # We don't especially care *why* the dir is immortal. |
114 | - raise ImmortalLimbo(self._limbodir) |
115 | - try: |
116 | - if self._deletiondir is not None: |
117 | - os.rmdir(self._deletiondir) |
118 | - except OSError: |
119 | - raise errors.ImmortalPendingDeletion(self._deletiondir) |
120 | - finally: |
121 | - self._tree.unlock() |
122 | - self._tree = None |
123 | - |
124 | def _assign_id(self): |
125 | """Produce a new tranform id""" |
126 | new_id = "new-%s" % self._id_number |
127 | @@ -200,37 +163,12 @@ |
128 | """Change the path that is assigned to a transaction id.""" |
129 | if trans_id == self._new_root: |
130 | raise CantMoveRoot |
131 | - previous_parent = self._new_parent.get(trans_id) |
132 | - previous_name = self._new_name.get(trans_id) |
133 | self._new_name[trans_id] = name |
134 | self._new_parent[trans_id] = parent |
135 | if parent == ROOT_PARENT: |
136 | if self._new_root is not None: |
137 | raise ValueError("Cannot have multiple roots.") |
138 | self._new_root = trans_id |
139 | - if (trans_id in self._limbo_files and |
140 | - trans_id not in self._needs_rename): |
141 | - self._rename_in_limbo([trans_id]) |
142 | - self._limbo_children[previous_parent].remove(trans_id) |
143 | - del self._limbo_children_names[previous_parent][previous_name] |
144 | - |
145 | - def _rename_in_limbo(self, trans_ids): |
146 | - """Fix limbo names so that the right final path is produced. |
147 | - |
148 | - This means we outsmarted ourselves-- we tried to avoid renaming |
149 | - these files later by creating them with their final names in their |
150 | - final parents. But now the previous name or parent is no longer |
151 | - suitable, so we have to rename them. |
152 | - |
153 | - Even for trans_ids that have no new contents, we must remove their |
154 | - entries from _limbo_files, because they are now stale. |
155 | - """ |
156 | - for trans_id in trans_ids: |
157 | - old_path = self._limbo_files.pop(trans_id) |
158 | - if trans_id not in self._new_contents: |
159 | - continue |
160 | - new_path = self._limbo_name(trans_id) |
161 | - os.rename(old_path, new_path) |
162 | |
163 | def adjust_root_path(self, name, parent): |
164 | """Emulate moving the root by moving all children, instead. |
165 | @@ -298,25 +236,6 @@ |
166 | else: |
167 | return self.trans_id_tree_file_id(file_id) |
168 | |
169 | - def canonical_path(self, path): |
170 | - """Get the canonical tree-relative path""" |
171 | - # don't follow final symlinks |
172 | - abs = self._tree.abspath(path) |
173 | - if abs in self._relpaths: |
174 | - return self._relpaths[abs] |
175 | - dirname, basename = os.path.split(abs) |
176 | - if dirname not in self._realpaths: |
177 | - self._realpaths[dirname] = os.path.realpath(dirname) |
178 | - dirname = self._realpaths[dirname] |
179 | - abs = pathjoin(dirname, basename) |
180 | - if dirname in self._relpaths: |
181 | - relpath = pathjoin(self._relpaths[dirname], basename) |
182 | - relpath = relpath.rstrip('/\\') |
183 | - else: |
184 | - relpath = self._tree.relpath(abs) |
185 | - self._relpaths[abs] = relpath |
186 | - return relpath |
187 | - |
188 | def trans_id_tree_path(self, path): |
189 | """Determine (and maybe set) the transaction ID for a tree path.""" |
190 | path = self.canonical_path(path) |
191 | @@ -332,113 +251,6 @@ |
192 | return ROOT_PARENT |
193 | return self.trans_id_tree_path(os.path.dirname(path)) |
194 | |
195 | - def create_file(self, contents, trans_id, mode_id=None): |
196 | - """Schedule creation of a new file. |
197 | - |
198 | - See also new_file. |
199 | - |
200 | - Contents is an iterator of strings, all of which will be written |
201 | - to the target destination. |
202 | - |
203 | - New file takes the permissions of any existing file with that id, |
204 | - unless mode_id is specified. |
205 | - """ |
206 | - name = self._limbo_name(trans_id) |
207 | - f = open(name, 'wb') |
208 | - try: |
209 | - try: |
210 | - unique_add(self._new_contents, trans_id, 'file') |
211 | - except: |
212 | - # Clean up the file, it never got registered so |
213 | - # TreeTransform.finalize() won't clean it up. |
214 | - f.close() |
215 | - os.unlink(name) |
216 | - raise |
217 | - |
218 | - f.writelines(contents) |
219 | - finally: |
220 | - f.close() |
221 | - self._set_mode(trans_id, mode_id, S_ISREG) |
222 | - |
223 | - def _set_mode(self, trans_id, mode_id, typefunc): |
224 | - """Set the mode of new file contents. |
225 | - The mode_id is the existing file to get the mode from (often the same |
226 | - as trans_id). The operation is only performed if there's a mode match |
227 | - according to typefunc. |
228 | - """ |
229 | - if mode_id is None: |
230 | - mode_id = trans_id |
231 | - try: |
232 | - old_path = self._tree_id_paths[mode_id] |
233 | - except KeyError: |
234 | - return |
235 | - try: |
236 | - mode = os.stat(self._tree.abspath(old_path)).st_mode |
237 | - except OSError, e: |
238 | - if e.errno in (errno.ENOENT, errno.ENOTDIR): |
239 | - # Either old_path doesn't exist, or the parent of the |
240 | - # target is not a directory (but will be one eventually) |
241 | - # Either way, we know it doesn't exist *right now* |
242 | - # See also bug #248448 |
243 | - return |
244 | - else: |
245 | - raise |
246 | - if typefunc(mode): |
247 | - os.chmod(self._limbo_name(trans_id), mode) |
248 | - |
249 | - def create_hardlink(self, path, trans_id): |
250 | - """Schedule creation of a hard link""" |
251 | - name = self._limbo_name(trans_id) |
252 | - try: |
253 | - os.link(path, name) |
254 | - except OSError, e: |
255 | - if e.errno != errno.EPERM: |
256 | - raise |
257 | - raise errors.HardLinkNotSupported(path) |
258 | - try: |
259 | - unique_add(self._new_contents, trans_id, 'file') |
260 | - except: |
261 | - # Clean up the file, it never got registered so |
262 | - # TreeTransform.finalize() won't clean it up. |
263 | - os.unlink(name) |
264 | - raise |
265 | - |
266 | - def create_directory(self, trans_id): |
267 | - """Schedule creation of a new directory. |
268 | - |
269 | - See also new_directory. |
270 | - """ |
271 | - os.mkdir(self._limbo_name(trans_id)) |
272 | - unique_add(self._new_contents, trans_id, 'directory') |
273 | - |
274 | - def create_symlink(self, target, trans_id): |
275 | - """Schedule creation of a new symbolic link. |
276 | - |
277 | - target is a bytestring. |
278 | - See also new_symlink. |
279 | - """ |
280 | - if has_symlinks(): |
281 | - os.symlink(target, self._limbo_name(trans_id)) |
282 | - unique_add(self._new_contents, trans_id, 'symlink') |
283 | - else: |
284 | - try: |
285 | - path = FinalPaths(self).get_path(trans_id) |
286 | - except KeyError: |
287 | - path = None |
288 | - raise UnableCreateSymlink(path=path) |
289 | - |
290 | - def cancel_creation(self, trans_id): |
291 | - """Cancel the creation of new file contents.""" |
292 | - del self._new_contents[trans_id] |
293 | - children = self._limbo_children.get(trans_id) |
294 | - # if this is a limbo directory with children, move them before removing |
295 | - # the directory |
296 | - if children is not None: |
297 | - self._rename_in_limbo(children) |
298 | - del self._limbo_children[trans_id] |
299 | - del self._limbo_children_names[trans_id] |
300 | - delete_any(self._limbo_name(trans_id)) |
301 | - |
302 | def delete_contents(self, trans_id): |
303 | """Schedule the contents of a path entry for deletion""" |
304 | self.tree_kind(trans_id) |
305 | @@ -518,22 +330,6 @@ |
306 | new_ids.update(changed_kind) |
307 | return sorted(FinalPaths(self).get_paths(new_ids)) |
308 | |
309 | - def tree_kind(self, trans_id): |
310 | - """Determine the file kind in the working tree. |
311 | - |
312 | - Raises NoSuchFile if the file does not exist |
313 | - """ |
314 | - path = self._tree_id_paths.get(trans_id) |
315 | - if path is None: |
316 | - raise NoSuchFile(None) |
317 | - try: |
318 | - return file_kind(self._tree.abspath(path)) |
319 | - except OSError, e: |
320 | - if e.errno != errno.ENOENT: |
321 | - raise |
322 | - else: |
323 | - raise NoSuchFile(path) |
324 | - |
325 | def final_kind(self, trans_id): |
326 | """Determine the final file kind, after any changes applied. |
327 | |
328 | @@ -667,26 +463,6 @@ |
329 | # ensure that all children are registered with the transaction |
330 | list(self.iter_tree_children(parent_id)) |
331 | |
332 | - def iter_tree_children(self, parent_id): |
333 | - """Iterate through the entry's tree children, if any""" |
334 | - try: |
335 | - path = self._tree_id_paths[parent_id] |
336 | - except KeyError: |
337 | - return |
338 | - try: |
339 | - children = os.listdir(self._tree.abspath(path)) |
340 | - except OSError, e: |
341 | - if not (osutils._is_error_enotdir(e) |
342 | - or e.errno in (errno.ENOENT, errno.ESRCH)): |
343 | - raise |
344 | - return |
345 | - |
346 | - for child in children: |
347 | - childpath = joinpath(path, child) |
348 | - if self._tree.is_control_filename(childpath): |
349 | - continue |
350 | - yield self.trans_id_tree_path(childpath) |
351 | - |
352 | def has_named_child(self, by_parent, parent_id, name): |
353 | try: |
354 | children = by_parent[parent_id] |
355 | @@ -867,50 +643,6 @@ |
356 | return True |
357 | return False |
358 | |
359 | - def _limbo_name(self, trans_id): |
360 | - """Generate the limbo name of a file""" |
361 | - limbo_name = self._limbo_files.get(trans_id) |
362 | - if limbo_name is not None: |
363 | - return limbo_name |
364 | - parent = self._new_parent.get(trans_id) |
365 | - # if the parent directory is already in limbo (e.g. when building a |
366 | - # tree), choose a limbo name inside the parent, to reduce further |
367 | - # renames. |
368 | - use_direct_path = False |
369 | - if self._new_contents.get(parent) == 'directory': |
370 | - filename = self._new_name.get(trans_id) |
371 | - if filename is not None: |
372 | - if parent not in self._limbo_children: |
373 | - self._limbo_children[parent] = set() |
374 | - self._limbo_children_names[parent] = {} |
375 | - use_direct_path = True |
376 | - # the direct path can only be used if no other file has |
377 | - # already taken this pathname, i.e. if the name is unused, or |
378 | - # if it is already associated with this trans_id. |
379 | - elif self._case_sensitive_target: |
380 | - if (self._limbo_children_names[parent].get(filename) |
381 | - in (trans_id, None)): |
382 | - use_direct_path = True |
383 | - else: |
384 | - for l_filename, l_trans_id in\ |
385 | - self._limbo_children_names[parent].iteritems(): |
386 | - if l_trans_id == trans_id: |
387 | - continue |
388 | - if l_filename.lower() == filename.lower(): |
389 | - break |
390 | - else: |
391 | - use_direct_path = True |
392 | - |
393 | - if use_direct_path: |
394 | - limbo_name = pathjoin(self._limbo_files[parent], filename) |
395 | - self._limbo_children[parent].add(trans_id) |
396 | - self._limbo_children_names[parent][filename] = trans_id |
397 | - else: |
398 | - limbo_name = pathjoin(self._limbodir, trans_id) |
399 | - self._needs_rename.add(trans_id) |
400 | - self._limbo_files[trans_id] = limbo_name |
401 | - return limbo_name |
402 | - |
403 | def _set_executability(self, path, trans_id): |
404 | """Set the executability of versioned files """ |
405 | if supports_executable(): |
406 | @@ -1176,21 +908,17 @@ |
407 | (('attribs',),)) |
408 | for trans_id, kind in self._new_contents.items(): |
409 | if kind == 'file': |
410 | - cur_file = open(self._limbo_name(trans_id), 'rb') |
411 | - try: |
412 | - lines = osutils.chunks_to_lines(cur_file.readlines()) |
413 | - finally: |
414 | - cur_file.close() |
415 | + lines = osutils.chunks_to_lines( |
416 | + self._read_file_chunks(trans_id)) |
417 | parents = self._get_parents_lines(trans_id) |
418 | mpdiff = multiparent.MultiParent.from_lines(lines, parents) |
419 | content = ''.join(mpdiff.to_patch()) |
420 | if kind == 'directory': |
421 | content = '' |
422 | if kind == 'symlink': |
423 | - content = os.readlink(self._limbo_name(trans_id)) |
424 | + content = self._read_symlink_target(trans_id) |
425 | yield serializer.bytes_record(content, ((trans_id, kind),)) |
426 | |
427 | - |
428 | def deserialize(self, records): |
429 | """Deserialize a stored TreeTransform. |
430 | |
431 | @@ -1227,7 +955,228 @@ |
432 | self.create_symlink(content.decode('utf-8'), trans_id) |
433 | |
434 | |
435 | -class TreeTransform(TreeTransformBase): |
436 | +class DiskTreeTransform(TreeTransformBase): |
437 | + """Tree transform storing its contents on disk.""" |
438 | + |
439 | + def __init__(self, tree, limbodir, pb=DummyProgress(), |
440 | + case_sensitive=True): |
441 | + """Constructor. |
442 | + :param tree: The tree that will be transformed, but not necessarily |
443 | + the output tree. |
444 | + :param limbodir: A directory where new files can be stored until |
445 | + they are installed in their proper places |
446 | + :param pb: A ProgressBar indicating how much progress is being made |
447 | + :param case_sensitive: If True, the target of the transform is |
448 | + case sensitive, not just case preserving. |
449 | + """ |
450 | + TreeTransformBase.__init__(self, tree, pb, case_sensitive) |
451 | + self._limbodir = limbodir |
452 | + self._deletiondir = None |
453 | + # A mapping of transform ids to their limbo filename |
454 | + self._limbo_files = {} |
455 | + # A mapping of transform ids to a set of the transform ids of children |
456 | + # that their limbo directory has |
457 | + self._limbo_children = {} |
458 | + # Map transform ids to maps of child filename to child transform id |
459 | + self._limbo_children_names = {} |
460 | + # List of transform ids that need to be renamed from limbo into place |
461 | + self._needs_rename = set() |
462 | + |
463 | + def finalize(self): |
464 | + """Release the working tree lock, if held, clean up limbo dir. |
465 | + |
466 | + This is required if apply has not been invoked, but can be invoked |
467 | + even after apply. |
468 | + """ |
469 | + if self._tree is None: |
470 | + return |
471 | + try: |
472 | + entries = [(self._limbo_name(t), t, k) for t, k in |
473 | + self._new_contents.iteritems()] |
474 | + entries.sort(reverse=True) |
475 | + for path, trans_id, kind in entries: |
476 | + if kind == "directory": |
477 | + os.rmdir(path) |
478 | + else: |
479 | + os.unlink(path) |
480 | + try: |
481 | + os.rmdir(self._limbodir) |
482 | + except OSError: |
483 | + # We don't especially care *why* the dir is immortal. |
484 | + raise ImmortalLimbo(self._limbodir) |
485 | + try: |
486 | + if self._deletiondir is not None: |
487 | + os.rmdir(self._deletiondir) |
488 | + except OSError: |
489 | + raise errors.ImmortalPendingDeletion(self._deletiondir) |
490 | + finally: |
491 | + TreeTransformBase.finalize(self) |
492 | + |
493 | + def _limbo_name(self, trans_id): |
494 | + """Generate the limbo name of a file""" |
495 | + limbo_name = self._limbo_files.get(trans_id) |
496 | + if limbo_name is not None: |
497 | + return limbo_name |
498 | + parent = self._new_parent.get(trans_id) |
499 | + # if the parent directory is already in limbo (e.g. when building a |
500 | + # tree), choose a limbo name inside the parent, to reduce further |
501 | + # renames. |
502 | + use_direct_path = False |
503 | + if self._new_contents.get(parent) == 'directory': |
504 | + filename = self._new_name.get(trans_id) |
505 | + if filename is not None: |
506 | + if parent not in self._limbo_children: |
507 | + self._limbo_children[parent] = set() |
508 | + self._limbo_children_names[parent] = {} |
509 | + use_direct_path = True |
510 | + # the direct path can only be used if no other file has |
511 | + # already taken this pathname, i.e. if the name is unused, or |
512 | + # if it is already associated with this trans_id. |
513 | + elif self._case_sensitive_target: |
514 | + if (self._limbo_children_names[parent].get(filename) |
515 | + in (trans_id, None)): |
516 | + use_direct_path = True |
517 | + else: |
518 | + for l_filename, l_trans_id in\ |
519 | + self._limbo_children_names[parent].iteritems(): |
520 | + if l_trans_id == trans_id: |
521 | + continue |
522 | + if l_filename.lower() == filename.lower(): |
523 | + break |
524 | + else: |
525 | + use_direct_path = True |
526 | + |
527 | + if use_direct_path: |
528 | + limbo_name = pathjoin(self._limbo_files[parent], filename) |
529 | + self._limbo_children[parent].add(trans_id) |
530 | + self._limbo_children_names[parent][filename] = trans_id |
531 | + else: |
532 | + limbo_name = pathjoin(self._limbodir, trans_id) |
533 | + self._needs_rename.add(trans_id) |
534 | + self._limbo_files[trans_id] = limbo_name |
535 | + return limbo_name |
536 | + |
537 | + def adjust_path(self, name, parent, trans_id): |
538 | + previous_parent = self._new_parent.get(trans_id) |
539 | + previous_name = self._new_name.get(trans_id) |
540 | + TreeTransformBase.adjust_path(self, name, parent, trans_id) |
541 | + if (trans_id in self._limbo_files and |
542 | + trans_id not in self._needs_rename): |
543 | + self._rename_in_limbo([trans_id]) |
544 | + self._limbo_children[previous_parent].remove(trans_id) |
545 | + del self._limbo_children_names[previous_parent][previous_name] |
546 | + |
547 | + def _rename_in_limbo(self, trans_ids): |
548 | + """Fix limbo names so that the right final path is produced. |
549 | + |
550 | + This means we outsmarted ourselves-- we tried to avoid renaming |
551 | + these files later by creating them with their final names in their |
552 | + final parents. But now the previous name or parent is no longer |
553 | + suitable, so we have to rename them. |
554 | + |
555 | + Even for trans_ids that have no new contents, we must remove their |
556 | + entries from _limbo_files, because they are now stale. |
557 | + """ |
558 | + for trans_id in trans_ids: |
559 | + old_path = self._limbo_files.pop(trans_id) |
560 | + if trans_id not in self._new_contents: |
561 | + continue |
562 | + new_path = self._limbo_name(trans_id) |
563 | + os.rename(old_path, new_path) |
564 | + |
565 | + def create_file(self, contents, trans_id, mode_id=None): |
566 | + """Schedule creation of a new file. |
567 | + |
568 | + See also new_file. |
569 | + |
570 | + Contents is an iterator of strings, all of which will be written |
571 | + to the target destination. |
572 | + |
573 | + New file takes the permissions of any existing file with that id, |
574 | + unless mode_id is specified. |
575 | + """ |
576 | + name = self._limbo_name(trans_id) |
577 | + f = open(name, 'wb') |
578 | + try: |
579 | + try: |
580 | + unique_add(self._new_contents, trans_id, 'file') |
581 | + except: |
582 | + # Clean up the file, it never got registered so |
583 | + # TreeTransform.finalize() won't clean it up. |
584 | + f.close() |
585 | + os.unlink(name) |
586 | + raise |
587 | + |
588 | + f.writelines(contents) |
589 | + finally: |
590 | + f.close() |
591 | + self._set_mode(trans_id, mode_id, S_ISREG) |
592 | + |
593 | + def _read_file_chunks(self, trans_id): |
594 | + cur_file = open(self._limbo_name(trans_id), 'rb') |
595 | + try: |
596 | + return cur_file.readlines() |
597 | + finally: |
598 | + cur_file.close() |
599 | + |
600 | + def _read_symlink_target(self, trans_id): |
601 | + return os.readlink(self._limbo_name(trans_id)) |
602 | + |
603 | + def create_hardlink(self, path, trans_id): |
604 | + """Schedule creation of a hard link""" |
605 | + name = self._limbo_name(trans_id) |
606 | + try: |
607 | + os.link(path, name) |
608 | + except OSError, e: |
609 | + if e.errno != errno.EPERM: |
610 | + raise |
611 | + raise errors.HardLinkNotSupported(path) |
612 | + try: |
613 | + unique_add(self._new_contents, trans_id, 'file') |
614 | + except: |
615 | + # Clean up the file, it never got registered so |
616 | + # TreeTransform.finalize() won't clean it up. |
617 | + os.unlink(name) |
618 | + raise |
619 | + |
620 | + def create_directory(self, trans_id): |
621 | + """Schedule creation of a new directory. |
622 | + |
623 | + See also new_directory. |
624 | + """ |
625 | + os.mkdir(self._limbo_name(trans_id)) |
626 | + unique_add(self._new_contents, trans_id, 'directory') |
627 | + |
628 | + def create_symlink(self, target, trans_id): |
629 | + """Schedule creation of a new symbolic link. |
630 | + |
631 | + target is a bytestring. |
632 | + See also new_symlink. |
633 | + """ |
634 | + if has_symlinks(): |
635 | + os.symlink(target, self._limbo_name(trans_id)) |
636 | + unique_add(self._new_contents, trans_id, 'symlink') |
637 | + else: |
638 | + try: |
639 | + path = FinalPaths(self).get_path(trans_id) |
640 | + except KeyError: |
641 | + path = None |
642 | + raise UnableCreateSymlink(path=path) |
643 | + |
644 | + def cancel_creation(self, trans_id): |
645 | + """Cancel the creation of new file contents.""" |
646 | + del self._new_contents[trans_id] |
647 | + children = self._limbo_children.get(trans_id) |
648 | + # if this is a limbo directory with children, move them before removing |
649 | + # the directory |
650 | + if children is not None: |
651 | + self._rename_in_limbo(children) |
652 | + del self._limbo_children[trans_id] |
653 | + del self._limbo_children_names[trans_id] |
654 | + delete_any(self._limbo_name(trans_id)) |
655 | + |
656 | + |
657 | +class TreeTransform(DiskTreeTransform): |
658 | """Represent a tree transformation. |
659 | |
660 | This object is designed to support incremental generation of the transform, |
661 | @@ -1319,10 +1268,96 @@ |
662 | tree.unlock() |
663 | raise |
664 | |
665 | - TreeTransformBase.__init__(self, tree, limbodir, pb, |
666 | + # Cache of realpath results, to speed up canonical_path |
667 | + self._realpaths = {} |
668 | + # Cache of relpath results, to speed up canonical_path |
669 | + self._relpaths = {} |
670 | + DiskTreeTransform.__init__(self, tree, limbodir, pb, |
671 | tree.case_sensitive) |
672 | self._deletiondir = deletiondir |
673 | |
674 | + def canonical_path(self, path): |
675 | + """Get the canonical tree-relative path""" |
676 | + # don't follow final symlinks |
677 | + abs = self._tree.abspath(path) |
678 | + if abs in self._relpaths: |
679 | + return self._relpaths[abs] |
680 | + dirname, basename = os.path.split(abs) |
681 | + if dirname not in self._realpaths: |
682 | + self._realpaths[dirname] = os.path.realpath(dirname) |
683 | + dirname = self._realpaths[dirname] |
684 | + abs = pathjoin(dirname, basename) |
685 | + if dirname in self._relpaths: |
686 | + relpath = pathjoin(self._relpaths[dirname], basename) |
687 | + relpath = relpath.rstrip('/\\') |
688 | + else: |
689 | + relpath = self._tree.relpath(abs) |
690 | + self._relpaths[abs] = relpath |
691 | + return relpath |
692 | + |
693 | + def tree_kind(self, trans_id): |
694 | + """Determine the file kind in the working tree. |
695 | + |
696 | + Raises NoSuchFile if the file does not exist |
697 | + """ |
698 | + path = self._tree_id_paths.get(trans_id) |
699 | + if path is None: |
700 | + raise NoSuchFile(None) |
701 | + try: |
702 | + return file_kind(self._tree.abspath(path)) |
703 | + except OSError, e: |
704 | + if e.errno != errno.ENOENT: |
705 | + raise |
706 | + else: |
707 | + raise NoSuchFile(path) |
708 | + |
709 | + def _set_mode(self, trans_id, mode_id, typefunc): |
710 | + """Set the mode of new file contents. |
711 | + The mode_id is the existing file to get the mode from (often the same |
712 | + as trans_id). The operation is only performed if there's a mode match |
713 | + according to typefunc. |
714 | + """ |
715 | + if mode_id is None: |
716 | + mode_id = trans_id |
717 | + try: |
718 | + old_path = self._tree_id_paths[mode_id] |
719 | + except KeyError: |
720 | + return |
721 | + try: |
722 | + mode = os.stat(self._tree.abspath(old_path)).st_mode |
723 | + except OSError, e: |
724 | + if e.errno in (errno.ENOENT, errno.ENOTDIR): |
725 | + # Either old_path doesn't exist, or the parent of the |
726 | + # target is not a directory (but will be one eventually) |
727 | + # Either way, we know it doesn't exist *right now* |
728 | + # See also bug #248448 |
729 | + return |
730 | + else: |
731 | + raise |
732 | + if typefunc(mode): |
733 | + os.chmod(self._limbo_name(trans_id), mode) |
734 | + |
735 | + def iter_tree_children(self, parent_id): |
736 | + """Iterate through the entry's tree children, if any""" |
737 | + try: |
738 | + path = self._tree_id_paths[parent_id] |
739 | + except KeyError: |
740 | + return |
741 | + try: |
742 | + children = os.listdir(self._tree.abspath(path)) |
743 | + except OSError, e: |
744 | + if not (osutils._is_error_enotdir(e) |
745 | + or e.errno in (errno.ENOENT, errno.ESRCH)): |
746 | + raise |
747 | + return |
748 | + |
749 | + for child in children: |
750 | + childpath = joinpath(path, child) |
751 | + if self._tree.is_control_filename(childpath): |
752 | + continue |
753 | + yield self.trans_id_tree_path(childpath) |
754 | + |
755 | + |
756 | def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None): |
757 | """Apply all changes to the inventory and filesystem. |
758 | |
759 | @@ -1505,7 +1540,7 @@ |
760 | return modified_paths |
761 | |
762 | |
763 | -class TransformPreview(TreeTransformBase): |
764 | +class TransformPreview(DiskTreeTransform): |
765 | """A TreeTransform for generating preview trees. |
766 | |
767 | Unlike TreeTransform, this version works when the input tree is a |
768 | @@ -1516,7 +1551,7 @@ |
769 | def __init__(self, tree, pb=DummyProgress(), case_sensitive=True): |
770 | tree.lock_read() |
771 | limbodir = osutils.mkdtemp(prefix='bzr-limbo-') |
772 | - TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive) |
773 | + DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive) |
774 | |
775 | def canonical_path(self, path): |
776 | return path |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Hi all,
This patch refactors BaseTreeTranform by moving all code that assumes
limbo is disk-based into DiskTreeTransform, and by moving all methods
overridden by PreviewTreeTranform into TreeTransform.
This clears the way to implement a new TreeTransform type that doesn't
hit disk.
Aaron enigmail. mozdev. org
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAko N2ooACgkQ0F+ nu1YWqI2cIwCfY0 H3XpMLGRt5cIc7g Xd6y4wC kUIbHAwgOMH454s hp
GYkAnioLorhD672
=p07S
-----END PGP SIGNATURE-----