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 | 34 | 34 | ||
6 | 35 | * Added osutils.parent_directories(). (Ian Clatworthy) | 35 | * Added osutils.parent_directories(). (Ian Clatworthy) |
7 | 36 | 36 | ||
8 | 37 | * TreeTransformBase no longer assumes that limbo is provided via disk. | ||
9 | 38 | DiskTreeTransform now provides disk functionality. (Aaron Bentley) | ||
10 | 39 | |||
11 | 40 | |||
12 | 37 | Internals | 41 | Internals |
13 | 38 | ********* | 42 | ********* |
14 | 39 | 43 | ||
15 | 40 | 44 | ||
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 | 76 | 76 | ||
21 | 77 | 77 | ||
22 | 78 | class TreeTransformBase(object): | 78 | class TreeTransformBase(object): |
24 | 79 | """The base class for TreeTransform and TreeTransformBase""" | 79 | """The base class for TreeTransform and its kin.""" |
25 | 80 | 80 | ||
27 | 81 | def __init__(self, tree, limbodir, pb=DummyProgress(), | 81 | def __init__(self, tree, pb=DummyProgress(), |
28 | 82 | case_sensitive=True): | 82 | case_sensitive=True): |
29 | 83 | """Constructor. | 83 | """Constructor. |
30 | 84 | 84 | ||
31 | 85 | :param tree: The tree that will be transformed, but not necessarily | 85 | :param tree: The tree that will be transformed, but not necessarily |
32 | 86 | the output tree. | 86 | the output tree. |
33 | 87 | :param limbodir: A directory where new files can be stored until | ||
34 | 88 | they are installed in their proper places | ||
35 | 89 | :param pb: A ProgressBar indicating how much progress is being made | 87 | :param pb: A ProgressBar indicating how much progress is being made |
36 | 90 | :param case_sensitive: If True, the target of the transform is | 88 | :param case_sensitive: If True, the target of the transform is |
37 | 91 | case sensitive, not just case preserving. | 89 | case sensitive, not just case preserving. |
38 | 92 | """ | 90 | """ |
39 | 93 | object.__init__(self) | 91 | object.__init__(self) |
40 | 94 | self._tree = tree | 92 | self._tree = tree |
41 | 95 | self._limbodir = limbodir | ||
42 | 96 | self._deletiondir = None | ||
43 | 97 | self._id_number = 0 | 93 | self._id_number = 0 |
44 | 98 | # mapping of trans_id -> new basename | 94 | # mapping of trans_id -> new basename |
45 | 99 | self._new_name = {} | 95 | self._new_name = {} |
46 | @@ -101,15 +97,6 @@ | |||
47 | 101 | self._new_parent = {} | 97 | self._new_parent = {} |
48 | 102 | # mapping of trans_id with new contents -> new file_kind | 98 | # mapping of trans_id with new contents -> new file_kind |
49 | 103 | self._new_contents = {} | 99 | self._new_contents = {} |
50 | 104 | # A mapping of transform ids to their limbo filename | ||
51 | 105 | self._limbo_files = {} | ||
52 | 106 | # A mapping of transform ids to a set of the transform ids of children | ||
53 | 107 | # that their limbo directory has | ||
54 | 108 | self._limbo_children = {} | ||
55 | 109 | # Map transform ids to maps of child filename to child transform id | ||
56 | 110 | self._limbo_children_names = {} | ||
57 | 111 | # List of transform ids that need to be renamed from limbo into place | ||
58 | 112 | self._needs_rename = set() | ||
59 | 113 | # Set of trans_ids whose contents will be removed | 100 | # Set of trans_ids whose contents will be removed |
60 | 114 | self._removed_contents = set() | 101 | self._removed_contents = set() |
61 | 115 | # Mapping of trans_id -> new execute-bit value | 102 | # Mapping of trans_id -> new execute-bit value |
62 | @@ -128,10 +115,6 @@ | |||
63 | 128 | self._tree_path_ids = {} | 115 | self._tree_path_ids = {} |
64 | 129 | # Mapping trans_id -> path in old tree | 116 | # Mapping trans_id -> path in old tree |
65 | 130 | self._tree_id_paths = {} | 117 | self._tree_id_paths = {} |
66 | 131 | # Cache of realpath results, to speed up canonical_path | ||
67 | 132 | self._realpaths = {} | ||
68 | 133 | # Cache of relpath results, to speed up canonical_path | ||
69 | 134 | self._relpaths = {} | ||
70 | 135 | # The trans_id that will be used as the tree root | 118 | # The trans_id that will be used as the tree root |
71 | 136 | root_id = tree.get_root_id() | 119 | root_id = tree.get_root_id() |
72 | 137 | if root_id is not None: | 120 | if root_id is not None: |
73 | @@ -147,42 +130,22 @@ | |||
74 | 147 | # A counter of how many files have been renamed | 130 | # A counter of how many files have been renamed |
75 | 148 | self.rename_count = 0 | 131 | self.rename_count = 0 |
76 | 149 | 132 | ||
77 | 133 | def finalize(self): | ||
78 | 134 | """Release the working tree lock, if held. | ||
79 | 135 | |||
80 | 136 | This is required if apply has not been invoked, but can be invoked | ||
81 | 137 | even after apply. | ||
82 | 138 | """ | ||
83 | 139 | if self._tree is None: | ||
84 | 140 | return | ||
85 | 141 | self._tree.unlock() | ||
86 | 142 | self._tree = None | ||
87 | 143 | |||
88 | 150 | def __get_root(self): | 144 | def __get_root(self): |
89 | 151 | return self._new_root | 145 | return self._new_root |
90 | 152 | 146 | ||
91 | 153 | root = property(__get_root) | 147 | root = property(__get_root) |
92 | 154 | 148 | ||
93 | 155 | def finalize(self): | ||
94 | 156 | """Release the working tree lock, if held, clean up limbo dir. | ||
95 | 157 | |||
96 | 158 | This is required if apply has not been invoked, but can be invoked | ||
97 | 159 | even after apply. | ||
98 | 160 | """ | ||
99 | 161 | if self._tree is None: | ||
100 | 162 | return | ||
101 | 163 | try: | ||
102 | 164 | entries = [(self._limbo_name(t), t, k) for t, k in | ||
103 | 165 | self._new_contents.iteritems()] | ||
104 | 166 | entries.sort(reverse=True) | ||
105 | 167 | for path, trans_id, kind in entries: | ||
106 | 168 | if kind == "directory": | ||
107 | 169 | os.rmdir(path) | ||
108 | 170 | else: | ||
109 | 171 | os.unlink(path) | ||
110 | 172 | try: | ||
111 | 173 | os.rmdir(self._limbodir) | ||
112 | 174 | except OSError: | ||
113 | 175 | # We don't especially care *why* the dir is immortal. | ||
114 | 176 | raise ImmortalLimbo(self._limbodir) | ||
115 | 177 | try: | ||
116 | 178 | if self._deletiondir is not None: | ||
117 | 179 | os.rmdir(self._deletiondir) | ||
118 | 180 | except OSError: | ||
119 | 181 | raise errors.ImmortalPendingDeletion(self._deletiondir) | ||
120 | 182 | finally: | ||
121 | 183 | self._tree.unlock() | ||
122 | 184 | self._tree = None | ||
123 | 185 | |||
124 | 186 | def _assign_id(self): | 149 | def _assign_id(self): |
125 | 187 | """Produce a new tranform id""" | 150 | """Produce a new tranform id""" |
126 | 188 | new_id = "new-%s" % self._id_number | 151 | new_id = "new-%s" % self._id_number |
127 | @@ -200,37 +163,12 @@ | |||
128 | 200 | """Change the path that is assigned to a transaction id.""" | 163 | """Change the path that is assigned to a transaction id.""" |
129 | 201 | if trans_id == self._new_root: | 164 | if trans_id == self._new_root: |
130 | 202 | raise CantMoveRoot | 165 | raise CantMoveRoot |
131 | 203 | previous_parent = self._new_parent.get(trans_id) | ||
132 | 204 | previous_name = self._new_name.get(trans_id) | ||
133 | 205 | self._new_name[trans_id] = name | 166 | self._new_name[trans_id] = name |
134 | 206 | self._new_parent[trans_id] = parent | 167 | self._new_parent[trans_id] = parent |
135 | 207 | if parent == ROOT_PARENT: | 168 | if parent == ROOT_PARENT: |
136 | 208 | if self._new_root is not None: | 169 | if self._new_root is not None: |
137 | 209 | raise ValueError("Cannot have multiple roots.") | 170 | raise ValueError("Cannot have multiple roots.") |
138 | 210 | self._new_root = trans_id | 171 | self._new_root = trans_id |
139 | 211 | if (trans_id in self._limbo_files and | ||
140 | 212 | trans_id not in self._needs_rename): | ||
141 | 213 | self._rename_in_limbo([trans_id]) | ||
142 | 214 | self._limbo_children[previous_parent].remove(trans_id) | ||
143 | 215 | del self._limbo_children_names[previous_parent][previous_name] | ||
144 | 216 | |||
145 | 217 | def _rename_in_limbo(self, trans_ids): | ||
146 | 218 | """Fix limbo names so that the right final path is produced. | ||
147 | 219 | |||
148 | 220 | This means we outsmarted ourselves-- we tried to avoid renaming | ||
149 | 221 | these files later by creating them with their final names in their | ||
150 | 222 | final parents. But now the previous name or parent is no longer | ||
151 | 223 | suitable, so we have to rename them. | ||
152 | 224 | |||
153 | 225 | Even for trans_ids that have no new contents, we must remove their | ||
154 | 226 | entries from _limbo_files, because they are now stale. | ||
155 | 227 | """ | ||
156 | 228 | for trans_id in trans_ids: | ||
157 | 229 | old_path = self._limbo_files.pop(trans_id) | ||
158 | 230 | if trans_id not in self._new_contents: | ||
159 | 231 | continue | ||
160 | 232 | new_path = self._limbo_name(trans_id) | ||
161 | 233 | os.rename(old_path, new_path) | ||
162 | 234 | 172 | ||
163 | 235 | def adjust_root_path(self, name, parent): | 173 | def adjust_root_path(self, name, parent): |
164 | 236 | """Emulate moving the root by moving all children, instead. | 174 | """Emulate moving the root by moving all children, instead. |
165 | @@ -298,25 +236,6 @@ | |||
166 | 298 | else: | 236 | else: |
167 | 299 | return self.trans_id_tree_file_id(file_id) | 237 | return self.trans_id_tree_file_id(file_id) |
168 | 300 | 238 | ||
169 | 301 | def canonical_path(self, path): | ||
170 | 302 | """Get the canonical tree-relative path""" | ||
171 | 303 | # don't follow final symlinks | ||
172 | 304 | abs = self._tree.abspath(path) | ||
173 | 305 | if abs in self._relpaths: | ||
174 | 306 | return self._relpaths[abs] | ||
175 | 307 | dirname, basename = os.path.split(abs) | ||
176 | 308 | if dirname not in self._realpaths: | ||
177 | 309 | self._realpaths[dirname] = os.path.realpath(dirname) | ||
178 | 310 | dirname = self._realpaths[dirname] | ||
179 | 311 | abs = pathjoin(dirname, basename) | ||
180 | 312 | if dirname in self._relpaths: | ||
181 | 313 | relpath = pathjoin(self._relpaths[dirname], basename) | ||
182 | 314 | relpath = relpath.rstrip('/\\') | ||
183 | 315 | else: | ||
184 | 316 | relpath = self._tree.relpath(abs) | ||
185 | 317 | self._relpaths[abs] = relpath | ||
186 | 318 | return relpath | ||
187 | 319 | |||
188 | 320 | def trans_id_tree_path(self, path): | 239 | def trans_id_tree_path(self, path): |
189 | 321 | """Determine (and maybe set) the transaction ID for a tree path.""" | 240 | """Determine (and maybe set) the transaction ID for a tree path.""" |
190 | 322 | path = self.canonical_path(path) | 241 | path = self.canonical_path(path) |
191 | @@ -332,113 +251,6 @@ | |||
192 | 332 | return ROOT_PARENT | 251 | return ROOT_PARENT |
193 | 333 | return self.trans_id_tree_path(os.path.dirname(path)) | 252 | return self.trans_id_tree_path(os.path.dirname(path)) |
194 | 334 | 253 | ||
195 | 335 | def create_file(self, contents, trans_id, mode_id=None): | ||
196 | 336 | """Schedule creation of a new file. | ||
197 | 337 | |||
198 | 338 | See also new_file. | ||
199 | 339 | |||
200 | 340 | Contents is an iterator of strings, all of which will be written | ||
201 | 341 | to the target destination. | ||
202 | 342 | |||
203 | 343 | New file takes the permissions of any existing file with that id, | ||
204 | 344 | unless mode_id is specified. | ||
205 | 345 | """ | ||
206 | 346 | name = self._limbo_name(trans_id) | ||
207 | 347 | f = open(name, 'wb') | ||
208 | 348 | try: | ||
209 | 349 | try: | ||
210 | 350 | unique_add(self._new_contents, trans_id, 'file') | ||
211 | 351 | except: | ||
212 | 352 | # Clean up the file, it never got registered so | ||
213 | 353 | # TreeTransform.finalize() won't clean it up. | ||
214 | 354 | f.close() | ||
215 | 355 | os.unlink(name) | ||
216 | 356 | raise | ||
217 | 357 | |||
218 | 358 | f.writelines(contents) | ||
219 | 359 | finally: | ||
220 | 360 | f.close() | ||
221 | 361 | self._set_mode(trans_id, mode_id, S_ISREG) | ||
222 | 362 | |||
223 | 363 | def _set_mode(self, trans_id, mode_id, typefunc): | ||
224 | 364 | """Set the mode of new file contents. | ||
225 | 365 | The mode_id is the existing file to get the mode from (often the same | ||
226 | 366 | as trans_id). The operation is only performed if there's a mode match | ||
227 | 367 | according to typefunc. | ||
228 | 368 | """ | ||
229 | 369 | if mode_id is None: | ||
230 | 370 | mode_id = trans_id | ||
231 | 371 | try: | ||
232 | 372 | old_path = self._tree_id_paths[mode_id] | ||
233 | 373 | except KeyError: | ||
234 | 374 | return | ||
235 | 375 | try: | ||
236 | 376 | mode = os.stat(self._tree.abspath(old_path)).st_mode | ||
237 | 377 | except OSError, e: | ||
238 | 378 | if e.errno in (errno.ENOENT, errno.ENOTDIR): | ||
239 | 379 | # Either old_path doesn't exist, or the parent of the | ||
240 | 380 | # target is not a directory (but will be one eventually) | ||
241 | 381 | # Either way, we know it doesn't exist *right now* | ||
242 | 382 | # See also bug #248448 | ||
243 | 383 | return | ||
244 | 384 | else: | ||
245 | 385 | raise | ||
246 | 386 | if typefunc(mode): | ||
247 | 387 | os.chmod(self._limbo_name(trans_id), mode) | ||
248 | 388 | |||
249 | 389 | def create_hardlink(self, path, trans_id): | ||
250 | 390 | """Schedule creation of a hard link""" | ||
251 | 391 | name = self._limbo_name(trans_id) | ||
252 | 392 | try: | ||
253 | 393 | os.link(path, name) | ||
254 | 394 | except OSError, e: | ||
255 | 395 | if e.errno != errno.EPERM: | ||
256 | 396 | raise | ||
257 | 397 | raise errors.HardLinkNotSupported(path) | ||
258 | 398 | try: | ||
259 | 399 | unique_add(self._new_contents, trans_id, 'file') | ||
260 | 400 | except: | ||
261 | 401 | # Clean up the file, it never got registered so | ||
262 | 402 | # TreeTransform.finalize() won't clean it up. | ||
263 | 403 | os.unlink(name) | ||
264 | 404 | raise | ||
265 | 405 | |||
266 | 406 | def create_directory(self, trans_id): | ||
267 | 407 | """Schedule creation of a new directory. | ||
268 | 408 | |||
269 | 409 | See also new_directory. | ||
270 | 410 | """ | ||
271 | 411 | os.mkdir(self._limbo_name(trans_id)) | ||
272 | 412 | unique_add(self._new_contents, trans_id, 'directory') | ||
273 | 413 | |||
274 | 414 | def create_symlink(self, target, trans_id): | ||
275 | 415 | """Schedule creation of a new symbolic link. | ||
276 | 416 | |||
277 | 417 | target is a bytestring. | ||
278 | 418 | See also new_symlink. | ||
279 | 419 | """ | ||
280 | 420 | if has_symlinks(): | ||
281 | 421 | os.symlink(target, self._limbo_name(trans_id)) | ||
282 | 422 | unique_add(self._new_contents, trans_id, 'symlink') | ||
283 | 423 | else: | ||
284 | 424 | try: | ||
285 | 425 | path = FinalPaths(self).get_path(trans_id) | ||
286 | 426 | except KeyError: | ||
287 | 427 | path = None | ||
288 | 428 | raise UnableCreateSymlink(path=path) | ||
289 | 429 | |||
290 | 430 | def cancel_creation(self, trans_id): | ||
291 | 431 | """Cancel the creation of new file contents.""" | ||
292 | 432 | del self._new_contents[trans_id] | ||
293 | 433 | children = self._limbo_children.get(trans_id) | ||
294 | 434 | # if this is a limbo directory with children, move them before removing | ||
295 | 435 | # the directory | ||
296 | 436 | if children is not None: | ||
297 | 437 | self._rename_in_limbo(children) | ||
298 | 438 | del self._limbo_children[trans_id] | ||
299 | 439 | del self._limbo_children_names[trans_id] | ||
300 | 440 | delete_any(self._limbo_name(trans_id)) | ||
301 | 441 | |||
302 | 442 | def delete_contents(self, trans_id): | 254 | def delete_contents(self, trans_id): |
303 | 443 | """Schedule the contents of a path entry for deletion""" | 255 | """Schedule the contents of a path entry for deletion""" |
304 | 444 | self.tree_kind(trans_id) | 256 | self.tree_kind(trans_id) |
305 | @@ -518,22 +330,6 @@ | |||
306 | 518 | new_ids.update(changed_kind) | 330 | new_ids.update(changed_kind) |
307 | 519 | return sorted(FinalPaths(self).get_paths(new_ids)) | 331 | return sorted(FinalPaths(self).get_paths(new_ids)) |
308 | 520 | 332 | ||
309 | 521 | def tree_kind(self, trans_id): | ||
310 | 522 | """Determine the file kind in the working tree. | ||
311 | 523 | |||
312 | 524 | Raises NoSuchFile if the file does not exist | ||
313 | 525 | """ | ||
314 | 526 | path = self._tree_id_paths.get(trans_id) | ||
315 | 527 | if path is None: | ||
316 | 528 | raise NoSuchFile(None) | ||
317 | 529 | try: | ||
318 | 530 | return file_kind(self._tree.abspath(path)) | ||
319 | 531 | except OSError, e: | ||
320 | 532 | if e.errno != errno.ENOENT: | ||
321 | 533 | raise | ||
322 | 534 | else: | ||
323 | 535 | raise NoSuchFile(path) | ||
324 | 536 | |||
325 | 537 | def final_kind(self, trans_id): | 333 | def final_kind(self, trans_id): |
326 | 538 | """Determine the final file kind, after any changes applied. | 334 | """Determine the final file kind, after any changes applied. |
327 | 539 | 335 | ||
328 | @@ -667,26 +463,6 @@ | |||
329 | 667 | # ensure that all children are registered with the transaction | 463 | # ensure that all children are registered with the transaction |
330 | 668 | list(self.iter_tree_children(parent_id)) | 464 | list(self.iter_tree_children(parent_id)) |
331 | 669 | 465 | ||
332 | 670 | def iter_tree_children(self, parent_id): | ||
333 | 671 | """Iterate through the entry's tree children, if any""" | ||
334 | 672 | try: | ||
335 | 673 | path = self._tree_id_paths[parent_id] | ||
336 | 674 | except KeyError: | ||
337 | 675 | return | ||
338 | 676 | try: | ||
339 | 677 | children = os.listdir(self._tree.abspath(path)) | ||
340 | 678 | except OSError, e: | ||
341 | 679 | if not (osutils._is_error_enotdir(e) | ||
342 | 680 | or e.errno in (errno.ENOENT, errno.ESRCH)): | ||
343 | 681 | raise | ||
344 | 682 | return | ||
345 | 683 | |||
346 | 684 | for child in children: | ||
347 | 685 | childpath = joinpath(path, child) | ||
348 | 686 | if self._tree.is_control_filename(childpath): | ||
349 | 687 | continue | ||
350 | 688 | yield self.trans_id_tree_path(childpath) | ||
351 | 689 | |||
352 | 690 | def has_named_child(self, by_parent, parent_id, name): | 466 | def has_named_child(self, by_parent, parent_id, name): |
353 | 691 | try: | 467 | try: |
354 | 692 | children = by_parent[parent_id] | 468 | children = by_parent[parent_id] |
355 | @@ -867,50 +643,6 @@ | |||
356 | 867 | return True | 643 | return True |
357 | 868 | return False | 644 | return False |
358 | 869 | 645 | ||
359 | 870 | def _limbo_name(self, trans_id): | ||
360 | 871 | """Generate the limbo name of a file""" | ||
361 | 872 | limbo_name = self._limbo_files.get(trans_id) | ||
362 | 873 | if limbo_name is not None: | ||
363 | 874 | return limbo_name | ||
364 | 875 | parent = self._new_parent.get(trans_id) | ||
365 | 876 | # if the parent directory is already in limbo (e.g. when building a | ||
366 | 877 | # tree), choose a limbo name inside the parent, to reduce further | ||
367 | 878 | # renames. | ||
368 | 879 | use_direct_path = False | ||
369 | 880 | if self._new_contents.get(parent) == 'directory': | ||
370 | 881 | filename = self._new_name.get(trans_id) | ||
371 | 882 | if filename is not None: | ||
372 | 883 | if parent not in self._limbo_children: | ||
373 | 884 | self._limbo_children[parent] = set() | ||
374 | 885 | self._limbo_children_names[parent] = {} | ||
375 | 886 | use_direct_path = True | ||
376 | 887 | # the direct path can only be used if no other file has | ||
377 | 888 | # already taken this pathname, i.e. if the name is unused, or | ||
378 | 889 | # if it is already associated with this trans_id. | ||
379 | 890 | elif self._case_sensitive_target: | ||
380 | 891 | if (self._limbo_children_names[parent].get(filename) | ||
381 | 892 | in (trans_id, None)): | ||
382 | 893 | use_direct_path = True | ||
383 | 894 | else: | ||
384 | 895 | for l_filename, l_trans_id in\ | ||
385 | 896 | self._limbo_children_names[parent].iteritems(): | ||
386 | 897 | if l_trans_id == trans_id: | ||
387 | 898 | continue | ||
388 | 899 | if l_filename.lower() == filename.lower(): | ||
389 | 900 | break | ||
390 | 901 | else: | ||
391 | 902 | use_direct_path = True | ||
392 | 903 | |||
393 | 904 | if use_direct_path: | ||
394 | 905 | limbo_name = pathjoin(self._limbo_files[parent], filename) | ||
395 | 906 | self._limbo_children[parent].add(trans_id) | ||
396 | 907 | self._limbo_children_names[parent][filename] = trans_id | ||
397 | 908 | else: | ||
398 | 909 | limbo_name = pathjoin(self._limbodir, trans_id) | ||
399 | 910 | self._needs_rename.add(trans_id) | ||
400 | 911 | self._limbo_files[trans_id] = limbo_name | ||
401 | 912 | return limbo_name | ||
402 | 913 | |||
403 | 914 | def _set_executability(self, path, trans_id): | 646 | def _set_executability(self, path, trans_id): |
404 | 915 | """Set the executability of versioned files """ | 647 | """Set the executability of versioned files """ |
405 | 916 | if supports_executable(): | 648 | if supports_executable(): |
406 | @@ -1176,21 +908,17 @@ | |||
407 | 1176 | (('attribs',),)) | 908 | (('attribs',),)) |
408 | 1177 | for trans_id, kind in self._new_contents.items(): | 909 | for trans_id, kind in self._new_contents.items(): |
409 | 1178 | if kind == 'file': | 910 | if kind == 'file': |
415 | 1179 | cur_file = open(self._limbo_name(trans_id), 'rb') | 911 | lines = osutils.chunks_to_lines( |
416 | 1180 | try: | 912 | self._read_file_chunks(trans_id)) |
412 | 1181 | lines = osutils.chunks_to_lines(cur_file.readlines()) | ||
413 | 1182 | finally: | ||
414 | 1183 | cur_file.close() | ||
417 | 1184 | parents = self._get_parents_lines(trans_id) | 913 | parents = self._get_parents_lines(trans_id) |
418 | 1185 | mpdiff = multiparent.MultiParent.from_lines(lines, parents) | 914 | mpdiff = multiparent.MultiParent.from_lines(lines, parents) |
419 | 1186 | content = ''.join(mpdiff.to_patch()) | 915 | content = ''.join(mpdiff.to_patch()) |
420 | 1187 | if kind == 'directory': | 916 | if kind == 'directory': |
421 | 1188 | content = '' | 917 | content = '' |
422 | 1189 | if kind == 'symlink': | 918 | if kind == 'symlink': |
424 | 1190 | content = os.readlink(self._limbo_name(trans_id)) | 919 | content = self._read_symlink_target(trans_id) |
425 | 1191 | yield serializer.bytes_record(content, ((trans_id, kind),)) | 920 | yield serializer.bytes_record(content, ((trans_id, kind),)) |
426 | 1192 | 921 | ||
427 | 1193 | |||
428 | 1194 | def deserialize(self, records): | 922 | def deserialize(self, records): |
429 | 1195 | """Deserialize a stored TreeTransform. | 923 | """Deserialize a stored TreeTransform. |
430 | 1196 | 924 | ||
431 | @@ -1227,7 +955,228 @@ | |||
432 | 1227 | self.create_symlink(content.decode('utf-8'), trans_id) | 955 | self.create_symlink(content.decode('utf-8'), trans_id) |
433 | 1228 | 956 | ||
434 | 1229 | 957 | ||
436 | 1230 | class TreeTransform(TreeTransformBase): | 958 | class DiskTreeTransform(TreeTransformBase): |
437 | 959 | """Tree transform storing its contents on disk.""" | ||
438 | 960 | |||
439 | 961 | def __init__(self, tree, limbodir, pb=DummyProgress(), | ||
440 | 962 | case_sensitive=True): | ||
441 | 963 | """Constructor. | ||
442 | 964 | :param tree: The tree that will be transformed, but not necessarily | ||
443 | 965 | the output tree. | ||
444 | 966 | :param limbodir: A directory where new files can be stored until | ||
445 | 967 | they are installed in their proper places | ||
446 | 968 | :param pb: A ProgressBar indicating how much progress is being made | ||
447 | 969 | :param case_sensitive: If True, the target of the transform is | ||
448 | 970 | case sensitive, not just case preserving. | ||
449 | 971 | """ | ||
450 | 972 | TreeTransformBase.__init__(self, tree, pb, case_sensitive) | ||
451 | 973 | self._limbodir = limbodir | ||
452 | 974 | self._deletiondir = None | ||
453 | 975 | # A mapping of transform ids to their limbo filename | ||
454 | 976 | self._limbo_files = {} | ||
455 | 977 | # A mapping of transform ids to a set of the transform ids of children | ||
456 | 978 | # that their limbo directory has | ||
457 | 979 | self._limbo_children = {} | ||
458 | 980 | # Map transform ids to maps of child filename to child transform id | ||
459 | 981 | self._limbo_children_names = {} | ||
460 | 982 | # List of transform ids that need to be renamed from limbo into place | ||
461 | 983 | self._needs_rename = set() | ||
462 | 984 | |||
463 | 985 | def finalize(self): | ||
464 | 986 | """Release the working tree lock, if held, clean up limbo dir. | ||
465 | 987 | |||
466 | 988 | This is required if apply has not been invoked, but can be invoked | ||
467 | 989 | even after apply. | ||
468 | 990 | """ | ||
469 | 991 | if self._tree is None: | ||
470 | 992 | return | ||
471 | 993 | try: | ||
472 | 994 | entries = [(self._limbo_name(t), t, k) for t, k in | ||
473 | 995 | self._new_contents.iteritems()] | ||
474 | 996 | entries.sort(reverse=True) | ||
475 | 997 | for path, trans_id, kind in entries: | ||
476 | 998 | if kind == "directory": | ||
477 | 999 | os.rmdir(path) | ||
478 | 1000 | else: | ||
479 | 1001 | os.unlink(path) | ||
480 | 1002 | try: | ||
481 | 1003 | os.rmdir(self._limbodir) | ||
482 | 1004 | except OSError: | ||
483 | 1005 | # We don't especially care *why* the dir is immortal. | ||
484 | 1006 | raise ImmortalLimbo(self._limbodir) | ||
485 | 1007 | try: | ||
486 | 1008 | if self._deletiondir is not None: | ||
487 | 1009 | os.rmdir(self._deletiondir) | ||
488 | 1010 | except OSError: | ||
489 | 1011 | raise errors.ImmortalPendingDeletion(self._deletiondir) | ||
490 | 1012 | finally: | ||
491 | 1013 | TreeTransformBase.finalize(self) | ||
492 | 1014 | |||
493 | 1015 | def _limbo_name(self, trans_id): | ||
494 | 1016 | """Generate the limbo name of a file""" | ||
495 | 1017 | limbo_name = self._limbo_files.get(trans_id) | ||
496 | 1018 | if limbo_name is not None: | ||
497 | 1019 | return limbo_name | ||
498 | 1020 | parent = self._new_parent.get(trans_id) | ||
499 | 1021 | # if the parent directory is already in limbo (e.g. when building a | ||
500 | 1022 | # tree), choose a limbo name inside the parent, to reduce further | ||
501 | 1023 | # renames. | ||
502 | 1024 | use_direct_path = False | ||
503 | 1025 | if self._new_contents.get(parent) == 'directory': | ||
504 | 1026 | filename = self._new_name.get(trans_id) | ||
505 | 1027 | if filename is not None: | ||
506 | 1028 | if parent not in self._limbo_children: | ||
507 | 1029 | self._limbo_children[parent] = set() | ||
508 | 1030 | self._limbo_children_names[parent] = {} | ||
509 | 1031 | use_direct_path = True | ||
510 | 1032 | # the direct path can only be used if no other file has | ||
511 | 1033 | # already taken this pathname, i.e. if the name is unused, or | ||
512 | 1034 | # if it is already associated with this trans_id. | ||
513 | 1035 | elif self._case_sensitive_target: | ||
514 | 1036 | if (self._limbo_children_names[parent].get(filename) | ||
515 | 1037 | in (trans_id, None)): | ||
516 | 1038 | use_direct_path = True | ||
517 | 1039 | else: | ||
518 | 1040 | for l_filename, l_trans_id in\ | ||
519 | 1041 | self._limbo_children_names[parent].iteritems(): | ||
520 | 1042 | if l_trans_id == trans_id: | ||
521 | 1043 | continue | ||
522 | 1044 | if l_filename.lower() == filename.lower(): | ||
523 | 1045 | break | ||
524 | 1046 | else: | ||
525 | 1047 | use_direct_path = True | ||
526 | 1048 | |||
527 | 1049 | if use_direct_path: | ||
528 | 1050 | limbo_name = pathjoin(self._limbo_files[parent], filename) | ||
529 | 1051 | self._limbo_children[parent].add(trans_id) | ||
530 | 1052 | self._limbo_children_names[parent][filename] = trans_id | ||
531 | 1053 | else: | ||
532 | 1054 | limbo_name = pathjoin(self._limbodir, trans_id) | ||
533 | 1055 | self._needs_rename.add(trans_id) | ||
534 | 1056 | self._limbo_files[trans_id] = limbo_name | ||
535 | 1057 | return limbo_name | ||
536 | 1058 | |||
537 | 1059 | def adjust_path(self, name, parent, trans_id): | ||
538 | 1060 | previous_parent = self._new_parent.get(trans_id) | ||
539 | 1061 | previous_name = self._new_name.get(trans_id) | ||
540 | 1062 | TreeTransformBase.adjust_path(self, name, parent, trans_id) | ||
541 | 1063 | if (trans_id in self._limbo_files and | ||
542 | 1064 | trans_id not in self._needs_rename): | ||
543 | 1065 | self._rename_in_limbo([trans_id]) | ||
544 | 1066 | self._limbo_children[previous_parent].remove(trans_id) | ||
545 | 1067 | del self._limbo_children_names[previous_parent][previous_name] | ||
546 | 1068 | |||
547 | 1069 | def _rename_in_limbo(self, trans_ids): | ||
548 | 1070 | """Fix limbo names so that the right final path is produced. | ||
549 | 1071 | |||
550 | 1072 | This means we outsmarted ourselves-- we tried to avoid renaming | ||
551 | 1073 | these files later by creating them with their final names in their | ||
552 | 1074 | final parents. But now the previous name or parent is no longer | ||
553 | 1075 | suitable, so we have to rename them. | ||
554 | 1076 | |||
555 | 1077 | Even for trans_ids that have no new contents, we must remove their | ||
556 | 1078 | entries from _limbo_files, because they are now stale. | ||
557 | 1079 | """ | ||
558 | 1080 | for trans_id in trans_ids: | ||
559 | 1081 | old_path = self._limbo_files.pop(trans_id) | ||
560 | 1082 | if trans_id not in self._new_contents: | ||
561 | 1083 | continue | ||
562 | 1084 | new_path = self._limbo_name(trans_id) | ||
563 | 1085 | os.rename(old_path, new_path) | ||
564 | 1086 | |||
565 | 1087 | def create_file(self, contents, trans_id, mode_id=None): | ||
566 | 1088 | """Schedule creation of a new file. | ||
567 | 1089 | |||
568 | 1090 | See also new_file. | ||
569 | 1091 | |||
570 | 1092 | Contents is an iterator of strings, all of which will be written | ||
571 | 1093 | to the target destination. | ||
572 | 1094 | |||
573 | 1095 | New file takes the permissions of any existing file with that id, | ||
574 | 1096 | unless mode_id is specified. | ||
575 | 1097 | """ | ||
576 | 1098 | name = self._limbo_name(trans_id) | ||
577 | 1099 | f = open(name, 'wb') | ||
578 | 1100 | try: | ||
579 | 1101 | try: | ||
580 | 1102 | unique_add(self._new_contents, trans_id, 'file') | ||
581 | 1103 | except: | ||
582 | 1104 | # Clean up the file, it never got registered so | ||
583 | 1105 | # TreeTransform.finalize() won't clean it up. | ||
584 | 1106 | f.close() | ||
585 | 1107 | os.unlink(name) | ||
586 | 1108 | raise | ||
587 | 1109 | |||
588 | 1110 | f.writelines(contents) | ||
589 | 1111 | finally: | ||
590 | 1112 | f.close() | ||
591 | 1113 | self._set_mode(trans_id, mode_id, S_ISREG) | ||
592 | 1114 | |||
593 | 1115 | def _read_file_chunks(self, trans_id): | ||
594 | 1116 | cur_file = open(self._limbo_name(trans_id), 'rb') | ||
595 | 1117 | try: | ||
596 | 1118 | return cur_file.readlines() | ||
597 | 1119 | finally: | ||
598 | 1120 | cur_file.close() | ||
599 | 1121 | |||
600 | 1122 | def _read_symlink_target(self, trans_id): | ||
601 | 1123 | return os.readlink(self._limbo_name(trans_id)) | ||
602 | 1124 | |||
603 | 1125 | def create_hardlink(self, path, trans_id): | ||
604 | 1126 | """Schedule creation of a hard link""" | ||
605 | 1127 | name = self._limbo_name(trans_id) | ||
606 | 1128 | try: | ||
607 | 1129 | os.link(path, name) | ||
608 | 1130 | except OSError, e: | ||
609 | 1131 | if e.errno != errno.EPERM: | ||
610 | 1132 | raise | ||
611 | 1133 | raise errors.HardLinkNotSupported(path) | ||
612 | 1134 | try: | ||
613 | 1135 | unique_add(self._new_contents, trans_id, 'file') | ||
614 | 1136 | except: | ||
615 | 1137 | # Clean up the file, it never got registered so | ||
616 | 1138 | # TreeTransform.finalize() won't clean it up. | ||
617 | 1139 | os.unlink(name) | ||
618 | 1140 | raise | ||
619 | 1141 | |||
620 | 1142 | def create_directory(self, trans_id): | ||
621 | 1143 | """Schedule creation of a new directory. | ||
622 | 1144 | |||
623 | 1145 | See also new_directory. | ||
624 | 1146 | """ | ||
625 | 1147 | os.mkdir(self._limbo_name(trans_id)) | ||
626 | 1148 | unique_add(self._new_contents, trans_id, 'directory') | ||
627 | 1149 | |||
628 | 1150 | def create_symlink(self, target, trans_id): | ||
629 | 1151 | """Schedule creation of a new symbolic link. | ||
630 | 1152 | |||
631 | 1153 | target is a bytestring. | ||
632 | 1154 | See also new_symlink. | ||
633 | 1155 | """ | ||
634 | 1156 | if has_symlinks(): | ||
635 | 1157 | os.symlink(target, self._limbo_name(trans_id)) | ||
636 | 1158 | unique_add(self._new_contents, trans_id, 'symlink') | ||
637 | 1159 | else: | ||
638 | 1160 | try: | ||
639 | 1161 | path = FinalPaths(self).get_path(trans_id) | ||
640 | 1162 | except KeyError: | ||
641 | 1163 | path = None | ||
642 | 1164 | raise UnableCreateSymlink(path=path) | ||
643 | 1165 | |||
644 | 1166 | def cancel_creation(self, trans_id): | ||
645 | 1167 | """Cancel the creation of new file contents.""" | ||
646 | 1168 | del self._new_contents[trans_id] | ||
647 | 1169 | children = self._limbo_children.get(trans_id) | ||
648 | 1170 | # if this is a limbo directory with children, move them before removing | ||
649 | 1171 | # the directory | ||
650 | 1172 | if children is not None: | ||
651 | 1173 | self._rename_in_limbo(children) | ||
652 | 1174 | del self._limbo_children[trans_id] | ||
653 | 1175 | del self._limbo_children_names[trans_id] | ||
654 | 1176 | delete_any(self._limbo_name(trans_id)) | ||
655 | 1177 | |||
656 | 1178 | |||
657 | 1179 | class TreeTransform(DiskTreeTransform): | ||
658 | 1231 | """Represent a tree transformation. | 1180 | """Represent a tree transformation. |
659 | 1232 | 1181 | ||
660 | 1233 | This object is designed to support incremental generation of the transform, | 1182 | This object is designed to support incremental generation of the transform, |
661 | @@ -1319,10 +1268,96 @@ | |||
662 | 1319 | tree.unlock() | 1268 | tree.unlock() |
663 | 1320 | raise | 1269 | raise |
664 | 1321 | 1270 | ||
666 | 1322 | TreeTransformBase.__init__(self, tree, limbodir, pb, | 1271 | # Cache of realpath results, to speed up canonical_path |
667 | 1272 | self._realpaths = {} | ||
668 | 1273 | # Cache of relpath results, to speed up canonical_path | ||
669 | 1274 | self._relpaths = {} | ||
670 | 1275 | DiskTreeTransform.__init__(self, tree, limbodir, pb, | ||
671 | 1323 | tree.case_sensitive) | 1276 | tree.case_sensitive) |
672 | 1324 | self._deletiondir = deletiondir | 1277 | self._deletiondir = deletiondir |
673 | 1325 | 1278 | ||
674 | 1279 | def canonical_path(self, path): | ||
675 | 1280 | """Get the canonical tree-relative path""" | ||
676 | 1281 | # don't follow final symlinks | ||
677 | 1282 | abs = self._tree.abspath(path) | ||
678 | 1283 | if abs in self._relpaths: | ||
679 | 1284 | return self._relpaths[abs] | ||
680 | 1285 | dirname, basename = os.path.split(abs) | ||
681 | 1286 | if dirname not in self._realpaths: | ||
682 | 1287 | self._realpaths[dirname] = os.path.realpath(dirname) | ||
683 | 1288 | dirname = self._realpaths[dirname] | ||
684 | 1289 | abs = pathjoin(dirname, basename) | ||
685 | 1290 | if dirname in self._relpaths: | ||
686 | 1291 | relpath = pathjoin(self._relpaths[dirname], basename) | ||
687 | 1292 | relpath = relpath.rstrip('/\\') | ||
688 | 1293 | else: | ||
689 | 1294 | relpath = self._tree.relpath(abs) | ||
690 | 1295 | self._relpaths[abs] = relpath | ||
691 | 1296 | return relpath | ||
692 | 1297 | |||
693 | 1298 | def tree_kind(self, trans_id): | ||
694 | 1299 | """Determine the file kind in the working tree. | ||
695 | 1300 | |||
696 | 1301 | Raises NoSuchFile if the file does not exist | ||
697 | 1302 | """ | ||
698 | 1303 | path = self._tree_id_paths.get(trans_id) | ||
699 | 1304 | if path is None: | ||
700 | 1305 | raise NoSuchFile(None) | ||
701 | 1306 | try: | ||
702 | 1307 | return file_kind(self._tree.abspath(path)) | ||
703 | 1308 | except OSError, e: | ||
704 | 1309 | if e.errno != errno.ENOENT: | ||
705 | 1310 | raise | ||
706 | 1311 | else: | ||
707 | 1312 | raise NoSuchFile(path) | ||
708 | 1313 | |||
709 | 1314 | def _set_mode(self, trans_id, mode_id, typefunc): | ||
710 | 1315 | """Set the mode of new file contents. | ||
711 | 1316 | The mode_id is the existing file to get the mode from (often the same | ||
712 | 1317 | as trans_id). The operation is only performed if there's a mode match | ||
713 | 1318 | according to typefunc. | ||
714 | 1319 | """ | ||
715 | 1320 | if mode_id is None: | ||
716 | 1321 | mode_id = trans_id | ||
717 | 1322 | try: | ||
718 | 1323 | old_path = self._tree_id_paths[mode_id] | ||
719 | 1324 | except KeyError: | ||
720 | 1325 | return | ||
721 | 1326 | try: | ||
722 | 1327 | mode = os.stat(self._tree.abspath(old_path)).st_mode | ||
723 | 1328 | except OSError, e: | ||
724 | 1329 | if e.errno in (errno.ENOENT, errno.ENOTDIR): | ||
725 | 1330 | # Either old_path doesn't exist, or the parent of the | ||
726 | 1331 | # target is not a directory (but will be one eventually) | ||
727 | 1332 | # Either way, we know it doesn't exist *right now* | ||
728 | 1333 | # See also bug #248448 | ||
729 | 1334 | return | ||
730 | 1335 | else: | ||
731 | 1336 | raise | ||
732 | 1337 | if typefunc(mode): | ||
733 | 1338 | os.chmod(self._limbo_name(trans_id), mode) | ||
734 | 1339 | |||
735 | 1340 | def iter_tree_children(self, parent_id): | ||
736 | 1341 | """Iterate through the entry's tree children, if any""" | ||
737 | 1342 | try: | ||
738 | 1343 | path = self._tree_id_paths[parent_id] | ||
739 | 1344 | except KeyError: | ||
740 | 1345 | return | ||
741 | 1346 | try: | ||
742 | 1347 | children = os.listdir(self._tree.abspath(path)) | ||
743 | 1348 | except OSError, e: | ||
744 | 1349 | if not (osutils._is_error_enotdir(e) | ||
745 | 1350 | or e.errno in (errno.ENOENT, errno.ESRCH)): | ||
746 | 1351 | raise | ||
747 | 1352 | return | ||
748 | 1353 | |||
749 | 1354 | for child in children: | ||
750 | 1355 | childpath = joinpath(path, child) | ||
751 | 1356 | if self._tree.is_control_filename(childpath): | ||
752 | 1357 | continue | ||
753 | 1358 | yield self.trans_id_tree_path(childpath) | ||
754 | 1359 | |||
755 | 1360 | |||
756 | 1326 | def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None): | 1361 | def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None): |
757 | 1327 | """Apply all changes to the inventory and filesystem. | 1362 | """Apply all changes to the inventory and filesystem. |
758 | 1328 | 1363 | ||
759 | @@ -1505,7 +1540,7 @@ | |||
760 | 1505 | return modified_paths | 1540 | return modified_paths |
761 | 1506 | 1541 | ||
762 | 1507 | 1542 | ||
764 | 1508 | class TransformPreview(TreeTransformBase): | 1543 | class TransformPreview(DiskTreeTransform): |
765 | 1509 | """A TreeTransform for generating preview trees. | 1544 | """A TreeTransform for generating preview trees. |
766 | 1510 | 1545 | ||
767 | 1511 | Unlike TreeTransform, this version works when the input tree is a | 1546 | Unlike TreeTransform, this version works when the input tree is a |
768 | @@ -1516,7 +1551,7 @@ | |||
769 | 1516 | def __init__(self, tree, pb=DummyProgress(), case_sensitive=True): | 1551 | def __init__(self, tree, pb=DummyProgress(), case_sensitive=True): |
770 | 1517 | tree.lock_read() | 1552 | tree.lock_read() |
771 | 1518 | limbodir = osutils.mkdtemp(prefix='bzr-limbo-') | 1553 | limbodir = osutils.mkdtemp(prefix='bzr-limbo-') |
773 | 1519 | TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive) | 1554 | DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive) |
774 | 1520 | 1555 | ||
775 | 1521 | def canonical_path(self, path): | 1556 | def canonical_path(self, path): |
776 | 1522 | return path | 1557 | 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-----