bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
|
1534.7.106
by Aaron Bentley
Cleaned up imports, added copyright statements |
1 |
# Copyright (C) 2006 Canonical Ltd
|
2 |
||
3 |
# This program is free software; you can redistribute it and/or modify
|
|
4 |
# it under the terms of the GNU General Public License as published by
|
|
5 |
# the Free Software Foundation; either version 2 of the License, or
|
|
6 |
# (at your option) any later version.
|
|
7 |
||
8 |
# This program is distributed in the hope that it will be useful,
|
|
9 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11 |
# GNU General Public License for more details.
|
|
12 |
||
13 |
# You should have received a copy of the GNU General Public License
|
|
14 |
# along with this program; if not, write to the Free Software
|
|
15 |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
16 |
||
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
17 |
import os |
|
1534.7.106
by Aaron Bentley
Cleaned up imports, added copyright statements |
18 |
import errno |
|
1534.8.3
by Aaron Bentley
Added Diff3 merging for tree transforms |
19 |
from tempfile import mkdtemp |
20 |
from shutil import rmtree |
|
|
1534.7.117
by Aaron Bentley
Simplified permission handling of existing files in transform. |
21 |
from stat import S_ISREG |
|
1534.7.106
by Aaron Bentley
Cleaned up imports, added copyright statements |
22 |
|
23 |
from bzrlib import BZRDIR |
|
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
24 |
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile, |
|
1534.7.124
by Aaron Bentley
Fixed merge_core bug |
25 |
ReusingTransform, NotVersionedError, CantMoveRoot, |
26 |
WorkingTreeNotRevision) |
|
|
1534.7.106
by Aaron Bentley
Cleaned up imports, added copyright statements |
27 |
from bzrlib.inventory import InventoryEntry |
|
1534.8.3
by Aaron Bentley
Added Diff3 merging for tree transforms |
28 |
from bzrlib.osutils import file_kind, supports_executable, pathjoin |
|
1534.7.106
by Aaron Bentley
Cleaned up imports, added copyright statements |
29 |
from bzrlib.merge3 import Merge3 |
|
1534.7.116
by Aaron Bentley
Started work on retaining file mode across replacements |
30 |
from bzrlib.trace import mutter |
|
1534.7.31
by Aaron Bentley
Changed tree root parent to ROOT_PARENT |
31 |
|
32 |
ROOT_PARENT = "root-parent" |
|
33 |
||
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
34 |
def unique_add(map, key, value): |
35 |
if key in map: |
|
|
1534.7.5
by Aaron Bentley
Got unique_add under test |
36 |
raise DuplicateKey(key=key) |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
37 |
map[key] = value |
38 |
||
39 |
class TreeTransform(object): |
|
40 |
"""Represent a tree transformation.""" |
|
41 |
def __init__(self, tree): |
|
42 |
"""Note: a write lock is taken on the tree. |
|
43 |
|
|
44 |
Use TreeTransform.finalize() to release the lock
|
|
45 |
"""
|
|
46 |
object.__init__(self) |
|
47 |
self._tree = tree |
|
48 |
self._tree.lock_write() |
|
49 |
self._id_number = 0 |
|
50 |
self._new_name = {} |
|
51 |
self._new_parent = {} |
|
|
1534.7.4
by Aaron Bentley
Unified all file types as 'contents' |
52 |
self._new_contents = {} |
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
53 |
self._removed_contents = set() |
|
1534.7.25
by Aaron Bentley
Added set_executability |
54 |
self._new_executability = {} |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
55 |
self._new_id = {} |
|
1534.7.75
by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id |
56 |
self._r_new_id = {} |
|
1534.7.39
by Aaron Bentley
Ensured that files can be unversioned (de-versioned?) |
57 |
self._removed_id = set() |
|
1534.7.7
by Aaron Bentley
Added support for all-file path ids |
58 |
self._tree_path_ids = {} |
|
1534.7.8
by Aaron Bentley
Added TreeTransform.final_kind |
59 |
self._tree_id_paths = {} |
|
1534.7.3
by Aaron Bentley
Updated to use a real root id. Or whatever the working tree considers real. |
60 |
self._new_root = self.get_id_tree(tree.get_root_id()) |
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
61 |
self.__done = False |
|
1534.7.118
by Aaron Bentley
Dirty merge of the mainline |
62 |
# XXX use the WorkingTree LockableFiles, when available
|
63 |
control_files = self._tree.branch.control_files |
|
64 |
self._limbodir = control_files.controlfilename('limbo') |
|
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
65 |
os.mkdir(self._limbodir) |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
66 |
|
67 |
def finalize(self): |
|
|
1534.7.40
by Aaron Bentley
Updated docs |
68 |
"""Release the working tree lock, if held.""" |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
69 |
if self._tree is None: |
70 |
return
|
|
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
71 |
for trans_id, kind in self._new_contents.iteritems(): |
72 |
path = self._limbo_name(trans_id) |
|
73 |
if kind == "directory": |
|
74 |
os.rmdir(path) |
|
75 |
else: |
|
76 |
os.unlink(path) |
|
77 |
os.rmdir(self._limbodir) |
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
78 |
self._tree.unlock() |
79 |
self._tree = None |
|
80 |
||
81 |
def _assign_id(self): |
|
82 |
"""Produce a new tranform id""" |
|
83 |
new_id = "new-%s" % self._id_number |
|
84 |
self._id_number +=1 |
|
85 |
return new_id |
|
86 |
||
87 |
def create_path(self, name, parent): |
|
88 |
"""Assign a transaction id to a new path""" |
|
89 |
trans_id = self._assign_id() |
|
90 |
unique_add(self._new_name, trans_id, name) |
|
91 |
unique_add(self._new_parent, trans_id, parent) |
|
92 |
return trans_id |
|
93 |
||
|
1534.7.6
by Aaron Bentley
Added conflict handling |
94 |
def adjust_path(self, name, parent, trans_id): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
95 |
"""Change the path that is assigned to a transaction id.""" |
|
1534.7.66
by Aaron Bentley
Ensured we don't accidentally move the root directory |
96 |
if trans_id == self._new_root: |
97 |
raise CantMoveRoot |
|
|
1534.7.6
by Aaron Bentley
Added conflict handling |
98 |
self._new_name[trans_id] = name |
99 |
self._new_parent[trans_id] = parent |
|
100 |
||
|
1534.7.68
by Aaron Bentley
Got semi-reasonable root directory renaming working |
101 |
def adjust_root_path(self, name, parent): |
102 |
"""Emulate moving the root by moving all children, instead. |
|
103 |
|
|
104 |
We do this by undoing the association of root's transaction id with the
|
|
105 |
current tree. This allows us to create a new directory with that
|
|
|
1534.7.69
by Aaron Bentley
Got real root moves working |
106 |
transaction id. We unversion the root directory and version the
|
107 |
physically new directory, and hope someone versions the tree root
|
|
108 |
later.
|
|
|
1534.7.68
by Aaron Bentley
Got semi-reasonable root directory renaming working |
109 |
"""
|
110 |
old_root = self._new_root |
|
111 |
old_root_file_id = self.final_file_id(old_root) |
|
112 |
# force moving all children of root
|
|
113 |
for child_id in self.iter_tree_children(old_root): |
|
114 |
if child_id != parent: |
|
115 |
self.adjust_path(self.final_name(child_id), |
|
116 |
self.final_parent(child_id), child_id) |
|
|
1534.7.69
by Aaron Bentley
Got real root moves working |
117 |
file_id = self.final_file_id(child_id) |
118 |
if file_id is not None: |
|
119 |
self.unversion_file(child_id) |
|
120 |
self.version_file(file_id, child_id) |
|
|
1534.7.68
by Aaron Bentley
Got semi-reasonable root directory renaming working |
121 |
|
122 |
# the physical root needs a new transaction id
|
|
123 |
self._tree_path_ids.pop("") |
|
124 |
self._tree_id_paths.pop(old_root) |
|
125 |
self._new_root = self.get_id_tree(self._tree.get_root_id()) |
|
126 |
if parent == old_root: |
|
127 |
parent = self._new_root |
|
128 |
self.adjust_path(name, parent, old_root) |
|
129 |
self.create_directory(old_root) |
|
|
1534.7.69
by Aaron Bentley
Got real root moves working |
130 |
self.version_file(old_root_file_id, old_root) |
131 |
self.unversion_file(self._new_root) |
|
|
1534.7.68
by Aaron Bentley
Got semi-reasonable root directory renaming working |
132 |
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
133 |
def get_id_tree(self, inventory_id): |
134 |
"""Determine the transaction id of a working tree file. |
|
135 |
|
|
136 |
This reflects only files that already exist, not ones that will be
|
|
137 |
added by transactions.
|
|
138 |
"""
|
|
|
1534.7.7
by Aaron Bentley
Added support for all-file path ids |
139 |
return self.get_tree_path_id(self._tree.id2path(inventory_id)) |
140 |
||
|
1534.7.75
by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id |
141 |
def get_trans_id(self, file_id): |
142 |
"""\ |
|
143 |
Determine or set the transaction id associated with a file ID.
|
|
144 |
A new id is only created for file_ids that were never present. If
|
|
145 |
a transaction has been unversioned, it is deliberately still returned.
|
|
146 |
(this will likely lead to an unversioned parent conflict.)
|
|
147 |
"""
|
|
148 |
if file_id in self._r_new_id and self._r_new_id[file_id] is not None: |
|
149 |
return self._r_new_id[file_id] |
|
150 |
elif file_id in self._tree.inventory: |
|
151 |
return self.get_id_tree(file_id) |
|
152 |
else: |
|
153 |
trans_id = self._assign_id() |
|
154 |
self.version_file(file_id, trans_id) |
|
155 |
return trans_id |
|
156 |
||
|
1534.7.12
by Aaron Bentley
Added canonical_path function |
157 |
def canonical_path(self, path): |
158 |
"""Get the canonical tree-relative path""" |
|
159 |
# don't follow final symlinks
|
|
160 |
dirname, basename = os.path.split(self._tree.abspath(path)) |
|
161 |
dirname = os.path.realpath(dirname) |
|
162 |
return self._tree.relpath(os.path.join(dirname, basename)) |
|
163 |
||
|
1534.7.7
by Aaron Bentley
Added support for all-file path ids |
164 |
def get_tree_path_id(self, path): |
165 |
"""Determine (and maybe set) the transaction ID for a tree path.""" |
|
|
1534.7.12
by Aaron Bentley
Added canonical_path function |
166 |
path = self.canonical_path(path) |
|
1534.7.7
by Aaron Bentley
Added support for all-file path ids |
167 |
if path not in self._tree_path_ids: |
168 |
self._tree_path_ids[path] = self._assign_id() |
|
|
1534.7.8
by Aaron Bentley
Added TreeTransform.final_kind |
169 |
self._tree_id_paths[self._tree_path_ids[path]] = path |
|
1534.7.7
by Aaron Bentley
Added support for all-file path ids |
170 |
return self._tree_path_ids[path] |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
171 |
|
|
1534.7.16
by Aaron Bentley
Added get_tree_parent |
172 |
def get_tree_parent(self, trans_id): |
|
1534.7.31
by Aaron Bentley
Changed tree root parent to ROOT_PARENT |
173 |
"""Determine id of the parent in the tree.""" |
|
1534.7.16
by Aaron Bentley
Added get_tree_parent |
174 |
path = self._tree_id_paths[trans_id] |
175 |
if path == "": |
|
|
1534.7.31
by Aaron Bentley
Changed tree root parent to ROOT_PARENT |
176 |
return ROOT_PARENT |
|
1534.7.16
by Aaron Bentley
Added get_tree_parent |
177 |
return self.get_tree_path_id(os.path.dirname(path)) |
178 |
||
|
1534.7.117
by Aaron Bentley
Simplified permission handling of existing files in transform. |
179 |
def create_file(self, contents, trans_id, mode_id=None): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
180 |
"""Schedule creation of a new file. |
181 |
||
182 |
See also new_file.
|
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
183 |
|
184 |
Contents is an iterator of strings, all of which will be written
|
|
|
1534.7.21
by Aaron Bentley
Updated docstrings |
185 |
to the target destination.
|
|
1534.7.117
by Aaron Bentley
Simplified permission handling of existing files in transform. |
186 |
|
187 |
New file takes the permissions of any existing file with that id,
|
|
188 |
unless mode_id is specified.
|
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
189 |
"""
|
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
190 |
f = file(self._limbo_name(trans_id), 'wb') |
|
1534.8.1
by Aaron Bentley
Reference files in limbo before their creation is finished, for finalize. |
191 |
unique_add(self._new_contents, trans_id, 'file') |
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
192 |
for segment in contents: |
193 |
f.write(segment) |
|
194 |
f.close() |
|
|
1534.7.117
by Aaron Bentley
Simplified permission handling of existing files in transform. |
195 |
self._set_mode(trans_id, mode_id, S_ISREG) |
196 |
||
197 |
def _set_mode(self, trans_id, mode_id, typefunc): |
|
198 |
if mode_id is None: |
|
199 |
mode_id = trans_id |
|
200 |
try: |
|
201 |
old_path = self._tree_id_paths[mode_id] |
|
202 |
except KeyError: |
|
203 |
return
|
|
204 |
try: |
|
205 |
mode = os.stat(old_path).st_mode |
|
206 |
except OSError, e: |
|
207 |
if e.errno == errno.ENOENT: |
|
208 |
return
|
|
209 |
else: |
|
210 |
raise
|
|
211 |
if typefunc(mode): |
|
212 |
os.chmod(self._limbo_name(trans_id), mode) |
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
213 |
|
|
1534.7.20
by Aaron Bentley
Added directory handling |
214 |
def create_directory(self, trans_id): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
215 |
"""Schedule creation of a new directory. |
216 |
|
|
217 |
See also new_directory.
|
|
218 |
"""
|
|
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
219 |
os.mkdir(self._limbo_name(trans_id)) |
220 |
unique_add(self._new_contents, trans_id, 'directory') |
|
|
1534.7.20
by Aaron Bentley
Added directory handling |
221 |
|
|
1534.7.22
by Aaron Bentley
Added symlink support |
222 |
def create_symlink(self, target, trans_id): |
223 |
"""Schedule creation of a new symbolic link. |
|
224 |
||
225 |
target is a bytestring.
|
|
226 |
See also new_symlink.
|
|
227 |
"""
|
|
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
228 |
os.symlink(target, self._limbo_name(trans_id)) |
229 |
unique_add(self._new_contents, trans_id, 'symlink') |
|
|
1534.7.22
by Aaron Bentley
Added symlink support |
230 |
|
|
1534.7.129
by Aaron Bentley
Converted test cases to Tree Transform |
231 |
@staticmethod
|
232 |
def delete_any(full_path): |
|
233 |
try: |
|
234 |
os.unlink(full_path) |
|
235 |
except OSError, e: |
|
236 |
# We may be renaming a dangling inventory id
|
|
237 |
if e.errno != errno.EISDIR and e.errno != errno.EACCES: |
|
238 |
raise
|
|
239 |
os.rmdir(full_path) |
|
240 |
||
241 |
def cancel_creation(self, trans_id): |
|
242 |
del self._new_contents[trans_id] |
|
243 |
self.delete_any(self._limbo_name(trans_id)) |
|
244 |
||
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
245 |
def delete_contents(self, trans_id): |
246 |
"""Schedule the contents of a path entry for deletion""" |
|
|
1534.7.130
by Aaron Bentley
More conflict handling, test porting |
247 |
self.tree_kind(trans_id) |
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
248 |
self._removed_contents.add(trans_id) |
249 |
||
|
1534.7.61
by Aaron Bentley
Handled parent loops, missing parents, unversioned parents |
250 |
def cancel_deletion(self, trans_id): |
251 |
"""Cancel a scheduled deletion""" |
|
252 |
self._removed_contents.remove(trans_id) |
|
253 |
||
|
1534.7.39
by Aaron Bentley
Ensured that files can be unversioned (de-versioned?) |
254 |
def unversion_file(self, trans_id): |
255 |
"""Schedule a path entry to become unversioned""" |
|
256 |
self._removed_id.add(trans_id) |
|
257 |
||
258 |
def delete_versioned(self, trans_id): |
|
259 |
"""Delete and unversion a versioned file""" |
|
260 |
self.delete_contents(trans_id) |
|
261 |
self.unversion_file(trans_id) |
|
262 |
||
|
1534.7.25
by Aaron Bentley
Added set_executability |
263 |
def set_executability(self, executability, trans_id): |
264 |
"""Schedule setting of the 'execute' bit""" |
|
|
1534.7.26
by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries |
265 |
if executability is None: |
266 |
del self._new_executability[trans_id] |
|
267 |
else: |
|
268 |
unique_add(self._new_executability, trans_id, executability) |
|
|
1534.7.25
by Aaron Bentley
Added set_executability |
269 |
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
270 |
def version_file(self, file_id, trans_id): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
271 |
"""Schedule a file to become versioned.""" |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
272 |
unique_add(self._new_id, trans_id, file_id) |
|
1534.7.75
by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id |
273 |
unique_add(self._r_new_id, file_id, trans_id) |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
274 |
|
|
1534.7.105
by Aaron Bentley
Got merge with rename working |
275 |
def cancel_versioning(self, trans_id): |
276 |
"""Undo a previous versioning of a file""" |
|
277 |
file_id = self._new_id[trans_id] |
|
278 |
del self._new_id[trans_id] |
|
279 |
del self._r_new_id[file_id] |
|
280 |
||
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
281 |
def new_paths(self): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
282 |
"""Determine the paths of all new and changed files""" |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
283 |
new_ids = set() |
|
1534.7.33
by Aaron Bentley
Fixed naming |
284 |
fp = FinalPaths(self._new_root, self) |
|
1534.7.4
by Aaron Bentley
Unified all file types as 'contents' |
285 |
for id_set in (self._new_name, self._new_parent, self._new_contents, |
|
1534.7.25
by Aaron Bentley
Added set_executability |
286 |
self._new_id, self._new_executability): |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
287 |
new_ids.update(id_set) |
288 |
new_paths = [(fp.get_path(t), t) for t in new_ids] |
|
289 |
new_paths.sort() |
|
290 |
return new_paths |
|
|
1534.7.6
by Aaron Bentley
Added conflict handling |
291 |
|
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
292 |
def tree_kind(self, trans_id): |
|
1534.7.40
by Aaron Bentley
Updated docs |
293 |
"""Determine the file kind in the working tree. |
294 |
||
295 |
Raises NoSuchFile if the file does not exist
|
|
296 |
"""
|
|
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
297 |
path = self._tree_id_paths.get(trans_id) |
298 |
if path is None: |
|
299 |
raise NoSuchFile(None) |
|
300 |
try: |
|
301 |
return file_kind(self._tree.abspath(path)) |
|
302 |
except OSError, e: |
|
303 |
if e.errno != errno.ENOENT: |
|
304 |
raise
|
|
305 |
else: |
|
306 |
raise NoSuchFile(path) |
|
307 |
||
|
1534.7.8
by Aaron Bentley
Added TreeTransform.final_kind |
308 |
def final_kind(self, trans_id): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
309 |
"""\ |
310 |
Determine the final file kind, after any changes applied.
|
|
|
1534.7.8
by Aaron Bentley
Added TreeTransform.final_kind |
311 |
|
312 |
Raises NoSuchFile if the file does not exist/has no contents.
|
|
313 |
(It is conceivable that a path would be created without the
|
|
314 |
corresponding contents insertion command)
|
|
315 |
"""
|
|
316 |
if trans_id in self._new_contents: |
|
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
317 |
return self._new_contents[trans_id] |
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
318 |
elif trans_id in self._removed_contents: |
319 |
raise NoSuchFile(None) |
|
|
1534.7.8
by Aaron Bentley
Added TreeTransform.final_kind |
320 |
else: |
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
321 |
return self.tree_kind(trans_id) |
|
1534.7.8
by Aaron Bentley
Added TreeTransform.final_kind |
322 |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
323 |
def get_tree_file_id(self, trans_id): |
324 |
"""Determine the file id associated with the trans_id in the tree""" |
|
325 |
try: |
|
326 |
path = self._tree_id_paths[trans_id] |
|
327 |
except KeyError: |
|
328 |
# the file is a new, unversioned file, or invalid trans_id
|
|
329 |
return None |
|
330 |
# the file is old; the old id is still valid
|
|
|
1534.7.68
by Aaron Bentley
Got semi-reasonable root directory renaming working |
331 |
if self._new_root == trans_id: |
332 |
return self._tree.inventory.root.file_id |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
333 |
return self._tree.path2id(path) |
334 |
||
|
1534.7.13
by Aaron Bentley
Implemented final_file_id |
335 |
def final_file_id(self, trans_id): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
336 |
"""\ |
337 |
Determine the file id after any changes are applied, or None.
|
|
338 |
|
|
339 |
None indicates that the file will not be versioned after changes are
|
|
340 |
applied.
|
|
341 |
"""
|
|
|
1534.7.13
by Aaron Bentley
Implemented final_file_id |
342 |
try: |
343 |
# there is a new id for this file
|
|
344 |
return self._new_id[trans_id] |
|
345 |
except KeyError: |
|
|
1534.7.39
by Aaron Bentley
Ensured that files can be unversioned (de-versioned?) |
346 |
if trans_id in self._removed_id: |
347 |
return None |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
348 |
return self.get_tree_file_id(trans_id) |
|
1534.7.13
by Aaron Bentley
Implemented final_file_id |
349 |
|
|
1534.7.17
by Aaron Bentley
Added final_parent function |
350 |
def final_parent(self, trans_id): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
351 |
"""\ |
352 |
Determine the parent file_id, after any changes are applied.
|
|
353 |
||
|
1534.7.31
by Aaron Bentley
Changed tree root parent to ROOT_PARENT |
354 |
ROOT_PARENT is returned for the tree root.
|
|
1534.7.21
by Aaron Bentley
Updated docstrings |
355 |
"""
|
|
1534.7.17
by Aaron Bentley
Added final_parent function |
356 |
try: |
357 |
return self._new_parent[trans_id] |
|
358 |
except KeyError: |
|
359 |
return self.get_tree_parent(trans_id) |
|
360 |
||
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
361 |
def final_name(self, trans_id): |
|
1534.7.40
by Aaron Bentley
Updated docs |
362 |
"""Determine the final filename, after all changes are applied.""" |
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
363 |
try: |
364 |
return self._new_name[trans_id] |
|
365 |
except KeyError: |
|
366 |
return os.path.basename(self._tree_id_paths[trans_id]) |
|
367 |
||
368 |
def _by_parent(self): |
|
|
1534.7.40
by Aaron Bentley
Updated docs |
369 |
"""Return a map of parent: children for known parents. |
370 |
|
|
371 |
Only new paths and parents of tree files with assigned ids are used.
|
|
372 |
"""
|
|
|
1534.7.6
by Aaron Bentley
Added conflict handling |
373 |
by_parent = {} |
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
374 |
items = list(self._new_parent.iteritems()) |
|
1534.7.76
by Aaron Bentley
Fixed final_parent, for the case where finding a parent adds tree id paths. |
375 |
items.extend((t, self.final_parent(t)) for t in |
376 |
self._tree_id_paths.keys()) |
|
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
377 |
for trans_id, parent_id in items: |
|
1534.7.6
by Aaron Bentley
Added conflict handling |
378 |
if parent_id not in by_parent: |
379 |
by_parent[parent_id] = set() |
|
380 |
by_parent[parent_id].add(trans_id) |
|
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
381 |
return by_parent |
|
1534.7.11
by Aaron Bentley
Refactored conflict handling |
382 |
|
|
1534.7.57
by Aaron Bentley
Enhanced conflict resolution. |
383 |
def path_changed(self, trans_id): |
384 |
return trans_id in self._new_name or trans_id in self._new_parent |
|
385 |
||
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
386 |
def find_conflicts(self): |
|
1534.7.40
by Aaron Bentley
Updated docs |
387 |
"""Find any violations of inventory or filesystem invariants""" |
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
388 |
if self.__done is True: |
389 |
raise ReusingTransform() |
|
390 |
conflicts = [] |
|
391 |
# ensure all children of all existent parents are known
|
|
392 |
# all children of non-existent parents are known, by definition.
|
|
393 |
self._add_tree_children() |
|
394 |
by_parent = self._by_parent() |
|
|
1534.7.15
by Aaron Bentley
Add conflict types related to versioning |
395 |
conflicts.extend(self._unversioned_parents(by_parent)) |
|
1534.7.19
by Aaron Bentley
Added tests for parent loops |
396 |
conflicts.extend(self._parent_loops()) |
|
1534.7.11
by Aaron Bentley
Refactored conflict handling |
397 |
conflicts.extend(self._duplicate_entries(by_parent)) |
|
1534.7.50
by Aaron Bentley
Detect duplicate inventory ids |
398 |
conflicts.extend(self._duplicate_ids()) |
|
1534.7.11
by Aaron Bentley
Refactored conflict handling |
399 |
conflicts.extend(self._parent_type_conflicts(by_parent)) |
|
1534.7.15
by Aaron Bentley
Add conflict types related to versioning |
400 |
conflicts.extend(self._improper_versioning()) |
|
1534.7.26
by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries |
401 |
conflicts.extend(self._executability_conflicts()) |
|
1534.7.15
by Aaron Bentley
Add conflict types related to versioning |
402 |
return conflicts |
403 |
||
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
404 |
def _add_tree_children(self): |
|
1534.7.40
by Aaron Bentley
Updated docs |
405 |
"""\ |
406 |
Add all the children of all active parents to the known paths.
|
|
407 |
||
408 |
Active parents are those which gain children, and those which are
|
|
409 |
removed. This is a necessary first step in detecting conflicts.
|
|
410 |
"""
|
|
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
411 |
parents = self._by_parent().keys() |
412 |
parents.extend([t for t in self._removed_contents if |
|
413 |
self.tree_kind(t) == 'directory']) |
|
|
1534.7.50
by Aaron Bentley
Detect duplicate inventory ids |
414 |
for trans_id in self._removed_id: |
415 |
file_id = self.get_tree_file_id(trans_id) |
|
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
416 |
if self._tree.inventory[file_id].kind in ('directory', |
417 |
'root_directory'): |
|
|
1534.7.50
by Aaron Bentley
Detect duplicate inventory ids |
418 |
parents.append(trans_id) |
419 |
||
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
420 |
for parent_id in parents: |
|
1534.7.67
by Aaron Bentley
Refactored _add_tree_children |
421 |
# ensure that all children are registered with the transaction
|
422 |
list(self.iter_tree_children(parent_id)) |
|
423 |
||
424 |
def iter_tree_children(self, parent_id): |
|
425 |
"""Iterate through the entry's tree children, if any""" |
|
426 |
try: |
|
427 |
path = self._tree_id_paths[parent_id] |
|
428 |
except KeyError: |
|
429 |
return
|
|
430 |
try: |
|
431 |
children = os.listdir(self._tree.abspath(path)) |
|
432 |
except OSError, e: |
|
|
1534.7.71
by abentley
All tests pass under Windows |
433 |
if e.errno != errno.ENOENT and e.errno != errno.ESRCH: |
|
1534.7.67
by Aaron Bentley
Refactored _add_tree_children |
434 |
raise
|
435 |
return
|
|
436 |
||
437 |
for child in children: |
|
438 |
childpath = joinpath(path, child) |
|
439 |
if childpath == BZRDIR: |
|
440 |
continue
|
|
441 |
yield self.get_tree_path_id(childpath) |
|
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
442 |
|
|
1534.7.19
by Aaron Bentley
Added tests for parent loops |
443 |
def _parent_loops(self): |
444 |
"""No entry should be its own ancestor""" |
|
445 |
conflicts = [] |
|
446 |
for trans_id in self._new_parent: |
|
447 |
seen = set() |
|
448 |
parent_id = trans_id |
|
|
1534.7.31
by Aaron Bentley
Changed tree root parent to ROOT_PARENT |
449 |
while parent_id is not ROOT_PARENT: |
|
1534.7.19
by Aaron Bentley
Added tests for parent loops |
450 |
seen.add(parent_id) |
451 |
parent_id = self.final_parent(parent_id) |
|
452 |
if parent_id == trans_id: |
|
453 |
conflicts.append(('parent loop', trans_id)) |
|
454 |
if parent_id in seen: |
|
455 |
break
|
|
456 |
return conflicts |
|
457 |
||
|
1534.7.15
by Aaron Bentley
Add conflict types related to versioning |
458 |
def _unversioned_parents(self, by_parent): |
459 |
"""If parent directories are versioned, children must be versioned.""" |
|
460 |
conflicts = [] |
|
461 |
for parent_id, children in by_parent.iteritems(): |
|
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
462 |
if parent_id is ROOT_PARENT: |
463 |
continue
|
|
|
1534.7.15
by Aaron Bentley
Add conflict types related to versioning |
464 |
if self.final_file_id(parent_id) is not None: |
465 |
continue
|
|
466 |
for child_id in children: |
|
467 |
if self.final_file_id(child_id) is not None: |
|
468 |
conflicts.append(('unversioned parent', parent_id)) |
|
469 |
break; |
|
470 |
return conflicts |
|
471 |
||
472 |
def _improper_versioning(self): |
|
|
1534.7.21
by Aaron Bentley
Updated docstrings |
473 |
"""\ |
474 |
Cannot version a file with no contents, or a bad type.
|
|
|
1534.7.15
by Aaron Bentley
Add conflict types related to versioning |
475 |
|
476 |
However, existing entries with no contents are okay.
|
|
477 |
"""
|
|
478 |
conflicts = [] |
|
479 |
for trans_id in self._new_id.iterkeys(): |
|
480 |
try: |
|
481 |
kind = self.final_kind(trans_id) |
|
482 |
except NoSuchFile: |
|
483 |
conflicts.append(('versioning no contents', trans_id)) |
|
484 |
continue
|
|
485 |
if not InventoryEntry.versionable_kind(kind): |
|
|
1534.7.20
by Aaron Bentley
Added directory handling |
486 |
conflicts.append(('versioning bad kind', trans_id, kind)) |
|
1534.7.11
by Aaron Bentley
Refactored conflict handling |
487 |
return conflicts |
488 |
||
|
1534.7.26
by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries |
489 |
def _executability_conflicts(self): |
|
1534.7.40
by Aaron Bentley
Updated docs |
490 |
"""Check for bad executability changes. |
491 |
|
|
492 |
Only versioned files may have their executability set, because
|
|
493 |
1. only versioned entries can have executability under windows
|
|
494 |
2. only files can be executable. (The execute bit on a directory
|
|
495 |
does not indicate searchability)
|
|
496 |
"""
|
|
|
1534.7.26
by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries |
497 |
conflicts = [] |
498 |
for trans_id in self._new_executability: |
|
499 |
if self.final_file_id(trans_id) is None: |
|
500 |
conflicts.append(('unversioned executability', trans_id)) |
|
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
501 |
else: |
502 |
try: |
|
503 |
non_file = self.final_kind(trans_id) != "file" |
|
504 |
except NoSuchFile: |
|
505 |
non_file = True |
|
506 |
if non_file is True: |
|
507 |
conflicts.append(('non-file executability', trans_id)) |
|
|
1534.7.26
by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries |
508 |
return conflicts |
509 |
||
|
1534.7.11
by Aaron Bentley
Refactored conflict handling |
510 |
def _duplicate_entries(self, by_parent): |
511 |
"""No directory may have two entries with the same name.""" |
|
512 |
conflicts = [] |
|
|
1534.7.6
by Aaron Bentley
Added conflict handling |
513 |
for children in by_parent.itervalues(): |
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
514 |
name_ids = [(self.final_name(t), t) for t in children] |
|
1534.7.6
by Aaron Bentley
Added conflict handling |
515 |
name_ids.sort() |
516 |
last_name = None |
|
517 |
last_trans_id = None |
|
518 |
for name, trans_id in name_ids: |
|
519 |
if name == last_name: |
|
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
520 |
conflicts.append(('duplicate', last_trans_id, trans_id, |
521 |
name)) |
|
|
1534.7.6
by Aaron Bentley
Added conflict handling |
522 |
last_name = name |
523 |
last_trans_id = trans_id |
|
|
1534.7.11
by Aaron Bentley
Refactored conflict handling |
524 |
return conflicts |
525 |
||
|
1534.7.50
by Aaron Bentley
Detect duplicate inventory ids |
526 |
def _duplicate_ids(self): |
527 |
"""Each inventory id may only be used once""" |
|
528 |
conflicts = [] |
|
529 |
removed_tree_ids = set((self.get_tree_file_id(trans_id) for trans_id in |
|
530 |
self._removed_id)) |
|
531 |
active_tree_ids = set((f for f in self._tree.inventory if |
|
532 |
f not in removed_tree_ids)) |
|
533 |
for trans_id, file_id in self._new_id.iteritems(): |
|
534 |
if file_id in active_tree_ids: |
|
535 |
old_trans_id = self.get_id_tree(file_id) |
|
536 |
conflicts.append(('duplicate id', old_trans_id, trans_id)) |
|
537 |
return conflicts |
|
538 |
||
|
1534.7.11
by Aaron Bentley
Refactored conflict handling |
539 |
def _parent_type_conflicts(self, by_parent): |
540 |
"""parents must have directory 'contents'.""" |
|
541 |
conflicts = [] |
|
|
1534.7.37
by Aaron Bentley
Allowed removed dirs to have content-free children. |
542 |
for parent_id, children in by_parent.iteritems(): |
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
543 |
if parent_id is ROOT_PARENT: |
544 |
continue
|
|
|
1534.7.37
by Aaron Bentley
Allowed removed dirs to have content-free children. |
545 |
if not self._any_contents(children): |
546 |
continue
|
|
547 |
for child in children: |
|
548 |
try: |
|
549 |
self.final_kind(child) |
|
550 |
except NoSuchFile: |
|
551 |
continue
|
|
|
1534.7.10
by Aaron Bentley
Implemented missing parent and non-directory parent conflicts |
552 |
try: |
553 |
kind = self.final_kind(parent_id) |
|
554 |
except NoSuchFile: |
|
555 |
kind = None |
|
556 |
if kind is None: |
|
557 |
conflicts.append(('missing parent', parent_id)) |
|
558 |
elif kind != "directory": |
|
559 |
conflicts.append(('non-directory parent', parent_id)) |
|
|
1534.7.6
by Aaron Bentley
Added conflict handling |
560 |
return conflicts |
|
1534.7.37
by Aaron Bentley
Allowed removed dirs to have content-free children. |
561 |
|
562 |
def _any_contents(self, trans_ids): |
|
563 |
"""Return true if any of the trans_ids, will have contents.""" |
|
564 |
for trans_id in trans_ids: |
|
565 |
try: |
|
566 |
kind = self.final_kind(trans_id) |
|
567 |
except NoSuchFile: |
|
568 |
continue
|
|
569 |
return True |
|
570 |
return False |
|
|
1534.7.6
by Aaron Bentley
Added conflict handling |
571 |
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
572 |
def apply(self): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
573 |
"""\ |
574 |
Apply all changes to the inventory and filesystem.
|
|
575 |
|
|
576 |
If filesystem or inventory conflicts are present, MalformedTransform
|
|
577 |
will be thrown.
|
|
578 |
"""
|
|
|
1534.7.49
by Aaron Bentley
Printed conflicts in MalformedTransform |
579 |
conflicts = self.find_conflicts() |
580 |
if len(conflicts) != 0: |
|
581 |
raise MalformedTransform(conflicts=conflicts) |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
582 |
limbo_inv = {} |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
583 |
inv = self._tree.inventory |
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
584 |
self._apply_removals(inv, limbo_inv) |
585 |
self._apply_insertions(inv, limbo_inv) |
|
|
1534.7.35
by Aaron Bentley
Got file renaming working |
586 |
self._tree._write_inventory(inv) |
587 |
self.__done = True |
|
|
1534.7.59
by Aaron Bentley
Simplified tests |
588 |
self.finalize() |
|
1534.7.35
by Aaron Bentley
Got file renaming working |
589 |
|
|
1534.7.72
by Aaron Bentley
Moved new content generation to pre-renames |
590 |
def _limbo_name(self, trans_id): |
591 |
"""Generate the limbo name of a file""" |
|
|
1534.7.118
by Aaron Bentley
Dirty merge of the mainline |
592 |
return os.path.join(self._limbodir, trans_id) |
|
1534.7.72
by Aaron Bentley
Moved new content generation to pre-renames |
593 |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
594 |
def _apply_removals(self, inv, limbo_inv): |
|
1534.7.36
by Aaron Bentley
Added rename tests |
595 |
"""Perform tree operations that remove directory/inventory names. |
596 |
|
|
597 |
That is, delete files that are to be deleted, and put any files that
|
|
598 |
need renaming into limbo. This must be done in strict child-to-parent
|
|
599 |
order.
|
|
600 |
"""
|
|
|
1534.7.35
by Aaron Bentley
Got file renaming working |
601 |
tree_paths = list(self._tree_path_ids.iteritems()) |
602 |
tree_paths.sort(reverse=True) |
|
603 |
for path, trans_id in tree_paths: |
|
|
1534.7.43
by abentley
Fixed some Windows bugs, introduced a conflicts bug |
604 |
full_path = self._tree.abspath(path) |
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
605 |
if trans_id in self._removed_contents: |
|
1534.7.129
by Aaron Bentley
Converted test cases to Tree Transform |
606 |
self.delete_any(full_path) |
|
1534.7.35
by Aaron Bentley
Got file renaming working |
607 |
elif trans_id in self._new_name or trans_id in self._new_parent: |
|
1534.7.48
by Aaron Bentley
Ensured we can move/rename dangling inventory entries |
608 |
try: |
|
1534.7.118
by Aaron Bentley
Dirty merge of the mainline |
609 |
os.rename(full_path, self._limbo_name(trans_id)) |
|
1534.7.48
by Aaron Bentley
Ensured we can move/rename dangling inventory entries |
610 |
except OSError, e: |
611 |
if e.errno != errno.ENOENT: |
|
612 |
raise
|
|
|
1534.7.39
by Aaron Bentley
Ensured that files can be unversioned (de-versioned?) |
613 |
if trans_id in self._removed_id: |
|
1534.7.69
by Aaron Bentley
Got real root moves working |
614 |
if trans_id == self._new_root: |
615 |
file_id = self._tree.inventory.root.file_id |
|
616 |
else: |
|
617 |
file_id = self.get_tree_file_id(trans_id) |
|
618 |
del inv[file_id] |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
619 |
elif trans_id in self._new_name or trans_id in self._new_parent: |
620 |
file_id = self.get_tree_file_id(trans_id) |
|
|
1534.7.57
by Aaron Bentley
Enhanced conflict resolution. |
621 |
if file_id is not None: |
622 |
limbo_inv[trans_id] = inv[file_id] |
|
623 |
del inv[file_id] |
|
|
1534.7.34
by Aaron Bentley
Proper conflicts for removals |
624 |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
625 |
def _apply_insertions(self, inv, limbo_inv): |
|
1534.7.36
by Aaron Bentley
Added rename tests |
626 |
"""Perform tree operations that insert directory/inventory names. |
627 |
|
|
628 |
That is, create any files that need to be created, and restore from
|
|
629 |
limbo any files that needed renaming. This must be done in strict
|
|
630 |
parent-to-child order.
|
|
631 |
"""
|
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
632 |
for path, trans_id in self.new_paths(): |
|
1534.7.4
by Aaron Bentley
Unified all file types as 'contents' |
633 |
try: |
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
634 |
kind = self._new_contents[trans_id] |
|
1534.7.4
by Aaron Bentley
Unified all file types as 'contents' |
635 |
except KeyError: |
636 |
kind = contents = None |
|
|
1534.7.72
by Aaron Bentley
Moved new content generation to pre-renames |
637 |
if trans_id in self._new_contents or self.path_changed(trans_id): |
|
1534.7.48
by Aaron Bentley
Ensured we can move/rename dangling inventory entries |
638 |
full_path = self._tree.abspath(path) |
639 |
try: |
|
|
1534.7.72
by Aaron Bentley
Moved new content generation to pre-renames |
640 |
os.rename(self._limbo_name(trans_id), full_path) |
|
1534.7.48
by Aaron Bentley
Ensured we can move/rename dangling inventory entries |
641 |
except OSError, e: |
642 |
# We may be renaming a dangling inventory id
|
|
643 |
if e.errno != errno.ENOENT: |
|
644 |
raise
|
|
|
1534.7.73
by Aaron Bentley
Changed model again. Now iterator is used immediately. |
645 |
if trans_id in self._new_contents: |
646 |
del self._new_contents[trans_id] |
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
647 |
|
648 |
if trans_id in self._new_id: |
|
649 |
if kind is None: |
|
|
1534.7.14
by Aaron Bentley
Fixed file_kind call |
650 |
kind = file_kind(self._tree.abspath(path)) |
|
1534.7.69
by Aaron Bentley
Got real root moves working |
651 |
try: |
652 |
inv.add_path(path, kind, self._new_id[trans_id]) |
|
653 |
except: |
|
654 |
raise repr((path, kind, self._new_id[trans_id])) |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
655 |
elif trans_id in self._new_name or trans_id in self._new_parent: |
|
1534.7.56
by Aaron Bentley
Implemented the backup file detritus |
656 |
entry = limbo_inv.get(trans_id) |
657 |
if entry is not None: |
|
658 |
entry.name = self.final_name(trans_id) |
|
659 |
parent_trans_id = self.final_parent(trans_id) |
|
660 |
entry.parent_id = self.final_file_id(parent_trans_id) |
|
661 |
inv.add(entry) |
|
|
1534.7.41
by Aaron Bentley
Got inventory ID movement working |
662 |
|
|
1534.7.25
by Aaron Bentley
Added set_executability |
663 |
# requires files and inventory entries to be in place
|
664 |
if trans_id in self._new_executability: |
|
665 |
self._set_executability(path, inv, trans_id) |
|
|
1534.7.40
by Aaron Bentley
Updated docs |
666 |
|
|
1534.7.25
by Aaron Bentley
Added set_executability |
667 |
def _set_executability(self, path, inv, trans_id): |
|
1534.7.40
by Aaron Bentley
Updated docs |
668 |
"""Set the executability of versioned files """ |
|
1534.7.25
by Aaron Bentley
Added set_executability |
669 |
file_id = inv.path2id(path) |
670 |
new_executability = self._new_executability[trans_id] |
|
671 |
inv[file_id].executable = new_executability |
|
672 |
if supports_executable(): |
|
673 |
abspath = self._tree.abspath(path) |
|
674 |
current_mode = os.stat(abspath).st_mode |
|
675 |
if new_executability: |
|
676 |
umask = os.umask(0) |
|
677 |
os.umask(umask) |
|
678 |
to_mode = current_mode | (0100 & ~umask) |
|
679 |
# Enable x-bit for others only if they can read it.
|
|
680 |
if current_mode & 0004: |
|
681 |
to_mode |= 0001 & ~umask |
|
682 |
if current_mode & 0040: |
|
683 |
to_mode |= 0010 & ~umask |
|
684 |
else: |
|
685 |
to_mode = current_mode & ~0111 |
|
686 |
os.chmod(abspath, to_mode) |
|
687 |
||
|
1534.7.23
by Aaron Bentley
Transform.new_entry -> Transform._new_entry |
688 |
def _new_entry(self, name, parent_id, file_id): |
|
1534.7.21
by Aaron Bentley
Updated docstrings |
689 |
"""Helper function to create a new filesystem entry.""" |
|
1534.7.2
by Aaron Bentley
Added convenience function |
690 |
trans_id = self.create_path(name, parent_id) |
691 |
if file_id is not None: |
|
692 |
self.version_file(file_id, trans_id) |
|
693 |
return trans_id |
|
694 |
||
|
1534.7.27
by Aaron Bentley
Added execute bit to new_file method |
695 |
def new_file(self, name, parent_id, contents, file_id=None, |
696 |
executable=None): |
|
|
1534.7.21
by Aaron Bentley
Updated docstrings |
697 |
"""\ |
698 |
Convenience method to create files.
|
|
699 |
|
|
700 |
name is the name of the file to create.
|
|
701 |
parent_id is the transaction id of the parent directory of the file.
|
|
702 |
contents is an iterator of bytestrings, which will be used to produce
|
|
703 |
the file.
|
|
704 |
file_id is the inventory ID of the file, if it is to be versioned.
|
|
705 |
"""
|
|
|
1534.7.23
by Aaron Bentley
Transform.new_entry -> Transform._new_entry |
706 |
trans_id = self._new_entry(name, parent_id, file_id) |
|
1534.7.20
by Aaron Bentley
Added directory handling |
707 |
self.create_file(contents, trans_id) |
|
1534.7.27
by Aaron Bentley
Added execute bit to new_file method |
708 |
if executable is not None: |
709 |
self.set_executability(executable, trans_id) |
|
|
1534.7.20
by Aaron Bentley
Added directory handling |
710 |
return trans_id |
711 |
||
712 |
def new_directory(self, name, parent_id, file_id=None): |
|
|
1534.7.21
by Aaron Bentley
Updated docstrings |
713 |
"""\ |
714 |
Convenience method to create directories.
|
|
715 |
||
716 |
name is the name of the directory to create.
|
|
717 |
parent_id is the transaction id of the parent directory of the
|
|
718 |
directory.
|
|
719 |
file_id is the inventory ID of the directory, if it is to be versioned.
|
|
720 |
"""
|
|
|
1534.7.23
by Aaron Bentley
Transform.new_entry -> Transform._new_entry |
721 |
trans_id = self._new_entry(name, parent_id, file_id) |
|
1534.7.20
by Aaron Bentley
Added directory handling |
722 |
self.create_directory(trans_id) |
723 |
return trans_id |
|
724 |
||
|
1534.7.22
by Aaron Bentley
Added symlink support |
725 |
def new_symlink(self, name, parent_id, target, file_id=None): |
726 |
"""\ |
|
727 |
Convenience method to create symbolic link.
|
|
728 |
|
|
729 |
name is the name of the symlink to create.
|
|
730 |
parent_id is the transaction id of the parent directory of the symlink.
|
|
731 |
target is a bytestring of the target of the symlink.
|
|
732 |
file_id is the inventory ID of the file, if it is to be versioned.
|
|
733 |
"""
|
|
|
1534.7.23
by Aaron Bentley
Transform.new_entry -> Transform._new_entry |
734 |
trans_id = self._new_entry(name, parent_id, file_id) |
|
1534.7.22
by Aaron Bentley
Added symlink support |
735 |
self.create_symlink(target, trans_id) |
736 |
return trans_id |
|
737 |
||
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
738 |
def joinpath(parent, child): |
|
1534.7.40
by Aaron Bentley
Updated docs |
739 |
"""Join tree-relative paths, handling the tree root specially""" |
|
1534.7.32
by Aaron Bentley
Got conflict handling working when conflicts involve existing files |
740 |
if parent is None or parent == "": |
741 |
return child |
|
742 |
else: |
|
743 |
return os.path.join(parent, child) |
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
744 |
|
745 |
class FinalPaths(object): |
|
|
1534.7.21
by Aaron Bentley
Updated docstrings |
746 |
"""\ |
747 |
Make path calculation cheap by memoizing paths.
|
|
748 |
||
749 |
The underlying tree must not be manipulated between calls, or else
|
|
750 |
the results will likely be incorrect.
|
|
751 |
"""
|
|
|
1534.7.33
by Aaron Bentley
Fixed naming |
752 |
def __init__(self, root, transform): |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
753 |
object.__init__(self) |
754 |
self.root = root |
|
755 |
self._known_paths = {} |
|
|
1534.7.33
by Aaron Bentley
Fixed naming |
756 |
self.transform = transform |
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
757 |
|
758 |
def _determine_path(self, trans_id): |
|
759 |
if trans_id == self.root: |
|
760 |
return "" |
|
|
1534.7.33
by Aaron Bentley
Fixed naming |
761 |
name = self.transform.final_name(trans_id) |
762 |
parent_id = self.transform.final_parent(trans_id) |
|
|
1534.7.1
by Aaron Bentley
Got creation of a versioned file working |
763 |
if parent_id == self.root: |
764 |
return name |
|
765 |
else: |
|
766 |
return os.path.join(self.get_path(parent_id), name) |
|
767 |
||
768 |
def get_path(self, trans_id): |
|
769 |
if trans_id not in self._known_paths: |
|
770 |
self._known_paths[trans_id] = self._determine_path(trans_id) |
|
771 |
return self._known_paths[trans_id] |
|
|
1534.7.28
by Aaron Bentley
Nearly-working build_tree replacement |
772 |
|
|
1534.7.30
by Aaron Bentley
Factored out topological id sorting |
773 |
def topology_sorted_ids(tree): |
|
1534.7.40
by Aaron Bentley
Updated docs |
774 |
"""Determine the topological order of the ids in a tree""" |
|
1534.7.30
by Aaron Bentley
Factored out topological id sorting |
775 |
file_ids = list(tree) |
776 |
file_ids.sort(key=tree.id2path) |
|
777 |
return file_ids |
|
|
1534.7.28
by Aaron Bentley
Nearly-working build_tree replacement |
778 |
|
779 |
def build_tree(branch, tree): |
|
|
1534.7.40
by Aaron Bentley
Updated docs |
780 |
"""Create working tree for a branch, using a Transaction.""" |
|
1534.7.28
by Aaron Bentley
Nearly-working build_tree replacement |
781 |
file_trans_id = {} |
782 |
wt = branch.working_tree() |
|
783 |
tt = TreeTransform(wt) |
|
784 |
try: |
|
785 |
file_trans_id[wt.get_root_id()] = tt.get_id_tree(wt.get_root_id()) |
|
|
1534.7.30
by Aaron Bentley
Factored out topological id sorting |
786 |
file_ids = topology_sorted_ids(tree) |
|
1534.7.29
by Aaron Bentley
Got build passing all tests |
787 |
for file_id in file_ids: |
|
1534.7.28
by Aaron Bentley
Nearly-working build_tree replacement |
788 |
entry = tree.inventory[file_id] |
789 |
if entry.parent_id is None: |
|
790 |
continue
|
|
791 |
if entry.parent_id not in file_trans_id: |
|
792 |
raise repr(entry.parent_id) |
|
793 |
parent_id = file_trans_id[entry.parent_id] |
|
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
794 |
file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree) |
795 |
tt.apply() |
|
796 |
finally: |
|
797 |
tt.finalize() |
|
798 |
||
799 |
def new_by_entry(tt, entry, parent_id, tree): |
|
800 |
name = entry.name |
|
801 |
kind = entry.kind |
|
802 |
if kind == 'file': |
|
|
1534.7.79
by Aaron Bentley
Stopped calling get_file_lines on WorkingTree |
803 |
contents = tree.get_file(entry.file_id).readlines() |
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
804 |
executable = tree.is_executable(entry.file_id) |
805 |
return tt.new_file(name, parent_id, contents, entry.file_id, |
|
806 |
executable) |
|
807 |
elif kind == 'directory': |
|
|
1534.7.54
by Aaron Bentley
Fixed thinko |
808 |
return tt.new_directory(name, parent_id, entry.file_id) |
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
809 |
elif kind == 'symlink': |
810 |
target = entry.get_symlink_target(file_id) |
|
811 |
return tt.new_symlink(name, parent_id, target, file_id) |
|
812 |
||
|
1534.7.117
by Aaron Bentley
Simplified permission handling of existing files in transform. |
813 |
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None): |
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
814 |
if entry.kind == "file": |
|
1534.7.97
by Aaron Bentley
Ensured foo.BASE is a directory if there's a conflict |
815 |
if lines == None: |
816 |
lines = tree.get_file(entry.file_id).readlines() |
|
|
1534.7.117
by Aaron Bentley
Simplified permission handling of existing files in transform. |
817 |
tt.create_file(lines, trans_id, mode_id=mode_id) |
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
818 |
elif entry.kind == "symlink": |
|
1534.7.101
by Aaron Bentley
Got conflicts on symlinks working properly |
819 |
tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id) |
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
820 |
elif entry.kind == "directory": |
|
1534.7.51
by Aaron Bentley
New approach to revert |
821 |
tt.create_directory(trans_id) |
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
822 |
|
|
1534.7.89
by Aaron Bentley
Handle all content types in three-way |
823 |
def create_entry_executability(tt, entry, trans_id): |
824 |
if entry.kind == "file": |
|
825 |
tt.set_executability(entry.executable, trans_id) |
|
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
826 |
|
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
827 |
def find_interesting(working_tree, target_tree, filenames): |
828 |
if not filenames: |
|
829 |
interesting_ids = None |
|
830 |
else: |
|
831 |
interesting_ids = set() |
|
|
1534.7.118
by Aaron Bentley
Dirty merge of the mainline |
832 |
for tree_path in filenames: |
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
833 |
for tree in (working_tree, target_tree): |
834 |
not_found = True |
|
835 |
file_id = tree.inventory.path2id(tree_path) |
|
836 |
if file_id is not None: |
|
837 |
interesting_ids.add(file_id) |
|
838 |
not_found = False |
|
839 |
if not_found: |
|
|
1534.7.123
by Aaron Bentley
Fixed handling of unversioned files |
840 |
raise NotVersionedError(path=tree_path) |
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
841 |
return interesting_ids |
842 |
||
843 |
||
|
1534.7.56
by Aaron Bentley
Implemented the backup file detritus |
844 |
def change_entry(tt, file_id, working_tree, target_tree, |
845 |
get_trans_id, backups, trans_id): |
|
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
846 |
e_trans_id = get_trans_id(file_id) |
847 |
entry = target_tree.inventory[file_id] |
|
848 |
has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, |
|
849 |
working_tree) |
|
850 |
if contents_mod: |
|
|
1534.7.117
by Aaron Bentley
Simplified permission handling of existing files in transform. |
851 |
mode_id = e_trans_id |
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
852 |
if has_contents: |
|
1534.7.56
by Aaron Bentley
Implemented the backup file detritus |
853 |
if not backups: |
854 |
tt.delete_contents(e_trans_id) |
|
855 |
else: |
|
856 |
parent_trans_id = get_trans_id(entry.parent_id) |
|
857 |
tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_id) |
|
858 |
tt.unversion_file(e_trans_id) |
|
859 |
e_trans_id = tt.create_path(entry.name, parent_trans_id) |
|
860 |
tt.version_file(file_id, e_trans_id) |
|
861 |
trans_id[file_id] = e_trans_id |
|
|
1534.7.117
by Aaron Bentley
Simplified permission handling of existing files in transform. |
862 |
create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id) |
|
1534.7.89
by Aaron Bentley
Handle all content types in three-way |
863 |
create_entry_executability(tt, entry, e_trans_id) |
864 |
||
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
865 |
elif meta_mod: |
|
1534.7.58
by abentley
Fixed executability bug |
866 |
tt.set_executability(entry.executable, e_trans_id) |
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
867 |
if tt.final_name(e_trans_id) != entry.name: |
868 |
adjust_path = True |
|
869 |
else: |
|
870 |
parent_id = tt.final_parent(e_trans_id) |
|
871 |
parent_file_id = tt.final_file_id(parent_id) |
|
872 |
if parent_file_id != entry.parent_id: |
|
873 |
adjust_path = True |
|
874 |
else: |
|
875 |
adjust_path = False |
|
876 |
if adjust_path: |
|
|
1534.7.56
by Aaron Bentley
Implemented the backup file detritus |
877 |
parent_trans_id = get_trans_id(entry.parent_id) |
878 |
tt.adjust_path(entry.name, parent_trans_id, e_trans_id) |
|
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
879 |
|
880 |
||
881 |
def _entry_changes(file_id, entry, working_tree): |
|
882 |
"""\ |
|
883 |
Determine in which ways the inventory entry has changed.
|
|
884 |
||
885 |
Returns booleans: has_contents, content_mod, meta_mod
|
|
886 |
has_contents means there are currently contents, but they differ
|
|
887 |
contents_mod means contents need to be modified
|
|
888 |
meta_mod means the metadata needs to be modified
|
|
889 |
"""
|
|
890 |
cur_entry = working_tree.inventory[file_id] |
|
891 |
try: |
|
892 |
working_kind = working_tree.kind(file_id) |
|
893 |
has_contents = True |
|
894 |
except OSError, e: |
|
895 |
if e.errno != errno.ENOENT: |
|
896 |
raise
|
|
897 |
has_contents = False |
|
898 |
contents_mod = True |
|
899 |
meta_mod = False |
|
900 |
if has_contents is True: |
|
901 |
real_e_kind = entry.kind |
|
902 |
if real_e_kind == 'root_directory': |
|
903 |
real_e_kind = 'directory' |
|
904 |
if real_e_kind != working_kind: |
|
905 |
contents_mod, meta_mod = True, False |
|
906 |
else: |
|
907 |
cur_entry._read_tree_state(working_tree.id2path(file_id), |
|
908 |
working_tree) |
|
909 |
contents_mod, meta_mod = entry.detect_changes(cur_entry) |
|
910 |
return has_contents, contents_mod, meta_mod |
|
911 |
||
|
1534.7.56
by Aaron Bentley
Implemented the backup file detritus |
912 |
|
913 |
def revert(working_tree, target_tree, filenames, backups=False): |
|
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
914 |
interesting_ids = find_interesting(working_tree, target_tree, filenames) |
915 |
def interesting(file_id): |
|
916 |
return interesting_ids is None or file_id in interesting_ids |
|
917 |
||
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
918 |
tt = TreeTransform(working_tree) |
919 |
try: |
|
|
1534.7.51
by Aaron Bentley
New approach to revert |
920 |
trans_id = {} |
921 |
def get_trans_id(file_id): |
|
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
922 |
try: |
|
1534.7.51
by Aaron Bentley
New approach to revert |
923 |
return trans_id[file_id] |
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
924 |
except KeyError: |
|
1534.7.51
by Aaron Bentley
New approach to revert |
925 |
return tt.get_id_tree(file_id) |
926 |
||
927 |
for file_id in topology_sorted_ids(target_tree): |
|
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
928 |
if not interesting(file_id): |
|
1534.7.51
by Aaron Bentley
New approach to revert |
929 |
continue
|
|
1534.7.52
by Aaron Bentley
Revert fixes with disappearing roots |
930 |
if file_id not in working_tree.inventory: |
|
1534.7.51
by Aaron Bentley
New approach to revert |
931 |
entry = target_tree.inventory[file_id] |
932 |
parent_id = get_trans_id(entry.parent_id) |
|
933 |
e_trans_id = new_by_entry(tt, entry, parent_id, target_tree) |
|
934 |
trans_id[file_id] = e_trans_id |
|
|
1534.7.47
by Aaron Bentley
Started work on 'revert' |
935 |
else: |
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
936 |
change_entry(tt, file_id, working_tree, target_tree, |
|
1534.7.56
by Aaron Bentley
Implemented the backup file detritus |
937 |
get_trans_id, backups, trans_id) |
|
1534.7.51
by Aaron Bentley
New approach to revert |
938 |
for file_id in working_tree: |
|
1534.7.55
by Aaron Bentley
Fixed up the change detection |
939 |
if not interesting(file_id): |
940 |
continue
|
|
941 |
if file_id not in target_tree: |
|
|
1534.7.52
by Aaron Bentley
Revert fixes with disappearing roots |
942 |
tt.unversion_file(tt.get_id_tree(file_id)) |
|
1534.7.51
by Aaron Bentley
New approach to revert |
943 |
resolve_conflicts(tt) |
|
1534.7.28
by Aaron Bentley
Nearly-working build_tree replacement |
944 |
tt.apply() |
945 |
finally: |
|
946 |
tt.finalize() |
|
|
1534.7.51
by Aaron Bentley
New approach to revert |
947 |
|
|
1534.7.57
by Aaron Bentley
Enhanced conflict resolution. |
948 |
|
|
1534.7.51
by Aaron Bentley
New approach to revert |
949 |
def resolve_conflicts(tt): |
|
1534.7.57
by Aaron Bentley
Enhanced conflict resolution. |
950 |
"""Make many conflict-resolution attempts, but die if they fail""" |
951 |
for n in range(10): |
|
952 |
conflicts = tt.find_conflicts() |
|
953 |
if len(conflicts) == 0: |
|
954 |
return
|
|
955 |
conflict_pass(tt, conflicts) |
|
|
1534.7.61
by Aaron Bentley
Handled parent loops, missing parents, unversioned parents |
956 |
raise MalformedTransform(conflicts=conflicts) |
|
1534.7.57
by Aaron Bentley
Enhanced conflict resolution. |
957 |
|
958 |
||
959 |
def conflict_pass(tt, conflicts): |
|
|
1534.7.61
by Aaron Bentley
Handled parent loops, missing parents, unversioned parents |
960 |
for c_type, conflict in ((c[0], c) for c in conflicts): |
961 |
if c_type == 'duplicate id': |
|
|
1534.7.51
by Aaron Bentley
New approach to revert |
962 |
tt.unversion_file(conflict[1]) |
|
1534.7.61
by Aaron Bentley
Handled parent loops, missing parents, unversioned parents |
963 |
elif c_type == 'duplicate': |
|
1534.7.57
by Aaron Bentley
Enhanced conflict resolution. |
964 |
# files that were renamed take precedence
|
965 |
new_name = tt.final_name(conflict[1])+'.moved' |
|
966 |
final_parent = tt.final_parent(conflict[1]) |
|
967 |
if tt.path_changed(conflict[1]): |
|
968 |
tt.adjust_path(new_name, final_parent, conflict[2]) |
|
969 |
else: |
|
970 |
tt.adjust_path(new_name, final_parent, conflict[1]) |
|
|
1534.7.61
by Aaron Bentley
Handled parent loops, missing parents, unversioned parents |
971 |
elif c_type == 'parent loop': |
972 |
# break the loop by undoing one of the ops that caused the loop
|
|
973 |
cur = conflict[1] |
|
974 |
while not tt.path_changed(cur): |
|
975 |
cur = tt.final_parent(cur) |
|
976 |
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur) |
|
977 |
elif c_type == 'missing parent': |
|
|
1534.7.128
by Aaron Bentley
Got missing contents test working |
978 |
trans_id = conflict[1] |
979 |
try: |
|
980 |
tt.cancel_deletion(trans_id) |
|
981 |
except KeyError: |
|
982 |
tt.create_directory(trans_id) |
|
|
1534.7.61
by Aaron Bentley
Handled parent loops, missing parents, unversioned parents |
983 |
elif c_type == 'unversioned parent': |
984 |
tt.version_file(tt.get_tree_file_id(conflict[1]), conflict[1]) |
|
|
1534.7.74
by Aaron Bentley
Started on TreeTransform merge |
985 |
|
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
986 |
|
987 |
class Merge3Merger(object): |
|
988 |
requires_base = True |
|
989 |
supports_reprocess = True |
|
|
1534.7.107
by Aaron Bentley
Implemented show-base |
990 |
supports_show_base = True |
|
1534.7.84
by Aaron Bentley
Added reprocess support, support for varying merge types |
991 |
history_based = False |
992 |
def __init__(self, working_tree, this_tree, base_tree, other_tree, |
|
|
1534.7.107
by Aaron Bentley
Implemented show-base |
993 |
reprocess=False, show_base=False): |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
994 |
object.__init__(self) |
|
1534.7.83
by Aaron Bentley
differentiated between this tree(input) and working tree(output) in merge. |
995 |
self.this_tree = working_tree |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
996 |
self.base_tree = base_tree |
997 |
self.other_tree = other_tree |
|
998 |
self.conflicts = [] |
|
|
1534.7.87
by Aaron Bentley
Don't set executability on non-files |
999 |
self.reprocess = reprocess |
|
1534.7.107
by Aaron Bentley
Implemented show-base |
1000 |
self.show_base = show_base |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1001 |
|
1002 |
all_ids = set(base_tree) |
|
1003 |
all_ids.update(other_tree) |
|
1004 |
self.tt = TreeTransform(working_tree) |
|
1005 |
try: |
|
1006 |
for file_id in all_ids: |
|
|
1534.7.105
by Aaron Bentley
Got merge with rename working |
1007 |
self.merge_names(file_id) |
|
1534.7.89
by Aaron Bentley
Handle all content types in three-way |
1008 |
file_status = self.merge_contents(file_id) |
|
1534.7.88
by Aaron Bentley
Defer determining trans_id until we need it, because each trans_id carries a cost. |
1009 |
self.merge_executable(file_id, file_status) |
|
1534.7.85
by Aaron Bentley
Handled merging the execute bit |
1010 |
|
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1011 |
resolve_conflicts(self.tt) |
1012 |
self.tt.apply() |
|
1013 |
finally: |
|
1014 |
try: |
|
1015 |
self.tt.finalize() |
|
1016 |
except: |
|
1017 |
pass
|
|
1018 |
||
1019 |
@staticmethod
|
|
|
1534.7.74
by Aaron Bentley
Started on TreeTransform merge |
1020 |
def parent(entry, file_id): |
|
1534.7.105
by Aaron Bentley
Got merge with rename working |
1021 |
if entry is None: |
1022 |
return None |
|
1023 |
return entry.parent_id |
|
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1024 |
|
1025 |
@staticmethod
|
|
|
1534.7.74
by Aaron Bentley
Started on TreeTransform merge |
1026 |
def name(entry, file_id): |
|
1534.7.105
by Aaron Bentley
Got merge with rename working |
1027 |
if entry is None: |
1028 |
return None |
|
|
1534.7.74
by Aaron Bentley
Started on TreeTransform merge |
1029 |
return entry.name |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1030 |
|
1031 |
@staticmethod
|
|
|
1534.7.74
by Aaron Bentley
Started on TreeTransform merge |
1032 |
def contents_sha1(tree, file_id): |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1033 |
if file_id not in tree: |
1034 |
return None |
|
|
1534.7.74
by Aaron Bentley
Started on TreeTransform merge |
1035 |
return tree.get_file_sha1(file_id) |
1036 |
||
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1037 |
@staticmethod
|
|
1534.7.85
by Aaron Bentley
Handled merging the execute bit |
1038 |
def executable(tree, file_id): |
1039 |
if file_id not in tree: |
|
1040 |
return None |
|
|
1534.7.109
by Aaron Bentley
Fixed executability test, so it works with EmptyTree |
1041 |
if tree.kind(file_id) != "file": |
1042 |
return False |
|
|
1534.7.85
by Aaron Bentley
Handled merging the execute bit |
1043 |
return tree.is_executable(file_id) |
1044 |
||
1045 |
@staticmethod
|
|
|
1534.7.89
by Aaron Bentley
Handle all content types in three-way |
1046 |
def kind(tree, file_id): |
1047 |
if file_id not in tree: |
|
1048 |
return None |
|
1049 |
return tree.kind(file_id) |
|
1050 |
||
1051 |
@staticmethod
|
|
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1052 |
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key): |
1053 |
"""Do a three-way test on a scalar. |
|
1054 |
Return "this", "other" or "conflict", depending whether a value wins.
|
|
1055 |
"""
|
|
1056 |
key_base = key(base_tree, file_id) |
|
1057 |
key_other = key(other_tree, file_id) |
|
1058 |
#if base == other, either they all agree, or only THIS has changed.
|
|
1059 |
if key_base == key_other: |
|
1060 |
return "this" |
|
1061 |
key_this = key(this_tree, file_id) |
|
1062 |
if key_this not in (key_base, key_other): |
|
1063 |
return "conflict" |
|
1064 |
# "Ambiguous clean merge"
|
|
1065 |
elif key_this == key_other: |
|
1066 |
return "this" |
|
1067 |
else: |
|
1068 |
assert key_this == key_base |
|
1069 |
return "other" |
|
1070 |
||
|
1534.7.105
by Aaron Bentley
Got merge with rename working |
1071 |
def merge_names(self, file_id): |
1072 |
def get_entry(tree): |
|
1073 |
if file_id in tree.inventory: |
|
1074 |
return tree.inventory[file_id] |
|
1075 |
else: |
|
1076 |
return None |
|
1077 |
this_entry = get_entry(self.this_tree) |
|
1078 |
other_entry = get_entry(self.other_tree) |
|
1079 |
base_entry = get_entry(self.base_tree) |
|
1080 |
name_winner = self.scalar_three_way(this_entry, base_entry, |
|
1081 |
other_entry, file_id, self.name) |
|
1082 |
parent_id_winner = self.scalar_three_way(this_entry, base_entry, |
|
1083 |
other_entry, file_id, |
|
1084 |
self.parent) |
|
1085 |
if this_entry is None: |
|
1086 |
if name_winner == "this": |
|
1087 |
name_winner = "other" |
|
1088 |
if parent_id_winner == "this": |
|
1089 |
parent_id_winner = "other" |
|
1090 |
if name_winner == "this" and parent_id_winner == "this": |
|
1091 |
return
|
|
|
1534.7.130
by Aaron Bentley
More conflict handling, test porting |
1092 |
if name_winner == "conflict": |
1093 |
trans_id = self.tt.get_trans_id(file_id) |
|
1094 |
self.conflicts.append(('name conflict', trans_id, |
|
1095 |
self.name(this_entry, file_id), |
|
1096 |
self.name(other_entry, file_id))) |
|
1097 |
if parent_id_winner == "conflict": |
|
1098 |
trans_id = self.tt.get_trans_id(file_id) |
|
1099 |
self.conflicts.append(('parent conflict', trans_id, |
|
1100 |
self.parent(this_entry, file_id), |
|
1101 |
self.parent(other_entry, file_id))) |
|
|
1534.7.105
by Aaron Bentley
Got merge with rename working |
1102 |
if other_entry is None: |
1103 |
# it doesn't matter whether the result was 'other' or
|
|
1104 |
# 'conflict'-- if there's no 'other', we leave it alone.
|
|
1105 |
return
|
|
1106 |
# if we get here, name_winner and parent_winner are set to safe values.
|
|
1107 |
winner_entry = {"this": this_entry, "other": other_entry, |
|
1108 |
"conflict": other_entry} |
|
1109 |
trans_id = self.tt.get_trans_id(file_id) |
|
1110 |
parent_id = winner_entry[parent_id_winner].parent_id |
|
1111 |
parent_trans_id = self.tt.get_trans_id(parent_id) |
|
1112 |
self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id, |
|
1113 |
trans_id) |
|
1114 |
||
1115 |
||
|
1534.7.89
by Aaron Bentley
Handle all content types in three-way |
1116 |
def merge_contents(self, file_id): |
1117 |
def contents_pair(tree): |
|
1118 |
if file_id not in tree: |
|
1119 |
return (None, None) |
|
1120 |
kind = tree.kind(file_id) |
|
1121 |
if kind == "file": |
|
1122 |
contents = tree.get_file_sha1(file_id) |
|
1123 |
elif kind == "symlink": |
|
1124 |
contents = tree.get_symlink_target(file_id) |
|
1125 |
else: |
|
1126 |
contents = None |
|
1127 |
return kind, contents |
|
1128 |
# See SPOT run. run, SPOT, run.
|
|
1129 |
# So we're not QUITE repeating ourselves; we do tricky things with
|
|
1130 |
# file kind...
|
|
1131 |
base_pair = contents_pair(self.base_tree) |
|
1132 |
other_pair = contents_pair(self.other_tree) |
|
1133 |
if base_pair == other_pair: |
|
1134 |
return "unmodified" |
|
1135 |
this_pair = contents_pair(self.this_tree) |
|
1136 |
if this_pair == other_pair: |
|
1137 |
return "unmodified" |
|
|
1534.7.90
by Aaron Bentley
fixed undefined trans_id |
1138 |
else: |
|
1534.7.89
by Aaron Bentley
Handle all content types in three-way |
1139 |
trans_id = self.tt.get_trans_id(file_id) |
|
1534.7.90
by Aaron Bentley
fixed undefined trans_id |
1140 |
if this_pair == base_pair: |
1141 |
if file_id in self.this_tree: |
|
1142 |
self.tt.delete_contents(trans_id) |
|
|
1534.7.110
by Aaron Bentley
Handled three-way with deletions better |
1143 |
if file_id in self.other_tree.inventory: |
1144 |
create_by_entry(self.tt, |
|
1145 |
self.other_tree.inventory[file_id], |
|
1146 |
self.other_tree, trans_id) |
|
1147 |
return "modified" |
|
1148 |
if file_id in self.this_tree: |
|
1149 |
self.tt.unversion_file(trans_id) |
|
1150 |
return "deleted" |
|
|
1534.7.90
by Aaron Bentley
fixed undefined trans_id |
1151 |
elif this_pair[0] == "file" and other_pair[0] == "file": |
1152 |
# If this and other are both files, either base is a file, or
|
|
1153 |
# both converted to files, so at least we have agreement that
|
|
1154 |
# output should be a file.
|
|
1155 |
self.text_merge(file_id, trans_id) |
|
1156 |
return "modified" |
|
|
1534.7.89
by Aaron Bentley
Handle all content types in three-way |
1157 |
else: |
|
1534.7.105
by Aaron Bentley
Got merge with rename working |
1158 |
trans_id = self.tt.get_trans_id(file_id) |
|
1534.7.101
by Aaron Bentley
Got conflicts on symlinks working properly |
1159 |
name = self.tt.final_name(trans_id) |
1160 |
parent_id = self.tt.final_parent(trans_id) |
|
1161 |
if file_id in self.this_tree.inventory: |
|
1162 |
self.tt.unversion_file(trans_id) |
|
|
1534.7.102
by Aaron Bentley
Deleted old pre-conflict contents |
1163 |
self.tt.delete_contents(trans_id) |
|
1534.7.105
by Aaron Bentley
Got merge with rename working |
1164 |
else: |
1165 |
self.tt.cancel_versioning(trans_id) |
|
|
1534.7.128
by Aaron Bentley
Got missing contents test working |
1166 |
self.conflicts.append(('contents conflict', (file_id))) |
|
1534.7.101
by Aaron Bentley
Got conflicts on symlinks working properly |
1167 |
file_group = self._dump_conflicts(name, parent_id, file_id, |
1168 |
set_version=True) |
|
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1169 |
|
1170 |
def get_lines(self, tree, file_id): |
|
1171 |
if file_id in tree: |
|
|
1534.7.79
by Aaron Bentley
Stopped calling get_file_lines on WorkingTree |
1172 |
return tree.get_file(file_id).readlines() |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1173 |
else: |
1174 |
return [] |
|
1175 |
||
1176 |
def text_merge(self, file_id, trans_id): |
|
1177 |
"""Perform a three-way text merge on a file_id""" |
|
|
1534.7.92
by Aaron Bentley
Handle non-file bases |
1178 |
# it's possible that we got here with base as a different type.
|
1179 |
# if so, we just want two-way text conflicts.
|
|
|
1534.7.99
by Aaron Bentley
Handle non-existent BASE properly |
1180 |
if file_id in self.base_tree and \ |
1181 |
self.base_tree.kind(file_id) == "file": |
|
|
1534.7.92
by Aaron Bentley
Handle non-file bases |
1182 |
base_lines = self.get_lines(self.base_tree, file_id) |
1183 |
else: |
|
1184 |
base_lines = [] |
|
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1185 |
other_lines = self.get_lines(self.other_tree, file_id) |
|
1534.7.83
by Aaron Bentley
differentiated between this tree(input) and working tree(output) in merge. |
1186 |
this_lines = self.get_lines(self.this_tree, file_id) |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1187 |
m3 = Merge3(base_lines, this_lines, other_lines) |
1188 |
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE" |
|
|
1534.7.107
by Aaron Bentley
Implemented show-base |
1189 |
if self.show_base is True: |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1190 |
base_marker = '|' * 7 |
1191 |
else: |
|
1192 |
base_marker = None |
|
1193 |
||
1194 |
def iter_merge3(retval): |
|
1195 |
retval["text_conflicts"] = False |
|
1196 |
for line in m3.merge_lines(name_a = "TREE", |
|
1197 |
name_b = "MERGE-SOURCE", |
|
1198 |
name_base = "BASE-REVISION", |
|
1199 |
start_marker=start_marker, |
|
1200 |
base_marker=base_marker, |
|
|
1534.7.107
by Aaron Bentley
Implemented show-base |
1201 |
reprocess=self.reprocess): |
|
1534.7.77
by Aaron Bentley
Got merge functionality starting to work |
1202 |
if line.startswith(start_marker): |
1203 |
retval["text_conflicts"] = True |
|
1204 |
yield line.replace(start_marker, '<' * 7) |
|
1205 |
else: |
|
1206 |
yield line |
|
1207 |
retval = {} |
|
1208 |
merge3_iterator = iter_merge3(retval) |
|
1209 |
self.tt.create_file(merge3_iterator, trans_id) |
|
1210 |
if retval["text_conflicts"] is True: |
|
|
1534.7.82
by Aaron Bentley
Reported text conflicts programatically |
1211 |
self.conflicts.append(('text conflict', (file_id))) |
|
1534.7.101
by Aaron Bentley
Got conflicts on symlinks working properly |
1212 |
name = self.tt.final_name(trans_id) |
1213 |
parent_id = self.tt.final_parent(trans_id) |
|
1214 |
file_group = self._dump_conflicts(name, parent_id, file_id, |
|
1215 |
this_lines, base_lines, |
|
1216 |
other_lines) |
|
1217 |
file_group.append(trans_id) |
|
1218 |
||
1219 |
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None, |
|
|
1534.8.2
by Aaron Bentley
Implemented weave merge |
1220 |
base_lines=None, other_lines=None, set_version=False, |
1221 |
no_base=False): |
|
1222 |
data = [('OTHER', self.other_tree, other_lines), |
|
1223 |
('THIS', self.this_tree, this_lines)] |
|
1224 |
if not no_base: |
|
1225 |
data.append(('BASE', self.base_tree, base_lines)) |
|
|
1534.7.101
by Aaron Bentley
Got conflicts on symlinks working properly |
1226 |
versioned = False |
1227 |
file_group = [] |
|
1228 |
for suffix, tree, lines in data: |
|
1229 |
if file_id in tree: |
|
1230 |
trans_id = self._conflict_file(name, parent_id, tree, file_id, |
|
1231 |
suffix, lines) |
|
1232 |
file_group.append(trans_id) |
|
|
1534.7.104
by Aaron Bentley
Fixed set_versioned, enhanced conflict testing |
1233 |
if set_version and not versioned: |
1234 |
self.tt.version_file(file_id, trans_id) |
|
1235 |
versioned = True |
|
|
1534.7.101
by Aaron Bentley
Got conflicts on symlinks working properly |
1236 |
return file_group |
|
1534.7.85
by Aaron Bentley
Handled merging the execute bit |
1237 |
|
|
1534.7.101
by Aaron Bentley
Got conflicts on symlinks working properly |
1238 |
def _conflict_file(self, name, parent_id, tree, file_id, suffix, |
1239 |
lines=None): |
|
1240 |
name = name + '.' + suffix |
|
|
1534.7.97
by Aaron Bentley
Ensured foo.BASE is a directory if there's a conflict |
1241 |
trans_id = self.tt.create_path(name, parent_id) |
1242 |
entry = tree.inventory[file_id] |
|
1243 |
create_by_entry(self.tt, entry, tree, trans_id, lines) |
|
1244 |
return trans_id |
|
|
1534.7.85
by Aaron Bentley
Handled merging the execute bit |
1245 |
|
|
1534.7.88
by Aaron Bentley
Defer determining trans_id until we need it, because each trans_id carries a cost. |
1246 |
def merge_executable(self, file_id, file_status): |
1247 |
if file_status == "deleted": |
|
|
1534.7.85
by Aaron Bentley
Handled merging the execute bit |
1248 |
return
|
|
1534.7.125
by Aaron Bentley
Fixed executability merge bug |
1249 |
trans_id = self.tt.get_trans_id(file_id) |
1250 |
try: |
|
1251 |
if self.tt.final_kind(trans_id) != "file": |
|
1252 |
return
|
|
1253 |
except NoSuchFile: |
|
|
1534.7.111
by Aaron Bentley
Avoid non-file executability |
1254 |
return
|
|
1534.7.85
by Aaron Bentley
Handled merging the execute bit |
1255 |
winner = self.scalar_three_way(self.this_tree, self.base_tree, |
1256 |
self.other_tree, file_id, |
|
1257 |
self.executable) |
|
1258 |
if winner == "conflict": |
|
1259 |
# There must be a None in here, if we have a conflict, but we
|
|
1260 |
# need executability since file status was not deleted.
|
|
1261 |
if self.other_tree.is_executable(file_id) is None: |
|
1262 |
winner == "this" |
|
1263 |
else: |
|
1264 |
winner == "other" |
|
1265 |
if winner == "this": |
|
1266 |
if file_status == "modified": |
|
1267 |
executability = self.this_tree.is_executable(file_id) |
|
|
1534.7.108
by Aaron Bentley
Fixed executability handling |
1268 |
if executability is not None: |
1269 |
trans_id = self.tt.get_trans_id(file_id) |
|
1270 |
self.tt.set_executability(executability, trans_id) |
|
|
1534.7.85
by Aaron Bentley
Handled merging the execute bit |
1271 |
else: |
1272 |
assert winner == "other" |
|
|
1534.7.126
by Aaron Bentley
Fixed executability merge bug |
1273 |
if file_id in self.other_tree: |
1274 |
executability = self.other_tree.is_executable(file_id) |
|
1275 |
elif file_id in self.this_tree: |
|
1276 |
executability = self.this_tree.is_executable(file_id) |
|
1277 |
elif file_id in self.base_tree: |
|
1278 |
executability = self.base_tree.is_executable(file_id) |
|
1279 |
if executability is not None: |
|
1280 |
trans_id = self.tt.get_trans_id(file_id) |
|
1281 |
self.tt.set_executability(executability, trans_id) |
|
|
1534.8.2
by Aaron Bentley
Implemented weave merge |
1282 |
|
1283 |
class WeaveMerger(Merge3Merger): |
|
1284 |
supports_reprocess = False |
|
1285 |
supports_show_base = False |
|
1286 |
def _merged_lines(self, file_id): |
|
1287 |
"""Generate the merged lines. |
|
1288 |
There is no distinction between lines that are meant to contain <<<<<<<
|
|
1289 |
and conflicts.
|
|
1290 |
"""
|
|
1291 |
if getattr(self.this_tree, 'get_weave', False) is False: |
|
1292 |
# If we have a WorkingTree, try using the basis
|
|
1293 |
wt_sha1 = self.this_tree.get_file_sha1(file_id) |
|
1294 |
this_tree = self.this_tree.branch.basis_tree() |
|
1295 |
if this_tree.get_file_sha1(file_id) != wt_sha1: |
|
1296 |
raise WorkingTreeNotRevision(self.this_tree) |
|
1297 |
else: |
|
1298 |
this_tree = self.this_tree |
|
1299 |
weave = this_tree.get_weave(file_id) |
|
1300 |
this_revision_id = this_tree.inventory[file_id].revision |
|
1301 |
other_revision_id = self.other_tree.inventory[file_id].revision |
|
1302 |
this_i = weave.lookup(this_revision_id) |
|
1303 |
other_i = weave.lookup(other_revision_id) |
|
1304 |
plan = weave.plan_merge(this_i, other_i) |
|
1305 |
return weave.weave_merge(plan) |
|
1306 |
||
1307 |
def text_merge(self, file_id, trans_id): |
|
1308 |
lines = self._merged_lines(file_id) |
|
1309 |
conflicts = '<<<<<<<\n' in lines |
|
1310 |
self.tt.create_file(lines, trans_id) |
|
1311 |
if conflicts: |
|
1312 |
self.conflicts.append(('text conflict', (file_id))) |
|
1313 |
name = self.tt.final_name(trans_id) |
|
1314 |
parent_id = self.tt.final_parent(trans_id) |
|
1315 |
file_group = self._dump_conflicts(name, parent_id, file_id, |
|
1316 |
no_base=True) |
|
1317 |
file_group.append(trans_id) |
|
|
1534.8.3
by Aaron Bentley
Added Diff3 merging for tree transforms |
1318 |
|
1319 |
||
1320 |
class Diff3Merger(Merge3Merger): |
|
1321 |
"""Use good ol' diff3 to do text merges""" |
|
1322 |
def dump_file(self, temp_dir, name, tree, file_id): |
|
1323 |
out_path = pathjoin(temp_dir, name) |
|
1324 |
out_file = file(out_path, "wb") |
|
1325 |
in_file = tree.get_file(file_id) |
|
1326 |
for line in in_file: |
|
1327 |
out_file.write(line) |
|
1328 |
return out_path |
|
1329 |
||
1330 |
def text_merge(self, file_id, trans_id): |
|
1331 |
import bzrlib.patch |
|
1332 |
temp_dir = mkdtemp(prefix="bzr-") |
|
1333 |
try: |
|
1334 |
new_file = os.path.join(temp_dir, "new") |
|
1335 |
this = self.dump_file(temp_dir, "this", self.this_tree, file_id) |
|
1336 |
base = self.dump_file(temp_dir, "base", self.base_tree, file_id) |
|
1337 |
other = self.dump_file(temp_dir, "other", self.other_tree, file_id) |
|
1338 |
status = bzrlib.patch.diff3(new_file, this, base, other) |
|
1339 |
if status not in (0, 1): |
|
1340 |
raise BzrError("Unhandled diff3 exit code") |
|
1341 |
self.tt.create_file(file(new_file, "rb"), trans_id) |
|
1342 |
if status == 1: |
|
1343 |
name = self.tt.final_name(trans_id) |
|
1344 |
parent_id = self.tt.final_parent(trans_id) |
|
1345 |
self._dump_conflicts(name, parent_id, file_id) |
|
1346 |
self.conflicts.append(('text conflict', (file_id))) |
|
1347 |
finally: |
|
1348 |
rmtree(temp_dir) |