##// END OF EJS Templates
dirstatemap: move `set_tracked` out of common methods and plug in Rust...
Raphaël Gomès -
r49989:55c158a3 default
parent child Browse files
Show More
@@ -1,730 +1,733 b''
1 # dirstatemap.py
1 # dirstatemap.py
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6
6
7 import errno
7 import errno
8
8
9 from .i18n import _
9 from .i18n import _
10
10
11 from . import (
11 from . import (
12 error,
12 error,
13 pathutil,
13 pathutil,
14 policy,
14 policy,
15 txnutil,
15 txnutil,
16 util,
16 util,
17 )
17 )
18
18
19 from .dirstateutils import (
19 from .dirstateutils import (
20 docket as docketmod,
20 docket as docketmod,
21 v2,
21 v2,
22 )
22 )
23
23
24 parsers = policy.importmod('parsers')
24 parsers = policy.importmod('parsers')
25 rustmod = policy.importrust('dirstate')
25 rustmod = policy.importrust('dirstate')
26
26
27 propertycache = util.propertycache
27 propertycache = util.propertycache
28
28
29 if rustmod is None:
29 if rustmod is None:
30 DirstateItem = parsers.DirstateItem
30 DirstateItem = parsers.DirstateItem
31 else:
31 else:
32 DirstateItem = rustmod.DirstateItem
32 DirstateItem = rustmod.DirstateItem
33
33
34 rangemask = 0x7FFFFFFF
34 rangemask = 0x7FFFFFFF
35
35
36
36
37 class _dirstatemapcommon:
37 class _dirstatemapcommon:
38 """
38 """
39 Methods that are identical for both implementations of the dirstatemap
39 Methods that are identical for both implementations of the dirstatemap
40 class, with and without Rust extensions enabled.
40 class, with and without Rust extensions enabled.
41 """
41 """
42
42
43 # please pytype
43 # please pytype
44
44
45 _map = None
45 _map = None
46 copymap = None
46 copymap = None
47
47
48 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
48 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
49 self._use_dirstate_v2 = use_dirstate_v2
49 self._use_dirstate_v2 = use_dirstate_v2
50 self._nodeconstants = nodeconstants
50 self._nodeconstants = nodeconstants
51 self._ui = ui
51 self._ui = ui
52 self._opener = opener
52 self._opener = opener
53 self._root = root
53 self._root = root
54 self._filename = b'dirstate'
54 self._filename = b'dirstate'
55 self._nodelen = 20 # Also update Rust code when changing this!
55 self._nodelen = 20 # Also update Rust code when changing this!
56 self._parents = None
56 self._parents = None
57 self._dirtyparents = False
57 self._dirtyparents = False
58 self._docket = None
58 self._docket = None
59
59
60 # for consistent view between _pl() and _read() invocations
60 # for consistent view between _pl() and _read() invocations
61 self._pendingmode = None
61 self._pendingmode = None
62
62
63 def preload(self):
63 def preload(self):
64 """Loads the underlying data, if it's not already loaded"""
64 """Loads the underlying data, if it's not already loaded"""
65 self._map
65 self._map
66
66
67 def get(self, key, default=None):
67 def get(self, key, default=None):
68 return self._map.get(key, default)
68 return self._map.get(key, default)
69
69
70 def __len__(self):
70 def __len__(self):
71 return len(self._map)
71 return len(self._map)
72
72
73 def __iter__(self):
73 def __iter__(self):
74 return iter(self._map)
74 return iter(self._map)
75
75
76 def __contains__(self, key):
76 def __contains__(self, key):
77 return key in self._map
77 return key in self._map
78
78
79 def __getitem__(self, item):
79 def __getitem__(self, item):
80 return self._map[item]
80 return self._map[item]
81
81
82 ### sub-class utility method
82 ### sub-class utility method
83 #
83 #
84 # Use to allow for generic implementation of some method while still coping
84 # Use to allow for generic implementation of some method while still coping
85 # with minor difference between implementation.
85 # with minor difference between implementation.
86
86
87 def _dirs_incr(self, filename, old_entry=None):
87 def _dirs_incr(self, filename, old_entry=None):
88 """increment the dirstate counter if applicable
88 """increment the dirstate counter if applicable
89
89
90 This might be a no-op for some subclasses who deal with directory
90 This might be a no-op for some subclasses who deal with directory
91 tracking in a different way.
91 tracking in a different way.
92 """
92 """
93
93
94 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
94 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
95 """decrement the dirstate counter if applicable
95 """decrement the dirstate counter if applicable
96
96
97 This might be a no-op for some subclasses who deal with directory
97 This might be a no-op for some subclasses who deal with directory
98 tracking in a different way.
98 tracking in a different way.
99 """
99 """
100
100
101 def _refresh_entry(self, f, entry):
101 def _refresh_entry(self, f, entry):
102 """record updated state of an entry"""
102 """record updated state of an entry"""
103
103
104 def _insert_entry(self, f, entry):
104 def _insert_entry(self, f, entry):
105 """add a new dirstate entry (or replace an unrelated one)
105 """add a new dirstate entry (or replace an unrelated one)
106
106
107 The fact it is actually new is the responsability of the caller
107 The fact it is actually new is the responsability of the caller
108 """
108 """
109
109
110 def _drop_entry(self, f):
110 def _drop_entry(self, f):
111 """remove any entry for file f
111 """remove any entry for file f
112
112
113 This should also drop associated copy information
113 This should also drop associated copy information
114
114
115 The fact we actually need to drop it is the responsability of the caller"""
115 The fact we actually need to drop it is the responsability of the caller"""
116
116
117 ### method to manipulate the entries
117 ### method to manipulate the entries
118
118
119 def set_possibly_dirty(self, filename):
119 def set_possibly_dirty(self, filename):
120 """record that the current state of the file on disk is unknown"""
120 """record that the current state of the file on disk is unknown"""
121 entry = self[filename]
121 entry = self[filename]
122 entry.set_possibly_dirty()
122 entry.set_possibly_dirty()
123 self._refresh_entry(filename, entry)
123 self._refresh_entry(filename, entry)
124
124
125 def set_clean(self, filename, mode, size, mtime):
125 def set_clean(self, filename, mode, size, mtime):
126 """mark a file as back to a clean state"""
126 """mark a file as back to a clean state"""
127 entry = self[filename]
127 entry = self[filename]
128 size = size & rangemask
128 size = size & rangemask
129 entry.set_clean(mode, size, mtime)
129 entry.set_clean(mode, size, mtime)
130 self._refresh_entry(filename, entry)
130 self._refresh_entry(filename, entry)
131 self.copymap.pop(filename, None)
131 self.copymap.pop(filename, None)
132
132
133 def set_tracked(self, filename):
134 new = False
135 entry = self.get(filename)
136 if entry is None:
137 self._dirs_incr(filename)
138 entry = DirstateItem(
139 wc_tracked=True,
140 )
141
142 self._insert_entry(filename, entry)
143 new = True
144 elif not entry.tracked:
145 self._dirs_incr(filename, entry)
146 entry.set_tracked()
147 self._refresh_entry(filename, entry)
148 new = True
149 else:
150 # XXX This is probably overkill for more case, but we need this to
151 # fully replace the `normallookup` call with `set_tracked` one.
152 # Consider smoothing this in the future.
153 entry.set_possibly_dirty()
154 self._refresh_entry(filename, entry)
155 return new
156
157 def set_untracked(self, f):
133 def set_untracked(self, f):
158 """Mark a file as no longer tracked in the dirstate map"""
134 """Mark a file as no longer tracked in the dirstate map"""
159 entry = self.get(f)
135 entry = self.get(f)
160 if entry is None:
136 if entry is None:
161 return False
137 return False
162 else:
138 else:
163 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
139 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
164 if not entry.p2_info:
140 if not entry.p2_info:
165 self.copymap.pop(f, None)
141 self.copymap.pop(f, None)
166 entry.set_untracked()
142 entry.set_untracked()
167 self._refresh_entry(f, entry)
143 self._refresh_entry(f, entry)
168 return True
144 return True
169
145
170 def reset_state(
146 def reset_state(
171 self,
147 self,
172 filename,
148 filename,
173 wc_tracked=False,
149 wc_tracked=False,
174 p1_tracked=False,
150 p1_tracked=False,
175 p2_info=False,
151 p2_info=False,
176 has_meaningful_mtime=True,
152 has_meaningful_mtime=True,
177 has_meaningful_data=True,
153 has_meaningful_data=True,
178 parentfiledata=None,
154 parentfiledata=None,
179 ):
155 ):
180 """Set a entry to a given state, diregarding all previous state
156 """Set a entry to a given state, diregarding all previous state
181
157
182 This is to be used by the part of the dirstate API dedicated to
158 This is to be used by the part of the dirstate API dedicated to
183 adjusting the dirstate after a update/merge.
159 adjusting the dirstate after a update/merge.
184
160
185 note: calling this might result to no entry existing at all if the
161 note: calling this might result to no entry existing at all if the
186 dirstate map does not see any point at having one for this file
162 dirstate map does not see any point at having one for this file
187 anymore.
163 anymore.
188 """
164 """
189 # copy information are now outdated
165 # copy information are now outdated
190 # (maybe new information should be in directly passed to this function)
166 # (maybe new information should be in directly passed to this function)
191 self.copymap.pop(filename, None)
167 self.copymap.pop(filename, None)
192
168
193 if not (p1_tracked or p2_info or wc_tracked):
169 if not (p1_tracked or p2_info or wc_tracked):
194 old_entry = self._map.get(filename)
170 old_entry = self._map.get(filename)
195 self._drop_entry(filename)
171 self._drop_entry(filename)
196 self._dirs_decr(filename, old_entry=old_entry)
172 self._dirs_decr(filename, old_entry=old_entry)
197 return
173 return
198
174
199 old_entry = self._map.get(filename)
175 old_entry = self._map.get(filename)
200 self._dirs_incr(filename, old_entry)
176 self._dirs_incr(filename, old_entry)
201 entry = DirstateItem(
177 entry = DirstateItem(
202 wc_tracked=wc_tracked,
178 wc_tracked=wc_tracked,
203 p1_tracked=p1_tracked,
179 p1_tracked=p1_tracked,
204 p2_info=p2_info,
180 p2_info=p2_info,
205 has_meaningful_mtime=has_meaningful_mtime,
181 has_meaningful_mtime=has_meaningful_mtime,
206 parentfiledata=parentfiledata,
182 parentfiledata=parentfiledata,
207 )
183 )
208 self._insert_entry(filename, entry)
184 self._insert_entry(filename, entry)
209
185
210 ### disk interaction
186 ### disk interaction
211
187
212 def _opendirstatefile(self):
188 def _opendirstatefile(self):
213 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
189 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
214 if self._pendingmode is not None and self._pendingmode != mode:
190 if self._pendingmode is not None and self._pendingmode != mode:
215 fp.close()
191 fp.close()
216 raise error.Abort(
192 raise error.Abort(
217 _(b'working directory state may be changed parallelly')
193 _(b'working directory state may be changed parallelly')
218 )
194 )
219 self._pendingmode = mode
195 self._pendingmode = mode
220 return fp
196 return fp
221
197
222 def _readdirstatefile(self, size=-1):
198 def _readdirstatefile(self, size=-1):
223 try:
199 try:
224 with self._opendirstatefile() as fp:
200 with self._opendirstatefile() as fp:
225 return fp.read(size)
201 return fp.read(size)
226 except IOError as err:
202 except IOError as err:
227 if err.errno != errno.ENOENT:
203 if err.errno != errno.ENOENT:
228 raise
204 raise
229 # File doesn't exist, so the current state is empty
205 # File doesn't exist, so the current state is empty
230 return b''
206 return b''
231
207
232 @property
208 @property
233 def docket(self):
209 def docket(self):
234 if not self._docket:
210 if not self._docket:
235 if not self._use_dirstate_v2:
211 if not self._use_dirstate_v2:
236 raise error.ProgrammingError(
212 raise error.ProgrammingError(
237 b'dirstate only has a docket in v2 format'
213 b'dirstate only has a docket in v2 format'
238 )
214 )
239 self._docket = docketmod.DirstateDocket.parse(
215 self._docket = docketmod.DirstateDocket.parse(
240 self._readdirstatefile(), self._nodeconstants
216 self._readdirstatefile(), self._nodeconstants
241 )
217 )
242 return self._docket
218 return self._docket
243
219
244 def write_v2_no_append(self, tr, st, meta, packed):
220 def write_v2_no_append(self, tr, st, meta, packed):
245 old_docket = self.docket
221 old_docket = self.docket
246 new_docket = docketmod.DirstateDocket.with_new_uuid(
222 new_docket = docketmod.DirstateDocket.with_new_uuid(
247 self.parents(), len(packed), meta
223 self.parents(), len(packed), meta
248 )
224 )
249 data_filename = new_docket.data_filename()
225 data_filename = new_docket.data_filename()
250 if tr:
226 if tr:
251 tr.add(data_filename, 0)
227 tr.add(data_filename, 0)
252 self._opener.write(data_filename, packed)
228 self._opener.write(data_filename, packed)
253 # Write the new docket after the new data file has been
229 # Write the new docket after the new data file has been
254 # written. Because `st` was opened with `atomictemp=True`,
230 # written. Because `st` was opened with `atomictemp=True`,
255 # the actual `.hg/dirstate` file is only affected on close.
231 # the actual `.hg/dirstate` file is only affected on close.
256 st.write(new_docket.serialize())
232 st.write(new_docket.serialize())
257 st.close()
233 st.close()
258 # Remove the old data file after the new docket pointing to
234 # Remove the old data file after the new docket pointing to
259 # the new data file was written.
235 # the new data file was written.
260 if old_docket.uuid:
236 if old_docket.uuid:
261 data_filename = old_docket.data_filename()
237 data_filename = old_docket.data_filename()
262 unlink = lambda _tr=None: self._opener.unlink(data_filename)
238 unlink = lambda _tr=None: self._opener.unlink(data_filename)
263 if tr:
239 if tr:
264 category = b"dirstate-v2-clean-" + old_docket.uuid
240 category = b"dirstate-v2-clean-" + old_docket.uuid
265 tr.addpostclose(category, unlink)
241 tr.addpostclose(category, unlink)
266 else:
242 else:
267 unlink()
243 unlink()
268 self._docket = new_docket
244 self._docket = new_docket
269
245
270 ### reading/setting parents
246 ### reading/setting parents
271
247
272 def parents(self):
248 def parents(self):
273 if not self._parents:
249 if not self._parents:
274 if self._use_dirstate_v2:
250 if self._use_dirstate_v2:
275 self._parents = self.docket.parents
251 self._parents = self.docket.parents
276 else:
252 else:
277 read_len = self._nodelen * 2
253 read_len = self._nodelen * 2
278 st = self._readdirstatefile(read_len)
254 st = self._readdirstatefile(read_len)
279 l = len(st)
255 l = len(st)
280 if l == read_len:
256 if l == read_len:
281 self._parents = (
257 self._parents = (
282 st[: self._nodelen],
258 st[: self._nodelen],
283 st[self._nodelen : 2 * self._nodelen],
259 st[self._nodelen : 2 * self._nodelen],
284 )
260 )
285 elif l == 0:
261 elif l == 0:
286 self._parents = (
262 self._parents = (
287 self._nodeconstants.nullid,
263 self._nodeconstants.nullid,
288 self._nodeconstants.nullid,
264 self._nodeconstants.nullid,
289 )
265 )
290 else:
266 else:
291 raise error.Abort(
267 raise error.Abort(
292 _(b'working directory state appears damaged!')
268 _(b'working directory state appears damaged!')
293 )
269 )
294
270
295 return self._parents
271 return self._parents
296
272
297
273
298 class dirstatemap(_dirstatemapcommon):
274 class dirstatemap(_dirstatemapcommon):
299 """Map encapsulating the dirstate's contents.
275 """Map encapsulating the dirstate's contents.
300
276
301 The dirstate contains the following state:
277 The dirstate contains the following state:
302
278
303 - `identity` is the identity of the dirstate file, which can be used to
279 - `identity` is the identity of the dirstate file, which can be used to
304 detect when changes have occurred to the dirstate file.
280 detect when changes have occurred to the dirstate file.
305
281
306 - `parents` is a pair containing the parents of the working copy. The
282 - `parents` is a pair containing the parents of the working copy. The
307 parents are updated by calling `setparents`.
283 parents are updated by calling `setparents`.
308
284
309 - the state map maps filenames to tuples of (state, mode, size, mtime),
285 - the state map maps filenames to tuples of (state, mode, size, mtime),
310 where state is a single character representing 'normal', 'added',
286 where state is a single character representing 'normal', 'added',
311 'removed', or 'merged'. It is read by treating the dirstate as a
287 'removed', or 'merged'. It is read by treating the dirstate as a
312 dict. File state is updated by calling various methods (see each
288 dict. File state is updated by calling various methods (see each
313 documentation for details):
289 documentation for details):
314
290
315 - `reset_state`,
291 - `reset_state`,
316 - `set_tracked`
292 - `set_tracked`
317 - `set_untracked`
293 - `set_untracked`
318 - `set_clean`
294 - `set_clean`
319 - `set_possibly_dirty`
295 - `set_possibly_dirty`
320
296
321 - `copymap` maps destination filenames to their source filename.
297 - `copymap` maps destination filenames to their source filename.
322
298
323 The dirstate also provides the following views onto the state:
299 The dirstate also provides the following views onto the state:
324
300
325 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
301 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
326 form that they appear as in the dirstate.
302 form that they appear as in the dirstate.
327
303
328 - `dirfoldmap` is a dict mapping normalized directory names to the
304 - `dirfoldmap` is a dict mapping normalized directory names to the
329 denormalized form that they appear as in the dirstate.
305 denormalized form that they appear as in the dirstate.
330 """
306 """
331
307
332 ### Core data storage and access
308 ### Core data storage and access
333
309
334 @propertycache
310 @propertycache
335 def _map(self):
311 def _map(self):
336 self._map = {}
312 self._map = {}
337 self.read()
313 self.read()
338 return self._map
314 return self._map
339
315
340 @propertycache
316 @propertycache
341 def copymap(self):
317 def copymap(self):
342 self.copymap = {}
318 self.copymap = {}
343 self._map
319 self._map
344 return self.copymap
320 return self.copymap
345
321
346 def clear(self):
322 def clear(self):
347 self._map.clear()
323 self._map.clear()
348 self.copymap.clear()
324 self.copymap.clear()
349 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
325 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
350 util.clearcachedproperty(self, b"_dirs")
326 util.clearcachedproperty(self, b"_dirs")
351 util.clearcachedproperty(self, b"_alldirs")
327 util.clearcachedproperty(self, b"_alldirs")
352 util.clearcachedproperty(self, b"filefoldmap")
328 util.clearcachedproperty(self, b"filefoldmap")
353 util.clearcachedproperty(self, b"dirfoldmap")
329 util.clearcachedproperty(self, b"dirfoldmap")
354
330
355 def items(self):
331 def items(self):
356 return self._map.items()
332 return self._map.items()
357
333
358 # forward for python2,3 compat
334 # forward for python2,3 compat
359 iteritems = items
335 iteritems = items
360
336
361 def debug_iter(self, all):
337 def debug_iter(self, all):
362 """
338 """
363 Return an iterator of (filename, state, mode, size, mtime) tuples
339 Return an iterator of (filename, state, mode, size, mtime) tuples
364
340
365 `all` is unused when Rust is not enabled
341 `all` is unused when Rust is not enabled
366 """
342 """
367 for (filename, item) in self.items():
343 for (filename, item) in self.items():
368 yield (filename, item.state, item.mode, item.size, item.mtime)
344 yield (filename, item.state, item.mode, item.size, item.mtime)
369
345
370 def keys(self):
346 def keys(self):
371 return self._map.keys()
347 return self._map.keys()
372
348
373 ### reading/setting parents
349 ### reading/setting parents
374
350
375 def setparents(self, p1, p2, fold_p2=False):
351 def setparents(self, p1, p2, fold_p2=False):
376 self._parents = (p1, p2)
352 self._parents = (p1, p2)
377 self._dirtyparents = True
353 self._dirtyparents = True
378 copies = {}
354 copies = {}
379 if fold_p2:
355 if fold_p2:
380 for f, s in self._map.items():
356 for f, s in self._map.items():
381 # Discard "merged" markers when moving away from a merge state
357 # Discard "merged" markers when moving away from a merge state
382 if s.p2_info:
358 if s.p2_info:
383 source = self.copymap.pop(f, None)
359 source = self.copymap.pop(f, None)
384 if source:
360 if source:
385 copies[f] = source
361 copies[f] = source
386 s.drop_merge_data()
362 s.drop_merge_data()
387 return copies
363 return copies
388
364
389 ### disk interaction
365 ### disk interaction
390
366
391 def read(self):
367 def read(self):
392 # ignore HG_PENDING because identity is used only for writing
368 # ignore HG_PENDING because identity is used only for writing
393 self.identity = util.filestat.frompath(
369 self.identity = util.filestat.frompath(
394 self._opener.join(self._filename)
370 self._opener.join(self._filename)
395 )
371 )
396
372
397 if self._use_dirstate_v2:
373 if self._use_dirstate_v2:
398 if not self.docket.uuid:
374 if not self.docket.uuid:
399 return
375 return
400 st = self._opener.read(self.docket.data_filename())
376 st = self._opener.read(self.docket.data_filename())
401 else:
377 else:
402 st = self._readdirstatefile()
378 st = self._readdirstatefile()
403
379
404 if not st:
380 if not st:
405 return
381 return
406
382
407 # TODO: adjust this estimate for dirstate-v2
383 # TODO: adjust this estimate for dirstate-v2
408 if util.safehasattr(parsers, b'dict_new_presized'):
384 if util.safehasattr(parsers, b'dict_new_presized'):
409 # Make an estimate of the number of files in the dirstate based on
385 # Make an estimate of the number of files in the dirstate based on
410 # its size. This trades wasting some memory for avoiding costly
386 # its size. This trades wasting some memory for avoiding costly
411 # resizes. Each entry have a prefix of 17 bytes followed by one or
387 # resizes. Each entry have a prefix of 17 bytes followed by one or
412 # two path names. Studies on various large-scale real-world repositories
388 # two path names. Studies on various large-scale real-world repositories
413 # found 54 bytes a reasonable upper limit for the average path names.
389 # found 54 bytes a reasonable upper limit for the average path names.
414 # Copy entries are ignored for the sake of this estimate.
390 # Copy entries are ignored for the sake of this estimate.
415 self._map = parsers.dict_new_presized(len(st) // 71)
391 self._map = parsers.dict_new_presized(len(st) // 71)
416
392
417 # Python's garbage collector triggers a GC each time a certain number
393 # Python's garbage collector triggers a GC each time a certain number
418 # of container objects (the number being defined by
394 # of container objects (the number being defined by
419 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
395 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
420 # for each file in the dirstate. The C version then immediately marks
396 # for each file in the dirstate. The C version then immediately marks
421 # them as not to be tracked by the collector. However, this has no
397 # them as not to be tracked by the collector. However, this has no
422 # effect on when GCs are triggered, only on what objects the GC looks
398 # effect on when GCs are triggered, only on what objects the GC looks
423 # into. This means that O(number of files) GCs are unavoidable.
399 # into. This means that O(number of files) GCs are unavoidable.
424 # Depending on when in the process's lifetime the dirstate is parsed,
400 # Depending on when in the process's lifetime the dirstate is parsed,
425 # this can get very expensive. As a workaround, disable GC while
401 # this can get very expensive. As a workaround, disable GC while
426 # parsing the dirstate.
402 # parsing the dirstate.
427 #
403 #
428 # (we cannot decorate the function directly since it is in a C module)
404 # (we cannot decorate the function directly since it is in a C module)
429 if self._use_dirstate_v2:
405 if self._use_dirstate_v2:
430 p = self.docket.parents
406 p = self.docket.parents
431 meta = self.docket.tree_metadata
407 meta = self.docket.tree_metadata
432 parse_dirstate = util.nogc(v2.parse_dirstate)
408 parse_dirstate = util.nogc(v2.parse_dirstate)
433 parse_dirstate(self._map, self.copymap, st, meta)
409 parse_dirstate(self._map, self.copymap, st, meta)
434 else:
410 else:
435 parse_dirstate = util.nogc(parsers.parse_dirstate)
411 parse_dirstate = util.nogc(parsers.parse_dirstate)
436 p = parse_dirstate(self._map, self.copymap, st)
412 p = parse_dirstate(self._map, self.copymap, st)
437 if not self._dirtyparents:
413 if not self._dirtyparents:
438 self.setparents(*p)
414 self.setparents(*p)
439
415
440 # Avoid excess attribute lookups by fast pathing certain checks
416 # Avoid excess attribute lookups by fast pathing certain checks
441 self.__contains__ = self._map.__contains__
417 self.__contains__ = self._map.__contains__
442 self.__getitem__ = self._map.__getitem__
418 self.__getitem__ = self._map.__getitem__
443 self.get = self._map.get
419 self.get = self._map.get
444
420
445 def write(self, tr, st):
421 def write(self, tr, st):
446 if self._use_dirstate_v2:
422 if self._use_dirstate_v2:
447 packed, meta = v2.pack_dirstate(self._map, self.copymap)
423 packed, meta = v2.pack_dirstate(self._map, self.copymap)
448 self.write_v2_no_append(tr, st, meta, packed)
424 self.write_v2_no_append(tr, st, meta, packed)
449 else:
425 else:
450 packed = parsers.pack_dirstate(
426 packed = parsers.pack_dirstate(
451 self._map, self.copymap, self.parents()
427 self._map, self.copymap, self.parents()
452 )
428 )
453 st.write(packed)
429 st.write(packed)
454 st.close()
430 st.close()
455 self._dirtyparents = False
431 self._dirtyparents = False
456
432
457 @propertycache
433 @propertycache
458 def identity(self):
434 def identity(self):
459 self._map
435 self._map
460 return self.identity
436 return self.identity
461
437
462 ### code related to maintaining and accessing "extra" property
438 ### code related to maintaining and accessing "extra" property
463 # (e.g. "has_dir")
439 # (e.g. "has_dir")
464
440
465 def _dirs_incr(self, filename, old_entry=None):
441 def _dirs_incr(self, filename, old_entry=None):
466 """incremente the dirstate counter if applicable"""
442 """incremente the dirstate counter if applicable"""
467 if (
443 if (
468 old_entry is None or old_entry.removed
444 old_entry is None or old_entry.removed
469 ) and "_dirs" in self.__dict__:
445 ) and "_dirs" in self.__dict__:
470 self._dirs.addpath(filename)
446 self._dirs.addpath(filename)
471 if old_entry is None and "_alldirs" in self.__dict__:
447 if old_entry is None and "_alldirs" in self.__dict__:
472 self._alldirs.addpath(filename)
448 self._alldirs.addpath(filename)
473
449
474 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
450 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
475 """decremente the dirstate counter if applicable"""
451 """decremente the dirstate counter if applicable"""
476 if old_entry is not None:
452 if old_entry is not None:
477 if "_dirs" in self.__dict__ and not old_entry.removed:
453 if "_dirs" in self.__dict__ and not old_entry.removed:
478 self._dirs.delpath(filename)
454 self._dirs.delpath(filename)
479 if "_alldirs" in self.__dict__ and not remove_variant:
455 if "_alldirs" in self.__dict__ and not remove_variant:
480 self._alldirs.delpath(filename)
456 self._alldirs.delpath(filename)
481 elif remove_variant and "_alldirs" in self.__dict__:
457 elif remove_variant and "_alldirs" in self.__dict__:
482 self._alldirs.addpath(filename)
458 self._alldirs.addpath(filename)
483 if "filefoldmap" in self.__dict__:
459 if "filefoldmap" in self.__dict__:
484 normed = util.normcase(filename)
460 normed = util.normcase(filename)
485 self.filefoldmap.pop(normed, None)
461 self.filefoldmap.pop(normed, None)
486
462
487 @propertycache
463 @propertycache
488 def filefoldmap(self):
464 def filefoldmap(self):
489 """Returns a dictionary mapping normalized case paths to their
465 """Returns a dictionary mapping normalized case paths to their
490 non-normalized versions.
466 non-normalized versions.
491 """
467 """
492 try:
468 try:
493 makefilefoldmap = parsers.make_file_foldmap
469 makefilefoldmap = parsers.make_file_foldmap
494 except AttributeError:
470 except AttributeError:
495 pass
471 pass
496 else:
472 else:
497 return makefilefoldmap(
473 return makefilefoldmap(
498 self._map, util.normcasespec, util.normcasefallback
474 self._map, util.normcasespec, util.normcasefallback
499 )
475 )
500
476
501 f = {}
477 f = {}
502 normcase = util.normcase
478 normcase = util.normcase
503 for name, s in self._map.items():
479 for name, s in self._map.items():
504 if not s.removed:
480 if not s.removed:
505 f[normcase(name)] = name
481 f[normcase(name)] = name
506 f[b'.'] = b'.' # prevents useless util.fspath() invocation
482 f[b'.'] = b'.' # prevents useless util.fspath() invocation
507 return f
483 return f
508
484
509 @propertycache
485 @propertycache
510 def dirfoldmap(self):
486 def dirfoldmap(self):
511 f = {}
487 f = {}
512 normcase = util.normcase
488 normcase = util.normcase
513 for name in self._dirs:
489 for name in self._dirs:
514 f[normcase(name)] = name
490 f[normcase(name)] = name
515 return f
491 return f
516
492
517 def hastrackeddir(self, d):
493 def hastrackeddir(self, d):
518 """
494 """
519 Returns True if the dirstate contains a tracked (not removed) file
495 Returns True if the dirstate contains a tracked (not removed) file
520 in this directory.
496 in this directory.
521 """
497 """
522 return d in self._dirs
498 return d in self._dirs
523
499
524 def hasdir(self, d):
500 def hasdir(self, d):
525 """
501 """
526 Returns True if the dirstate contains a file (tracked or removed)
502 Returns True if the dirstate contains a file (tracked or removed)
527 in this directory.
503 in this directory.
528 """
504 """
529 return d in self._alldirs
505 return d in self._alldirs
530
506
531 @propertycache
507 @propertycache
532 def _dirs(self):
508 def _dirs(self):
533 return pathutil.dirs(self._map, only_tracked=True)
509 return pathutil.dirs(self._map, only_tracked=True)
534
510
535 @propertycache
511 @propertycache
536 def _alldirs(self):
512 def _alldirs(self):
537 return pathutil.dirs(self._map)
513 return pathutil.dirs(self._map)
538
514
539 ### code related to manipulation of entries and copy-sources
515 ### code related to manipulation of entries and copy-sources
540
516
517 def set_tracked(self, filename):
518 new = False
519 entry = self.get(filename)
520 if entry is None:
521 self._dirs_incr(filename)
522 entry = DirstateItem(
523 wc_tracked=True,
524 )
525
526 self._insert_entry(filename, entry)
527 new = True
528 elif not entry.tracked:
529 self._dirs_incr(filename, entry)
530 entry.set_tracked()
531 self._refresh_entry(filename, entry)
532 new = True
533 else:
534 # XXX This is probably overkill for more case, but we need this to
535 # fully replace the `normallookup` call with `set_tracked` one.
536 # Consider smoothing this in the future.
537 entry.set_possibly_dirty()
538 self._refresh_entry(filename, entry)
539 return new
540
541 def _refresh_entry(self, f, entry):
541 def _refresh_entry(self, f, entry):
542 if not entry.any_tracked:
542 if not entry.any_tracked:
543 self._map.pop(f, None)
543 self._map.pop(f, None)
544
544
545 def _insert_entry(self, f, entry):
545 def _insert_entry(self, f, entry):
546 self._map[f] = entry
546 self._map[f] = entry
547
547
548 def _drop_entry(self, f):
548 def _drop_entry(self, f):
549 self._map.pop(f, None)
549 self._map.pop(f, None)
550 self.copymap.pop(f, None)
550 self.copymap.pop(f, None)
551
551
552
552
553 if rustmod is not None:
553 if rustmod is not None:
554
554
555 class dirstatemap(_dirstatemapcommon):
555 class dirstatemap(_dirstatemapcommon):
556
556
557 ### Core data storage and access
557 ### Core data storage and access
558
558
559 @propertycache
559 @propertycache
560 def _map(self):
560 def _map(self):
561 """
561 """
562 Fills the Dirstatemap when called.
562 Fills the Dirstatemap when called.
563 """
563 """
564 # ignore HG_PENDING because identity is used only for writing
564 # ignore HG_PENDING because identity is used only for writing
565 self.identity = util.filestat.frompath(
565 self.identity = util.filestat.frompath(
566 self._opener.join(self._filename)
566 self._opener.join(self._filename)
567 )
567 )
568
568
569 if self._use_dirstate_v2:
569 if self._use_dirstate_v2:
570 if self.docket.uuid:
570 if self.docket.uuid:
571 # TODO: use mmap when possible
571 # TODO: use mmap when possible
572 data = self._opener.read(self.docket.data_filename())
572 data = self._opener.read(self.docket.data_filename())
573 else:
573 else:
574 data = b''
574 data = b''
575 self._map = rustmod.DirstateMap.new_v2(
575 self._map = rustmod.DirstateMap.new_v2(
576 data, self.docket.data_size, self.docket.tree_metadata
576 data, self.docket.data_size, self.docket.tree_metadata
577 )
577 )
578 parents = self.docket.parents
578 parents = self.docket.parents
579 else:
579 else:
580 self._map, parents = rustmod.DirstateMap.new_v1(
580 self._map, parents = rustmod.DirstateMap.new_v1(
581 self._readdirstatefile()
581 self._readdirstatefile()
582 )
582 )
583
583
584 if parents and not self._dirtyparents:
584 if parents and not self._dirtyparents:
585 self.setparents(*parents)
585 self.setparents(*parents)
586
586
587 self.__contains__ = self._map.__contains__
587 self.__contains__ = self._map.__contains__
588 self.__getitem__ = self._map.__getitem__
588 self.__getitem__ = self._map.__getitem__
589 self.get = self._map.get
589 self.get = self._map.get
590 return self._map
590 return self._map
591
591
592 @property
592 @property
593 def copymap(self):
593 def copymap(self):
594 return self._map.copymap()
594 return self._map.copymap()
595
595
596 def debug_iter(self, all):
596 def debug_iter(self, all):
597 """
597 """
598 Return an iterator of (filename, state, mode, size, mtime) tuples
598 Return an iterator of (filename, state, mode, size, mtime) tuples
599
599
600 `all`: also include with `state == b' '` dirstate tree nodes that
600 `all`: also include with `state == b' '` dirstate tree nodes that
601 don't have an associated `DirstateItem`.
601 don't have an associated `DirstateItem`.
602
602
603 """
603 """
604 return self._map.debug_iter(all)
604 return self._map.debug_iter(all)
605
605
606 def clear(self):
606 def clear(self):
607 self._map.clear()
607 self._map.clear()
608 self.setparents(
608 self.setparents(
609 self._nodeconstants.nullid, self._nodeconstants.nullid
609 self._nodeconstants.nullid, self._nodeconstants.nullid
610 )
610 )
611 util.clearcachedproperty(self, b"_dirs")
611 util.clearcachedproperty(self, b"_dirs")
612 util.clearcachedproperty(self, b"_alldirs")
612 util.clearcachedproperty(self, b"_alldirs")
613 util.clearcachedproperty(self, b"dirfoldmap")
613 util.clearcachedproperty(self, b"dirfoldmap")
614
614
615 def items(self):
615 def items(self):
616 return self._map.items()
616 return self._map.items()
617
617
618 # forward for python2,3 compat
618 # forward for python2,3 compat
619 iteritems = items
619 iteritems = items
620
620
621 def keys(self):
621 def keys(self):
622 return iter(self._map)
622 return iter(self._map)
623
623
624 ### reading/setting parents
624 ### reading/setting parents
625
625
626 def setparents(self, p1, p2, fold_p2=False):
626 def setparents(self, p1, p2, fold_p2=False):
627 self._parents = (p1, p2)
627 self._parents = (p1, p2)
628 self._dirtyparents = True
628 self._dirtyparents = True
629 copies = {}
629 copies = {}
630 if fold_p2:
630 if fold_p2:
631 # Collect into an intermediate list to avoid a `RuntimeError`
631 # Collect into an intermediate list to avoid a `RuntimeError`
632 # exception due to mutation during iteration.
632 # exception due to mutation during iteration.
633 # TODO: move this the whole loop to Rust where `iter_mut`
633 # TODO: move this the whole loop to Rust where `iter_mut`
634 # enables in-place mutation of elements of a collection while
634 # enables in-place mutation of elements of a collection while
635 # iterating it, without mutating the collection itself.
635 # iterating it, without mutating the collection itself.
636 files_with_p2_info = [
636 files_with_p2_info = [
637 f for f, s in self._map.items() if s.p2_info
637 f for f, s in self._map.items() if s.p2_info
638 ]
638 ]
639 rust_map = self._map
639 rust_map = self._map
640 for f in files_with_p2_info:
640 for f in files_with_p2_info:
641 e = rust_map.get(f)
641 e = rust_map.get(f)
642 source = self.copymap.pop(f, None)
642 source = self.copymap.pop(f, None)
643 if source:
643 if source:
644 copies[f] = source
644 copies[f] = source
645 e.drop_merge_data()
645 e.drop_merge_data()
646 rust_map.set_dirstate_item(f, e)
646 rust_map.set_dirstate_item(f, e)
647 return copies
647 return copies
648
648
649 ### disk interaction
649 ### disk interaction
650
650
651 @propertycache
651 @propertycache
652 def identity(self):
652 def identity(self):
653 self._map
653 self._map
654 return self.identity
654 return self.identity
655
655
656 def write(self, tr, st):
656 def write(self, tr, st):
657 if not self._use_dirstate_v2:
657 if not self._use_dirstate_v2:
658 p1, p2 = self.parents()
658 p1, p2 = self.parents()
659 packed = self._map.write_v1(p1, p2)
659 packed = self._map.write_v1(p1, p2)
660 st.write(packed)
660 st.write(packed)
661 st.close()
661 st.close()
662 self._dirtyparents = False
662 self._dirtyparents = False
663 return
663 return
664
664
665 # We can only append to an existing data file if there is one
665 # We can only append to an existing data file if there is one
666 can_append = self.docket.uuid is not None
666 can_append = self.docket.uuid is not None
667 packed, meta, append = self._map.write_v2(can_append)
667 packed, meta, append = self._map.write_v2(can_append)
668 if append:
668 if append:
669 docket = self.docket
669 docket = self.docket
670 data_filename = docket.data_filename()
670 data_filename = docket.data_filename()
671 if tr:
671 if tr:
672 tr.add(data_filename, docket.data_size)
672 tr.add(data_filename, docket.data_size)
673 with self._opener(data_filename, b'r+b') as fp:
673 with self._opener(data_filename, b'r+b') as fp:
674 fp.seek(docket.data_size)
674 fp.seek(docket.data_size)
675 assert fp.tell() == docket.data_size
675 assert fp.tell() == docket.data_size
676 written = fp.write(packed)
676 written = fp.write(packed)
677 if written is not None: # py2 may return None
677 if written is not None: # py2 may return None
678 assert written == len(packed), (written, len(packed))
678 assert written == len(packed), (written, len(packed))
679 docket.data_size += len(packed)
679 docket.data_size += len(packed)
680 docket.parents = self.parents()
680 docket.parents = self.parents()
681 docket.tree_metadata = meta
681 docket.tree_metadata = meta
682 st.write(docket.serialize())
682 st.write(docket.serialize())
683 st.close()
683 st.close()
684 else:
684 else:
685 self.write_v2_no_append(tr, st, meta, packed)
685 self.write_v2_no_append(tr, st, meta, packed)
686 # Reload from the newly-written file
686 # Reload from the newly-written file
687 util.clearcachedproperty(self, b"_map")
687 util.clearcachedproperty(self, b"_map")
688 self._dirtyparents = False
688 self._dirtyparents = False
689
689
690 ### code related to maintaining and accessing "extra" property
690 ### code related to maintaining and accessing "extra" property
691 # (e.g. "has_dir")
691 # (e.g. "has_dir")
692
692
693 @propertycache
693 @propertycache
694 def filefoldmap(self):
694 def filefoldmap(self):
695 """Returns a dictionary mapping normalized case paths to their
695 """Returns a dictionary mapping normalized case paths to their
696 non-normalized versions.
696 non-normalized versions.
697 """
697 """
698 return self._map.filefoldmapasdict()
698 return self._map.filefoldmapasdict()
699
699
700 def hastrackeddir(self, d):
700 def hastrackeddir(self, d):
701 return self._map.hastrackeddir(d)
701 return self._map.hastrackeddir(d)
702
702
703 def hasdir(self, d):
703 def hasdir(self, d):
704 return self._map.hasdir(d)
704 return self._map.hasdir(d)
705
705
706 @propertycache
706 @propertycache
707 def dirfoldmap(self):
707 def dirfoldmap(self):
708 f = {}
708 f = {}
709 normcase = util.normcase
709 normcase = util.normcase
710 for name in self._map.tracked_dirs():
710 for name in self._map.tracked_dirs():
711 f[normcase(name)] = name
711 f[normcase(name)] = name
712 return f
712 return f
713
713
714 ### code related to manipulation of entries and copy-sources
714 ### code related to manipulation of entries and copy-sources
715
715
716 def _refresh_entry(self, f, entry):
716 def _refresh_entry(self, f, entry):
717 if not entry.any_tracked:
717 if not entry.any_tracked:
718 self._map.drop_item_and_copy_source(f)
718 self._map.drop_item_and_copy_source(f)
719 else:
719 else:
720 self._map.addfile(f, entry)
720 self._map.addfile(f, entry)
721
721
722 def _insert_entry(self, f, entry):
722 def _insert_entry(self, f, entry):
723 self._map.addfile(f, entry)
723 self._map.addfile(f, entry)
724
724
725 def set_tracked(self, f):
726 return self._map.set_tracked(f)
727
725 def _drop_entry(self, f):
728 def _drop_entry(self, f):
726 self._map.drop_item_and_copy_source(f)
729 self._map.drop_item_and_copy_source(f)
727
730
728 def __setitem__(self, key, value):
731 def __setitem__(self, key, value):
729 assert isinstance(value, DirstateItem)
732 assert isinstance(value, DirstateItem)
730 self._map.set_dirstate_item(key, value)
733 self._map.set_dirstate_item(key, value)
@@ -1,490 +1,500 b''
1 // dirstate_map.rs
1 // dirstate_map.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::{RefCell, RefMut};
11 use std::cell::{RefCell, RefMut};
12 use std::convert::TryInto;
12 use std::convert::TryInto;
13
13
14 use cpython::{
14 use cpython::{
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
17 };
17 };
18
18
19 use crate::{
19 use crate::{
20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
21 dirstate::item::DirstateItem,
21 dirstate::item::DirstateItem,
22 pybytes_deref::PyBytesDeref,
22 pybytes_deref::PyBytesDeref,
23 };
23 };
24 use hg::{
24 use hg::{
25 dirstate::StateMapIter,
25 dirstate::StateMapIter,
26 dirstate_tree::on_disk::DirstateV2ParseError,
26 dirstate_tree::on_disk::DirstateV2ParseError,
27 dirstate_tree::owning::OwningDirstateMap,
27 dirstate_tree::owning::OwningDirstateMap,
28 revlog::Node,
28 revlog::Node,
29 utils::files::normalize_case,
29 utils::files::normalize_case,
30 utils::hg_path::{HgPath, HgPathBuf},
30 utils::hg_path::{HgPath, HgPathBuf},
31 DirstateEntry, DirstateError, DirstateParents, EntryState,
31 DirstateEntry, DirstateError, DirstateParents, EntryState,
32 };
32 };
33
33
34 // TODO
34 // TODO
35 // This object needs to share references to multiple members of its Rust
35 // This object needs to share references to multiple members of its Rust
36 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
36 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
37 // Right now `CopyMap` is done, but it needs to have an explicit reference
37 // Right now `CopyMap` is done, but it needs to have an explicit reference
38 // to `RustDirstateMap` which itself needs to have an encapsulation for
38 // to `RustDirstateMap` which itself needs to have an encapsulation for
39 // every method in `CopyMap` (copymapcopy, etc.).
39 // every method in `CopyMap` (copymapcopy, etc.).
40 // This is ugly and hard to maintain.
40 // This is ugly and hard to maintain.
41 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
41 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
42 // `py_class!` is already implemented and does not mention
42 // `py_class!` is already implemented and does not mention
43 // `RustDirstateMap`, rightfully so.
43 // `RustDirstateMap`, rightfully so.
44 // All attributes also have to have a separate refcount data attribute for
44 // All attributes also have to have a separate refcount data attribute for
45 // leaks, with all methods that go along for reference sharing.
45 // leaks, with all methods that go along for reference sharing.
46 py_class!(pub class DirstateMap |py| {
46 py_class!(pub class DirstateMap |py| {
47 @shared data inner: OwningDirstateMap;
47 @shared data inner: OwningDirstateMap;
48
48
49 /// Returns a `(dirstate_map, parents)` tuple
49 /// Returns a `(dirstate_map, parents)` tuple
50 @staticmethod
50 @staticmethod
51 def new_v1(
51 def new_v1(
52 on_disk: PyBytes,
52 on_disk: PyBytes,
53 ) -> PyResult<PyObject> {
53 ) -> PyResult<PyObject> {
54 let on_disk = PyBytesDeref::new(py, on_disk);
54 let on_disk = PyBytesDeref::new(py, on_disk);
55 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
55 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
56 .map_err(|e| dirstate_error(py, e))?;
56 .map_err(|e| dirstate_error(py, e))?;
57 let map = Self::create_instance(py, map)?;
57 let map = Self::create_instance(py, map)?;
58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
59 let p2 = PyBytes::new(py, parents.p2.as_bytes());
59 let p2 = PyBytes::new(py, parents.p2.as_bytes());
60 let parents = (p1, p2);
60 let parents = (p1, p2);
61 Ok((map, parents).to_py_object(py).into_object())
61 Ok((map, parents).to_py_object(py).into_object())
62 }
62 }
63
63
64 /// Returns a DirstateMap
64 /// Returns a DirstateMap
65 @staticmethod
65 @staticmethod
66 def new_v2(
66 def new_v2(
67 on_disk: PyBytes,
67 on_disk: PyBytes,
68 data_size: usize,
68 data_size: usize,
69 tree_metadata: PyBytes,
69 tree_metadata: PyBytes,
70 ) -> PyResult<PyObject> {
70 ) -> PyResult<PyObject> {
71 let dirstate_error = |e: DirstateError| {
71 let dirstate_error = |e: DirstateError| {
72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
73 };
73 };
74 let on_disk = PyBytesDeref::new(py, on_disk);
74 let on_disk = PyBytesDeref::new(py, on_disk);
75 let map = OwningDirstateMap::new_v2(
75 let map = OwningDirstateMap::new_v2(
76 on_disk, data_size, tree_metadata.data(py),
76 on_disk, data_size, tree_metadata.data(py),
77 ).map_err(dirstate_error)?;
77 ).map_err(dirstate_error)?;
78 let map = Self::create_instance(py, map)?;
78 let map = Self::create_instance(py, map)?;
79 Ok(map.into_object())
79 Ok(map.into_object())
80 }
80 }
81
81
82 def clear(&self) -> PyResult<PyObject> {
82 def clear(&self) -> PyResult<PyObject> {
83 self.inner(py).borrow_mut().clear();
83 self.inner(py).borrow_mut().clear();
84 Ok(py.None())
84 Ok(py.None())
85 }
85 }
86
86
87 def get(
87 def get(
88 &self,
88 &self,
89 key: PyObject,
89 key: PyObject,
90 default: Option<PyObject> = None
90 default: Option<PyObject> = None
91 ) -> PyResult<Option<PyObject>> {
91 ) -> PyResult<Option<PyObject>> {
92 let key = key.extract::<PyBytes>(py)?;
92 let key = key.extract::<PyBytes>(py)?;
93 match self
93 match self
94 .inner(py)
94 .inner(py)
95 .borrow()
95 .borrow()
96 .get(HgPath::new(key.data(py)))
96 .get(HgPath::new(key.data(py)))
97 .map_err(|e| v2_error(py, e))?
97 .map_err(|e| v2_error(py, e))?
98 {
98 {
99 Some(entry) => {
99 Some(entry) => {
100 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
100 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
101 },
101 },
102 None => Ok(default)
102 None => Ok(default)
103 }
103 }
104 }
104 }
105
105
106 def set_dirstate_item(
106 def set_dirstate_item(
107 &self,
107 &self,
108 path: PyObject,
108 path: PyObject,
109 item: DirstateItem
109 item: DirstateItem
110 ) -> PyResult<PyObject> {
110 ) -> PyResult<PyObject> {
111 let f = path.extract::<PyBytes>(py)?;
111 let f = path.extract::<PyBytes>(py)?;
112 let filename = HgPath::new(f.data(py));
112 let filename = HgPath::new(f.data(py));
113 self.inner(py)
113 self.inner(py)
114 .borrow_mut()
114 .borrow_mut()
115 .set_entry(filename, item.get_entry(py))
115 .set_entry(filename, item.get_entry(py))
116 .map_err(|e| v2_error(py, e))?;
116 .map_err(|e| v2_error(py, e))?;
117 Ok(py.None())
117 Ok(py.None())
118 }
118 }
119
119
120 def addfile(
120 def addfile(
121 &self,
121 &self,
122 f: PyBytes,
122 f: PyBytes,
123 item: DirstateItem,
123 item: DirstateItem,
124 ) -> PyResult<PyNone> {
124 ) -> PyResult<PyNone> {
125 let filename = HgPath::new(f.data(py));
125 let filename = HgPath::new(f.data(py));
126 let entry = item.get_entry(py);
126 let entry = item.get_entry(py);
127 self.inner(py)
127 self.inner(py)
128 .borrow_mut()
128 .borrow_mut()
129 .add_file(filename, entry)
129 .add_file(filename, entry)
130 .map_err(|e |dirstate_error(py, e))?;
130 .map_err(|e |dirstate_error(py, e))?;
131 Ok(PyNone)
131 Ok(PyNone)
132 }
132 }
133
133
134 def set_tracked(&self, f: PyObject) -> PyResult<PyBool> {
135 let bytes = f.extract::<PyBytes>(py)?;
136 let path = HgPath::new(bytes.data(py));
137 let res = self.inner(py).borrow_mut().set_tracked(path);
138 let was_tracked = res.or_else(|_| {
139 Err(PyErr::new::<exc::OSError, _>(py, "Dirstate error".to_string()))
140 })?;
141 Ok(was_tracked.to_py_object(py))
142 }
143
134 def removefile(
144 def removefile(
135 &self,
145 &self,
136 f: PyObject,
146 f: PyObject,
137 in_merge: PyObject
147 in_merge: PyObject
138 ) -> PyResult<PyObject> {
148 ) -> PyResult<PyObject> {
139 self.inner(py).borrow_mut()
149 self.inner(py).borrow_mut()
140 .remove_file(
150 .remove_file(
141 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
151 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
142 in_merge.extract::<PyBool>(py)?.is_true(),
152 in_merge.extract::<PyBool>(py)?.is_true(),
143 )
153 )
144 .or_else(|_| {
154 .or_else(|_| {
145 Err(PyErr::new::<exc::OSError, _>(
155 Err(PyErr::new::<exc::OSError, _>(
146 py,
156 py,
147 "Dirstate error".to_string(),
157 "Dirstate error".to_string(),
148 ))
158 ))
149 })?;
159 })?;
150 Ok(py.None())
160 Ok(py.None())
151 }
161 }
152
162
153 def drop_item_and_copy_source(
163 def drop_item_and_copy_source(
154 &self,
164 &self,
155 f: PyBytes,
165 f: PyBytes,
156 ) -> PyResult<PyNone> {
166 ) -> PyResult<PyNone> {
157 self.inner(py)
167 self.inner(py)
158 .borrow_mut()
168 .borrow_mut()
159 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
169 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
160 .map_err(|e |dirstate_error(py, e))?;
170 .map_err(|e |dirstate_error(py, e))?;
161 Ok(PyNone)
171 Ok(PyNone)
162 }
172 }
163
173
164 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
174 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
165 let d = d.extract::<PyBytes>(py)?;
175 let d = d.extract::<PyBytes>(py)?;
166 Ok(self.inner(py).borrow_mut()
176 Ok(self.inner(py).borrow_mut()
167 .has_tracked_dir(HgPath::new(d.data(py)))
177 .has_tracked_dir(HgPath::new(d.data(py)))
168 .map_err(|e| {
178 .map_err(|e| {
169 PyErr::new::<exc::ValueError, _>(py, e.to_string())
179 PyErr::new::<exc::ValueError, _>(py, e.to_string())
170 })?
180 })?
171 .to_py_object(py))
181 .to_py_object(py))
172 }
182 }
173
183
174 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
184 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
175 let d = d.extract::<PyBytes>(py)?;
185 let d = d.extract::<PyBytes>(py)?;
176 Ok(self.inner(py).borrow_mut()
186 Ok(self.inner(py).borrow_mut()
177 .has_dir(HgPath::new(d.data(py)))
187 .has_dir(HgPath::new(d.data(py)))
178 .map_err(|e| {
188 .map_err(|e| {
179 PyErr::new::<exc::ValueError, _>(py, e.to_string())
189 PyErr::new::<exc::ValueError, _>(py, e.to_string())
180 })?
190 })?
181 .to_py_object(py))
191 .to_py_object(py))
182 }
192 }
183
193
184 def write_v1(
194 def write_v1(
185 &self,
195 &self,
186 p1: PyObject,
196 p1: PyObject,
187 p2: PyObject,
197 p2: PyObject,
188 ) -> PyResult<PyBytes> {
198 ) -> PyResult<PyBytes> {
189 let inner = self.inner(py).borrow();
199 let inner = self.inner(py).borrow();
190 let parents = DirstateParents {
200 let parents = DirstateParents {
191 p1: extract_node_id(py, &p1)?,
201 p1: extract_node_id(py, &p1)?,
192 p2: extract_node_id(py, &p2)?,
202 p2: extract_node_id(py, &p2)?,
193 };
203 };
194 let result = inner.pack_v1(parents);
204 let result = inner.pack_v1(parents);
195 match result {
205 match result {
196 Ok(packed) => Ok(PyBytes::new(py, &packed)),
206 Ok(packed) => Ok(PyBytes::new(py, &packed)),
197 Err(_) => Err(PyErr::new::<exc::OSError, _>(
207 Err(_) => Err(PyErr::new::<exc::OSError, _>(
198 py,
208 py,
199 "Dirstate error".to_string(),
209 "Dirstate error".to_string(),
200 )),
210 )),
201 }
211 }
202 }
212 }
203
213
204 /// Returns new data together with whether that data should be appended to
214 /// Returns new data together with whether that data should be appended to
205 /// the existing data file whose content is at `self.on_disk` (True),
215 /// the existing data file whose content is at `self.on_disk` (True),
206 /// instead of written to a new data file (False).
216 /// instead of written to a new data file (False).
207 def write_v2(
217 def write_v2(
208 &self,
218 &self,
209 can_append: bool,
219 can_append: bool,
210 ) -> PyResult<PyObject> {
220 ) -> PyResult<PyObject> {
211 let inner = self.inner(py).borrow();
221 let inner = self.inner(py).borrow();
212 let result = inner.pack_v2(can_append);
222 let result = inner.pack_v2(can_append);
213 match result {
223 match result {
214 Ok((packed, tree_metadata, append)) => {
224 Ok((packed, tree_metadata, append)) => {
215 let packed = PyBytes::new(py, &packed);
225 let packed = PyBytes::new(py, &packed);
216 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
226 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
217 let tuple = (packed, tree_metadata, append);
227 let tuple = (packed, tree_metadata, append);
218 Ok(tuple.to_py_object(py).into_object())
228 Ok(tuple.to_py_object(py).into_object())
219 },
229 },
220 Err(_) => Err(PyErr::new::<exc::OSError, _>(
230 Err(_) => Err(PyErr::new::<exc::OSError, _>(
221 py,
231 py,
222 "Dirstate error".to_string(),
232 "Dirstate error".to_string(),
223 )),
233 )),
224 }
234 }
225 }
235 }
226
236
227 def filefoldmapasdict(&self) -> PyResult<PyDict> {
237 def filefoldmapasdict(&self) -> PyResult<PyDict> {
228 let dict = PyDict::new(py);
238 let dict = PyDict::new(py);
229 for item in self.inner(py).borrow_mut().iter() {
239 for item in self.inner(py).borrow_mut().iter() {
230 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
240 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
231 if entry.state() != EntryState::Removed {
241 if entry.state() != EntryState::Removed {
232 let key = normalize_case(path);
242 let key = normalize_case(path);
233 let value = path;
243 let value = path;
234 dict.set_item(
244 dict.set_item(
235 py,
245 py,
236 PyBytes::new(py, key.as_bytes()).into_object(),
246 PyBytes::new(py, key.as_bytes()).into_object(),
237 PyBytes::new(py, value.as_bytes()).into_object(),
247 PyBytes::new(py, value.as_bytes()).into_object(),
238 )?;
248 )?;
239 }
249 }
240 }
250 }
241 Ok(dict)
251 Ok(dict)
242 }
252 }
243
253
244 def __len__(&self) -> PyResult<usize> {
254 def __len__(&self) -> PyResult<usize> {
245 Ok(self.inner(py).borrow().len())
255 Ok(self.inner(py).borrow().len())
246 }
256 }
247
257
248 def __contains__(&self, key: PyObject) -> PyResult<bool> {
258 def __contains__(&self, key: PyObject) -> PyResult<bool> {
249 let key = key.extract::<PyBytes>(py)?;
259 let key = key.extract::<PyBytes>(py)?;
250 self.inner(py)
260 self.inner(py)
251 .borrow()
261 .borrow()
252 .contains_key(HgPath::new(key.data(py)))
262 .contains_key(HgPath::new(key.data(py)))
253 .map_err(|e| v2_error(py, e))
263 .map_err(|e| v2_error(py, e))
254 }
264 }
255
265
256 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
266 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
257 let key = key.extract::<PyBytes>(py)?;
267 let key = key.extract::<PyBytes>(py)?;
258 let key = HgPath::new(key.data(py));
268 let key = HgPath::new(key.data(py));
259 match self
269 match self
260 .inner(py)
270 .inner(py)
261 .borrow()
271 .borrow()
262 .get(key)
272 .get(key)
263 .map_err(|e| v2_error(py, e))?
273 .map_err(|e| v2_error(py, e))?
264 {
274 {
265 Some(entry) => {
275 Some(entry) => {
266 Ok(DirstateItem::new_as_pyobject(py, entry)?)
276 Ok(DirstateItem::new_as_pyobject(py, entry)?)
267 },
277 },
268 None => Err(PyErr::new::<exc::KeyError, _>(
278 None => Err(PyErr::new::<exc::KeyError, _>(
269 py,
279 py,
270 String::from_utf8_lossy(key.as_bytes()),
280 String::from_utf8_lossy(key.as_bytes()),
271 )),
281 )),
272 }
282 }
273 }
283 }
274
284
275 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
285 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
276 let leaked_ref = self.inner(py).leak_immutable();
286 let leaked_ref = self.inner(py).leak_immutable();
277 DirstateMapKeysIterator::from_inner(
287 DirstateMapKeysIterator::from_inner(
278 py,
288 py,
279 unsafe { leaked_ref.map(py, |o| o.iter()) },
289 unsafe { leaked_ref.map(py, |o| o.iter()) },
280 )
290 )
281 }
291 }
282
292
283 def items(&self) -> PyResult<DirstateMapItemsIterator> {
293 def items(&self) -> PyResult<DirstateMapItemsIterator> {
284 let leaked_ref = self.inner(py).leak_immutable();
294 let leaked_ref = self.inner(py).leak_immutable();
285 DirstateMapItemsIterator::from_inner(
295 DirstateMapItemsIterator::from_inner(
286 py,
296 py,
287 unsafe { leaked_ref.map(py, |o| o.iter()) },
297 unsafe { leaked_ref.map(py, |o| o.iter()) },
288 )
298 )
289 }
299 }
290
300
291 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
301 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
292 let leaked_ref = self.inner(py).leak_immutable();
302 let leaked_ref = self.inner(py).leak_immutable();
293 DirstateMapKeysIterator::from_inner(
303 DirstateMapKeysIterator::from_inner(
294 py,
304 py,
295 unsafe { leaked_ref.map(py, |o| o.iter()) },
305 unsafe { leaked_ref.map(py, |o| o.iter()) },
296 )
306 )
297 }
307 }
298
308
299 // TODO all copymap* methods, see docstring above
309 // TODO all copymap* methods, see docstring above
300 def copymapcopy(&self) -> PyResult<PyDict> {
310 def copymapcopy(&self) -> PyResult<PyDict> {
301 let dict = PyDict::new(py);
311 let dict = PyDict::new(py);
302 for item in self.inner(py).borrow().copy_map_iter() {
312 for item in self.inner(py).borrow().copy_map_iter() {
303 let (key, value) = item.map_err(|e| v2_error(py, e))?;
313 let (key, value) = item.map_err(|e| v2_error(py, e))?;
304 dict.set_item(
314 dict.set_item(
305 py,
315 py,
306 PyBytes::new(py, key.as_bytes()),
316 PyBytes::new(py, key.as_bytes()),
307 PyBytes::new(py, value.as_bytes()),
317 PyBytes::new(py, value.as_bytes()),
308 )?;
318 )?;
309 }
319 }
310 Ok(dict)
320 Ok(dict)
311 }
321 }
312
322
313 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
323 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
314 let key = key.extract::<PyBytes>(py)?;
324 let key = key.extract::<PyBytes>(py)?;
315 match self
325 match self
316 .inner(py)
326 .inner(py)
317 .borrow()
327 .borrow()
318 .copy_map_get(HgPath::new(key.data(py)))
328 .copy_map_get(HgPath::new(key.data(py)))
319 .map_err(|e| v2_error(py, e))?
329 .map_err(|e| v2_error(py, e))?
320 {
330 {
321 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
331 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
322 None => Err(PyErr::new::<exc::KeyError, _>(
332 None => Err(PyErr::new::<exc::KeyError, _>(
323 py,
333 py,
324 String::from_utf8_lossy(key.data(py)),
334 String::from_utf8_lossy(key.data(py)),
325 )),
335 )),
326 }
336 }
327 }
337 }
328 def copymap(&self) -> PyResult<CopyMap> {
338 def copymap(&self) -> PyResult<CopyMap> {
329 CopyMap::from_inner(py, self.clone_ref(py))
339 CopyMap::from_inner(py, self.clone_ref(py))
330 }
340 }
331
341
332 def copymaplen(&self) -> PyResult<usize> {
342 def copymaplen(&self) -> PyResult<usize> {
333 Ok(self.inner(py).borrow().copy_map_len())
343 Ok(self.inner(py).borrow().copy_map_len())
334 }
344 }
335 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
345 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
336 let key = key.extract::<PyBytes>(py)?;
346 let key = key.extract::<PyBytes>(py)?;
337 self.inner(py)
347 self.inner(py)
338 .borrow()
348 .borrow()
339 .copy_map_contains_key(HgPath::new(key.data(py)))
349 .copy_map_contains_key(HgPath::new(key.data(py)))
340 .map_err(|e| v2_error(py, e))
350 .map_err(|e| v2_error(py, e))
341 }
351 }
342 def copymapget(
352 def copymapget(
343 &self,
353 &self,
344 key: PyObject,
354 key: PyObject,
345 default: Option<PyObject>
355 default: Option<PyObject>
346 ) -> PyResult<Option<PyObject>> {
356 ) -> PyResult<Option<PyObject>> {
347 let key = key.extract::<PyBytes>(py)?;
357 let key = key.extract::<PyBytes>(py)?;
348 match self
358 match self
349 .inner(py)
359 .inner(py)
350 .borrow()
360 .borrow()
351 .copy_map_get(HgPath::new(key.data(py)))
361 .copy_map_get(HgPath::new(key.data(py)))
352 .map_err(|e| v2_error(py, e))?
362 .map_err(|e| v2_error(py, e))?
353 {
363 {
354 Some(copy) => Ok(Some(
364 Some(copy) => Ok(Some(
355 PyBytes::new(py, copy.as_bytes()).into_object(),
365 PyBytes::new(py, copy.as_bytes()).into_object(),
356 )),
366 )),
357 None => Ok(default),
367 None => Ok(default),
358 }
368 }
359 }
369 }
360 def copymapsetitem(
370 def copymapsetitem(
361 &self,
371 &self,
362 key: PyObject,
372 key: PyObject,
363 value: PyObject
373 value: PyObject
364 ) -> PyResult<PyObject> {
374 ) -> PyResult<PyObject> {
365 let key = key.extract::<PyBytes>(py)?;
375 let key = key.extract::<PyBytes>(py)?;
366 let value = value.extract::<PyBytes>(py)?;
376 let value = value.extract::<PyBytes>(py)?;
367 self.inner(py)
377 self.inner(py)
368 .borrow_mut()
378 .borrow_mut()
369 .copy_map_insert(
379 .copy_map_insert(
370 HgPathBuf::from_bytes(key.data(py)),
380 HgPathBuf::from_bytes(key.data(py)),
371 HgPathBuf::from_bytes(value.data(py)),
381 HgPathBuf::from_bytes(value.data(py)),
372 )
382 )
373 .map_err(|e| v2_error(py, e))?;
383 .map_err(|e| v2_error(py, e))?;
374 Ok(py.None())
384 Ok(py.None())
375 }
385 }
376 def copymappop(
386 def copymappop(
377 &self,
387 &self,
378 key: PyObject,
388 key: PyObject,
379 default: Option<PyObject>
389 default: Option<PyObject>
380 ) -> PyResult<Option<PyObject>> {
390 ) -> PyResult<Option<PyObject>> {
381 let key = key.extract::<PyBytes>(py)?;
391 let key = key.extract::<PyBytes>(py)?;
382 match self
392 match self
383 .inner(py)
393 .inner(py)
384 .borrow_mut()
394 .borrow_mut()
385 .copy_map_remove(HgPath::new(key.data(py)))
395 .copy_map_remove(HgPath::new(key.data(py)))
386 .map_err(|e| v2_error(py, e))?
396 .map_err(|e| v2_error(py, e))?
387 {
397 {
388 Some(copy) => Ok(Some(
398 Some(copy) => Ok(Some(
389 PyBytes::new(py, copy.as_bytes()).into_object(),
399 PyBytes::new(py, copy.as_bytes()).into_object(),
390 )),
400 )),
391 None => Ok(default),
401 None => Ok(default),
392 }
402 }
393 }
403 }
394
404
395 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
405 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
396 let leaked_ref = self.inner(py).leak_immutable();
406 let leaked_ref = self.inner(py).leak_immutable();
397 CopyMapKeysIterator::from_inner(
407 CopyMapKeysIterator::from_inner(
398 py,
408 py,
399 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
409 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
400 )
410 )
401 }
411 }
402
412
403 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
413 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
404 let leaked_ref = self.inner(py).leak_immutable();
414 let leaked_ref = self.inner(py).leak_immutable();
405 CopyMapItemsIterator::from_inner(
415 CopyMapItemsIterator::from_inner(
406 py,
416 py,
407 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
417 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
408 )
418 )
409 }
419 }
410
420
411 def tracked_dirs(&self) -> PyResult<PyList> {
421 def tracked_dirs(&self) -> PyResult<PyList> {
412 let dirs = PyList::new(py, &[]);
422 let dirs = PyList::new(py, &[]);
413 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
423 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
414 .map_err(|e |dirstate_error(py, e))?
424 .map_err(|e |dirstate_error(py, e))?
415 {
425 {
416 let path = path.map_err(|e| v2_error(py, e))?;
426 let path = path.map_err(|e| v2_error(py, e))?;
417 let path = PyBytes::new(py, path.as_bytes());
427 let path = PyBytes::new(py, path.as_bytes());
418 dirs.append(py, path.into_object())
428 dirs.append(py, path.into_object())
419 }
429 }
420 Ok(dirs)
430 Ok(dirs)
421 }
431 }
422
432
423 def debug_iter(&self, all: bool) -> PyResult<PyList> {
433 def debug_iter(&self, all: bool) -> PyResult<PyList> {
424 let dirs = PyList::new(py, &[]);
434 let dirs = PyList::new(py, &[]);
425 for item in self.inner(py).borrow().debug_iter(all) {
435 for item in self.inner(py).borrow().debug_iter(all) {
426 let (path, (state, mode, size, mtime)) =
436 let (path, (state, mode, size, mtime)) =
427 item.map_err(|e| v2_error(py, e))?;
437 item.map_err(|e| v2_error(py, e))?;
428 let path = PyBytes::new(py, path.as_bytes());
438 let path = PyBytes::new(py, path.as_bytes());
429 let item = (path, state, mode, size, mtime);
439 let item = (path, state, mode, size, mtime);
430 dirs.append(py, item.to_py_object(py).into_object())
440 dirs.append(py, item.to_py_object(py).into_object())
431 }
441 }
432 Ok(dirs)
442 Ok(dirs)
433 }
443 }
434 });
444 });
435
445
436 impl DirstateMap {
446 impl DirstateMap {
437 pub fn get_inner_mut<'a>(
447 pub fn get_inner_mut<'a>(
438 &'a self,
448 &'a self,
439 py: Python<'a>,
449 py: Python<'a>,
440 ) -> RefMut<'a, OwningDirstateMap> {
450 ) -> RefMut<'a, OwningDirstateMap> {
441 self.inner(py).borrow_mut()
451 self.inner(py).borrow_mut()
442 }
452 }
443 fn translate_key(
453 fn translate_key(
444 py: Python,
454 py: Python,
445 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
455 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
446 ) -> PyResult<Option<PyBytes>> {
456 ) -> PyResult<Option<PyBytes>> {
447 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
457 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
448 Ok(Some(PyBytes::new(py, f.as_bytes())))
458 Ok(Some(PyBytes::new(py, f.as_bytes())))
449 }
459 }
450 fn translate_key_value(
460 fn translate_key_value(
451 py: Python,
461 py: Python,
452 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
462 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
453 ) -> PyResult<Option<(PyBytes, PyObject)>> {
463 ) -> PyResult<Option<(PyBytes, PyObject)>> {
454 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
464 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
455 Ok(Some((
465 Ok(Some((
456 PyBytes::new(py, f.as_bytes()),
466 PyBytes::new(py, f.as_bytes()),
457 DirstateItem::new_as_pyobject(py, entry)?,
467 DirstateItem::new_as_pyobject(py, entry)?,
458 )))
468 )))
459 }
469 }
460 }
470 }
461
471
462 py_shared_iterator!(
472 py_shared_iterator!(
463 DirstateMapKeysIterator,
473 DirstateMapKeysIterator,
464 UnsafePyLeaked<StateMapIter<'static>>,
474 UnsafePyLeaked<StateMapIter<'static>>,
465 DirstateMap::translate_key,
475 DirstateMap::translate_key,
466 Option<PyBytes>
476 Option<PyBytes>
467 );
477 );
468
478
469 py_shared_iterator!(
479 py_shared_iterator!(
470 DirstateMapItemsIterator,
480 DirstateMapItemsIterator,
471 UnsafePyLeaked<StateMapIter<'static>>,
481 UnsafePyLeaked<StateMapIter<'static>>,
472 DirstateMap::translate_key_value,
482 DirstateMap::translate_key_value,
473 Option<(PyBytes, PyObject)>
483 Option<(PyBytes, PyObject)>
474 );
484 );
475
485
476 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
486 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
477 let bytes = obj.extract::<PyBytes>(py)?;
487 let bytes = obj.extract::<PyBytes>(py)?;
478 match bytes.data(py).try_into() {
488 match bytes.data(py).try_into() {
479 Ok(s) => Ok(s),
489 Ok(s) => Ok(s),
480 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
490 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
481 }
491 }
482 }
492 }
483
493
484 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
494 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
485 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
495 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
486 }
496 }
487
497
488 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
498 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
489 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
499 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
490 }
500 }
General Comments 0
You need to be logged in to leave comments. Login now