##// END OF EJS Templates
dirstate: fix some typos in docstrings...
Raphaël Gomès -
r49916:3abf799b default
parent child Browse files
Show More
@@ -1,730 +1,730
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 """incremente the dirstate counter if applicable
88 """increment the dirstate counter if applicable
89
89
90 This might be a no-op for some subclass 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 """decremente the dirstate counter if applicable
95 """decrement the dirstate counter if applicable
96
96
97 This might be a no-op for some subclass 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):
133 def set_tracked(self, filename):
134 new = False
134 new = False
135 entry = self.get(filename)
135 entry = self.get(filename)
136 if entry is None:
136 if entry is None:
137 self._dirs_incr(filename)
137 self._dirs_incr(filename)
138 entry = DirstateItem(
138 entry = DirstateItem(
139 wc_tracked=True,
139 wc_tracked=True,
140 )
140 )
141
141
142 self._insert_entry(filename, entry)
142 self._insert_entry(filename, entry)
143 new = True
143 new = True
144 elif not entry.tracked:
144 elif not entry.tracked:
145 self._dirs_incr(filename, entry)
145 self._dirs_incr(filename, entry)
146 entry.set_tracked()
146 entry.set_tracked()
147 self._refresh_entry(filename, entry)
147 self._refresh_entry(filename, entry)
148 new = True
148 new = True
149 else:
149 else:
150 # XXX This is probably overkill for more case, but we need this to
150 # XXX This is probably overkill for more case, but we need this to
151 # fully replace the `normallookup` call with `set_tracked` one.
151 # fully replace the `normallookup` call with `set_tracked` one.
152 # Consider smoothing this in the future.
152 # Consider smoothing this in the future.
153 entry.set_possibly_dirty()
153 entry.set_possibly_dirty()
154 self._refresh_entry(filename, entry)
154 self._refresh_entry(filename, entry)
155 return new
155 return new
156
156
157 def set_untracked(self, f):
157 def set_untracked(self, f):
158 """Mark a file as no longer tracked in the dirstate map"""
158 """Mark a file as no longer tracked in the dirstate map"""
159 entry = self.get(f)
159 entry = self.get(f)
160 if entry is None:
160 if entry is None:
161 return False
161 return False
162 else:
162 else:
163 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
163 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
164 if not entry.p2_info:
164 if not entry.p2_info:
165 self.copymap.pop(f, None)
165 self.copymap.pop(f, None)
166 entry.set_untracked()
166 entry.set_untracked()
167 self._refresh_entry(f, entry)
167 self._refresh_entry(f, entry)
168 return True
168 return True
169
169
170 def reset_state(
170 def reset_state(
171 self,
171 self,
172 filename,
172 filename,
173 wc_tracked=False,
173 wc_tracked=False,
174 p1_tracked=False,
174 p1_tracked=False,
175 p2_info=False,
175 p2_info=False,
176 has_meaningful_mtime=True,
176 has_meaningful_mtime=True,
177 has_meaningful_data=True,
177 has_meaningful_data=True,
178 parentfiledata=None,
178 parentfiledata=None,
179 ):
179 ):
180 """Set a entry to a given state, diregarding all previous state
180 """Set a entry to a given state, diregarding all previous state
181
181
182 This is to be used by the part of the dirstate API dedicated to
182 This is to be used by the part of the dirstate API dedicated to
183 adjusting the dirstate after a update/merge.
183 adjusting the dirstate after a update/merge.
184
184
185 note: calling this might result to no entry existing at all if the
185 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
186 dirstate map does not see any point at having one for this file
187 anymore.
187 anymore.
188 """
188 """
189 # copy information are now outdated
189 # copy information are now outdated
190 # (maybe new information should be in directly passed to this function)
190 # (maybe new information should be in directly passed to this function)
191 self.copymap.pop(filename, None)
191 self.copymap.pop(filename, None)
192
192
193 if not (p1_tracked or p2_info or wc_tracked):
193 if not (p1_tracked or p2_info or wc_tracked):
194 old_entry = self._map.get(filename)
194 old_entry = self._map.get(filename)
195 self._drop_entry(filename)
195 self._drop_entry(filename)
196 self._dirs_decr(filename, old_entry=old_entry)
196 self._dirs_decr(filename, old_entry=old_entry)
197 return
197 return
198
198
199 old_entry = self._map.get(filename)
199 old_entry = self._map.get(filename)
200 self._dirs_incr(filename, old_entry)
200 self._dirs_incr(filename, old_entry)
201 entry = DirstateItem(
201 entry = DirstateItem(
202 wc_tracked=wc_tracked,
202 wc_tracked=wc_tracked,
203 p1_tracked=p1_tracked,
203 p1_tracked=p1_tracked,
204 p2_info=p2_info,
204 p2_info=p2_info,
205 has_meaningful_mtime=has_meaningful_mtime,
205 has_meaningful_mtime=has_meaningful_mtime,
206 parentfiledata=parentfiledata,
206 parentfiledata=parentfiledata,
207 )
207 )
208 self._insert_entry(filename, entry)
208 self._insert_entry(filename, entry)
209
209
210 ### disk interaction
210 ### disk interaction
211
211
212 def _opendirstatefile(self):
212 def _opendirstatefile(self):
213 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
213 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
214 if self._pendingmode is not None and self._pendingmode != mode:
214 if self._pendingmode is not None and self._pendingmode != mode:
215 fp.close()
215 fp.close()
216 raise error.Abort(
216 raise error.Abort(
217 _(b'working directory state may be changed parallelly')
217 _(b'working directory state may be changed parallelly')
218 )
218 )
219 self._pendingmode = mode
219 self._pendingmode = mode
220 return fp
220 return fp
221
221
222 def _readdirstatefile(self, size=-1):
222 def _readdirstatefile(self, size=-1):
223 try:
223 try:
224 with self._opendirstatefile() as fp:
224 with self._opendirstatefile() as fp:
225 return fp.read(size)
225 return fp.read(size)
226 except IOError as err:
226 except IOError as err:
227 if err.errno != errno.ENOENT:
227 if err.errno != errno.ENOENT:
228 raise
228 raise
229 # File doesn't exist, so the current state is empty
229 # File doesn't exist, so the current state is empty
230 return b''
230 return b''
231
231
232 @property
232 @property
233 def docket(self):
233 def docket(self):
234 if not self._docket:
234 if not self._docket:
235 if not self._use_dirstate_v2:
235 if not self._use_dirstate_v2:
236 raise error.ProgrammingError(
236 raise error.ProgrammingError(
237 b'dirstate only has a docket in v2 format'
237 b'dirstate only has a docket in v2 format'
238 )
238 )
239 self._docket = docketmod.DirstateDocket.parse(
239 self._docket = docketmod.DirstateDocket.parse(
240 self._readdirstatefile(), self._nodeconstants
240 self._readdirstatefile(), self._nodeconstants
241 )
241 )
242 return self._docket
242 return self._docket
243
243
244 def write_v2_no_append(self, tr, st, meta, packed):
244 def write_v2_no_append(self, tr, st, meta, packed):
245 old_docket = self.docket
245 old_docket = self.docket
246 new_docket = docketmod.DirstateDocket.with_new_uuid(
246 new_docket = docketmod.DirstateDocket.with_new_uuid(
247 self.parents(), len(packed), meta
247 self.parents(), len(packed), meta
248 )
248 )
249 data_filename = new_docket.data_filename()
249 data_filename = new_docket.data_filename()
250 if tr:
250 if tr:
251 tr.add(data_filename, 0)
251 tr.add(data_filename, 0)
252 self._opener.write(data_filename, packed)
252 self._opener.write(data_filename, packed)
253 # Write the new docket after the new data file has been
253 # Write the new docket after the new data file has been
254 # written. Because `st` was opened with `atomictemp=True`,
254 # written. Because `st` was opened with `atomictemp=True`,
255 # the actual `.hg/dirstate` file is only affected on close.
255 # the actual `.hg/dirstate` file is only affected on close.
256 st.write(new_docket.serialize())
256 st.write(new_docket.serialize())
257 st.close()
257 st.close()
258 # Remove the old data file after the new docket pointing to
258 # Remove the old data file after the new docket pointing to
259 # the new data file was written.
259 # the new data file was written.
260 if old_docket.uuid:
260 if old_docket.uuid:
261 data_filename = old_docket.data_filename()
261 data_filename = old_docket.data_filename()
262 unlink = lambda _tr=None: self._opener.unlink(data_filename)
262 unlink = lambda _tr=None: self._opener.unlink(data_filename)
263 if tr:
263 if tr:
264 category = b"dirstate-v2-clean-" + old_docket.uuid
264 category = b"dirstate-v2-clean-" + old_docket.uuid
265 tr.addpostclose(category, unlink)
265 tr.addpostclose(category, unlink)
266 else:
266 else:
267 unlink()
267 unlink()
268 self._docket = new_docket
268 self._docket = new_docket
269
269
270 ### reading/setting parents
270 ### reading/setting parents
271
271
272 def parents(self):
272 def parents(self):
273 if not self._parents:
273 if not self._parents:
274 if self._use_dirstate_v2:
274 if self._use_dirstate_v2:
275 self._parents = self.docket.parents
275 self._parents = self.docket.parents
276 else:
276 else:
277 read_len = self._nodelen * 2
277 read_len = self._nodelen * 2
278 st = self._readdirstatefile(read_len)
278 st = self._readdirstatefile(read_len)
279 l = len(st)
279 l = len(st)
280 if l == read_len:
280 if l == read_len:
281 self._parents = (
281 self._parents = (
282 st[: self._nodelen],
282 st[: self._nodelen],
283 st[self._nodelen : 2 * self._nodelen],
283 st[self._nodelen : 2 * self._nodelen],
284 )
284 )
285 elif l == 0:
285 elif l == 0:
286 self._parents = (
286 self._parents = (
287 self._nodeconstants.nullid,
287 self._nodeconstants.nullid,
288 self._nodeconstants.nullid,
288 self._nodeconstants.nullid,
289 )
289 )
290 else:
290 else:
291 raise error.Abort(
291 raise error.Abort(
292 _(b'working directory state appears damaged!')
292 _(b'working directory state appears damaged!')
293 )
293 )
294
294
295 return self._parents
295 return self._parents
296
296
297
297
298 class dirstatemap(_dirstatemapcommon):
298 class dirstatemap(_dirstatemapcommon):
299 """Map encapsulating the dirstate's contents.
299 """Map encapsulating the dirstate's contents.
300
300
301 The dirstate contains the following state:
301 The dirstate contains the following state:
302
302
303 - `identity` is the identity of the dirstate file, which can be used to
303 - `identity` is the identity of the dirstate file, which can be used to
304 detect when changes have occurred to the dirstate file.
304 detect when changes have occurred to the dirstate file.
305
305
306 - `parents` is a pair containing the parents of the working copy. The
306 - `parents` is a pair containing the parents of the working copy. The
307 parents are updated by calling `setparents`.
307 parents are updated by calling `setparents`.
308
308
309 - the state map maps filenames to tuples of (state, mode, size, mtime),
309 - the state map maps filenames to tuples of (state, mode, size, mtime),
310 where state is a single character representing 'normal', 'added',
310 where state is a single character representing 'normal', 'added',
311 'removed', or 'merged'. It is read by treating the dirstate as a
311 'removed', or 'merged'. It is read by treating the dirstate as a
312 dict. File state is updated by calling various methods (see each
312 dict. File state is updated by calling various methods (see each
313 documentation for details):
313 documentation for details):
314
314
315 - `reset_state`,
315 - `reset_state`,
316 - `set_tracked`
316 - `set_tracked`
317 - `set_untracked`
317 - `set_untracked`
318 - `set_clean`
318 - `set_clean`
319 - `set_possibly_dirty`
319 - `set_possibly_dirty`
320
320
321 - `copymap` maps destination filenames to their source filename.
321 - `copymap` maps destination filenames to their source filename.
322
322
323 The dirstate also provides the following views onto the state:
323 The dirstate also provides the following views onto the state:
324
324
325 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
325 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
326 form that they appear as in the dirstate.
326 form that they appear as in the dirstate.
327
327
328 - `dirfoldmap` is a dict mapping normalized directory names to the
328 - `dirfoldmap` is a dict mapping normalized directory names to the
329 denormalized form that they appear as in the dirstate.
329 denormalized form that they appear as in the dirstate.
330 """
330 """
331
331
332 ### Core data storage and access
332 ### Core data storage and access
333
333
334 @propertycache
334 @propertycache
335 def _map(self):
335 def _map(self):
336 self._map = {}
336 self._map = {}
337 self.read()
337 self.read()
338 return self._map
338 return self._map
339
339
340 @propertycache
340 @propertycache
341 def copymap(self):
341 def copymap(self):
342 self.copymap = {}
342 self.copymap = {}
343 self._map
343 self._map
344 return self.copymap
344 return self.copymap
345
345
346 def clear(self):
346 def clear(self):
347 self._map.clear()
347 self._map.clear()
348 self.copymap.clear()
348 self.copymap.clear()
349 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
349 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
350 util.clearcachedproperty(self, b"_dirs")
350 util.clearcachedproperty(self, b"_dirs")
351 util.clearcachedproperty(self, b"_alldirs")
351 util.clearcachedproperty(self, b"_alldirs")
352 util.clearcachedproperty(self, b"filefoldmap")
352 util.clearcachedproperty(self, b"filefoldmap")
353 util.clearcachedproperty(self, b"dirfoldmap")
353 util.clearcachedproperty(self, b"dirfoldmap")
354
354
355 def items(self):
355 def items(self):
356 return self._map.items()
356 return self._map.items()
357
357
358 # forward for python2,3 compat
358 # forward for python2,3 compat
359 iteritems = items
359 iteritems = items
360
360
361 def debug_iter(self, all):
361 def debug_iter(self, all):
362 """
362 """
363 Return an iterator of (filename, state, mode, size, mtime) tuples
363 Return an iterator of (filename, state, mode, size, mtime) tuples
364
364
365 `all` is unused when Rust is not enabled
365 `all` is unused when Rust is not enabled
366 """
366 """
367 for (filename, item) in self.items():
367 for (filename, item) in self.items():
368 yield (filename, item.state, item.mode, item.size, item.mtime)
368 yield (filename, item.state, item.mode, item.size, item.mtime)
369
369
370 def keys(self):
370 def keys(self):
371 return self._map.keys()
371 return self._map.keys()
372
372
373 ### reading/setting parents
373 ### reading/setting parents
374
374
375 def setparents(self, p1, p2, fold_p2=False):
375 def setparents(self, p1, p2, fold_p2=False):
376 self._parents = (p1, p2)
376 self._parents = (p1, p2)
377 self._dirtyparents = True
377 self._dirtyparents = True
378 copies = {}
378 copies = {}
379 if fold_p2:
379 if fold_p2:
380 for f, s in self._map.items():
380 for f, s in self._map.items():
381 # Discard "merged" markers when moving away from a merge state
381 # Discard "merged" markers when moving away from a merge state
382 if s.p2_info:
382 if s.p2_info:
383 source = self.copymap.pop(f, None)
383 source = self.copymap.pop(f, None)
384 if source:
384 if source:
385 copies[f] = source
385 copies[f] = source
386 s.drop_merge_data()
386 s.drop_merge_data()
387 return copies
387 return copies
388
388
389 ### disk interaction
389 ### disk interaction
390
390
391 def read(self):
391 def read(self):
392 # ignore HG_PENDING because identity is used only for writing
392 # ignore HG_PENDING because identity is used only for writing
393 self.identity = util.filestat.frompath(
393 self.identity = util.filestat.frompath(
394 self._opener.join(self._filename)
394 self._opener.join(self._filename)
395 )
395 )
396
396
397 if self._use_dirstate_v2:
397 if self._use_dirstate_v2:
398 if not self.docket.uuid:
398 if not self.docket.uuid:
399 return
399 return
400 st = self._opener.read(self.docket.data_filename())
400 st = self._opener.read(self.docket.data_filename())
401 else:
401 else:
402 st = self._readdirstatefile()
402 st = self._readdirstatefile()
403
403
404 if not st:
404 if not st:
405 return
405 return
406
406
407 # TODO: adjust this estimate for dirstate-v2
407 # TODO: adjust this estimate for dirstate-v2
408 if util.safehasattr(parsers, b'dict_new_presized'):
408 if util.safehasattr(parsers, b'dict_new_presized'):
409 # Make an estimate of the number of files in the dirstate based on
409 # Make an estimate of the number of files in the dirstate based on
410 # its size. This trades wasting some memory for avoiding costly
410 # its size. This trades wasting some memory for avoiding costly
411 # resizes. Each entry have a prefix of 17 bytes followed by one or
411 # 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
412 # two path names. Studies on various large-scale real-world repositories
413 # found 54 bytes a reasonable upper limit for the average path names.
413 # found 54 bytes a reasonable upper limit for the average path names.
414 # Copy entries are ignored for the sake of this estimate.
414 # Copy entries are ignored for the sake of this estimate.
415 self._map = parsers.dict_new_presized(len(st) // 71)
415 self._map = parsers.dict_new_presized(len(st) // 71)
416
416
417 # Python's garbage collector triggers a GC each time a certain number
417 # Python's garbage collector triggers a GC each time a certain number
418 # of container objects (the number being defined by
418 # of container objects (the number being defined by
419 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
419 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
420 # for each file in the dirstate. The C version then immediately marks
420 # 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
421 # 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
422 # 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.
423 # into. This means that O(number of files) GCs are unavoidable.
424 # Depending on when in the process's lifetime the dirstate is parsed,
424 # Depending on when in the process's lifetime the dirstate is parsed,
425 # this can get very expensive. As a workaround, disable GC while
425 # this can get very expensive. As a workaround, disable GC while
426 # parsing the dirstate.
426 # parsing the dirstate.
427 #
427 #
428 # (we cannot decorate the function directly since it is in a C module)
428 # (we cannot decorate the function directly since it is in a C module)
429 if self._use_dirstate_v2:
429 if self._use_dirstate_v2:
430 p = self.docket.parents
430 p = self.docket.parents
431 meta = self.docket.tree_metadata
431 meta = self.docket.tree_metadata
432 parse_dirstate = util.nogc(v2.parse_dirstate)
432 parse_dirstate = util.nogc(v2.parse_dirstate)
433 parse_dirstate(self._map, self.copymap, st, meta)
433 parse_dirstate(self._map, self.copymap, st, meta)
434 else:
434 else:
435 parse_dirstate = util.nogc(parsers.parse_dirstate)
435 parse_dirstate = util.nogc(parsers.parse_dirstate)
436 p = parse_dirstate(self._map, self.copymap, st)
436 p = parse_dirstate(self._map, self.copymap, st)
437 if not self._dirtyparents:
437 if not self._dirtyparents:
438 self.setparents(*p)
438 self.setparents(*p)
439
439
440 # Avoid excess attribute lookups by fast pathing certain checks
440 # Avoid excess attribute lookups by fast pathing certain checks
441 self.__contains__ = self._map.__contains__
441 self.__contains__ = self._map.__contains__
442 self.__getitem__ = self._map.__getitem__
442 self.__getitem__ = self._map.__getitem__
443 self.get = self._map.get
443 self.get = self._map.get
444
444
445 def write(self, tr, st):
445 def write(self, tr, st):
446 if self._use_dirstate_v2:
446 if self._use_dirstate_v2:
447 packed, meta = v2.pack_dirstate(self._map, self.copymap)
447 packed, meta = v2.pack_dirstate(self._map, self.copymap)
448 self.write_v2_no_append(tr, st, meta, packed)
448 self.write_v2_no_append(tr, st, meta, packed)
449 else:
449 else:
450 packed = parsers.pack_dirstate(
450 packed = parsers.pack_dirstate(
451 self._map, self.copymap, self.parents()
451 self._map, self.copymap, self.parents()
452 )
452 )
453 st.write(packed)
453 st.write(packed)
454 st.close()
454 st.close()
455 self._dirtyparents = False
455 self._dirtyparents = False
456
456
457 @propertycache
457 @propertycache
458 def identity(self):
458 def identity(self):
459 self._map
459 self._map
460 return self.identity
460 return self.identity
461
461
462 ### code related to maintaining and accessing "extra" property
462 ### code related to maintaining and accessing "extra" property
463 # (e.g. "has_dir")
463 # (e.g. "has_dir")
464
464
465 def _dirs_incr(self, filename, old_entry=None):
465 def _dirs_incr(self, filename, old_entry=None):
466 """incremente the dirstate counter if applicable"""
466 """incremente the dirstate counter if applicable"""
467 if (
467 if (
468 old_entry is None or old_entry.removed
468 old_entry is None or old_entry.removed
469 ) and "_dirs" in self.__dict__:
469 ) and "_dirs" in self.__dict__:
470 self._dirs.addpath(filename)
470 self._dirs.addpath(filename)
471 if old_entry is None and "_alldirs" in self.__dict__:
471 if old_entry is None and "_alldirs" in self.__dict__:
472 self._alldirs.addpath(filename)
472 self._alldirs.addpath(filename)
473
473
474 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
474 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
475 """decremente the dirstate counter if applicable"""
475 """decremente the dirstate counter if applicable"""
476 if old_entry is not None:
476 if old_entry is not None:
477 if "_dirs" in self.__dict__ and not old_entry.removed:
477 if "_dirs" in self.__dict__ and not old_entry.removed:
478 self._dirs.delpath(filename)
478 self._dirs.delpath(filename)
479 if "_alldirs" in self.__dict__ and not remove_variant:
479 if "_alldirs" in self.__dict__ and not remove_variant:
480 self._alldirs.delpath(filename)
480 self._alldirs.delpath(filename)
481 elif remove_variant and "_alldirs" in self.__dict__:
481 elif remove_variant and "_alldirs" in self.__dict__:
482 self._alldirs.addpath(filename)
482 self._alldirs.addpath(filename)
483 if "filefoldmap" in self.__dict__:
483 if "filefoldmap" in self.__dict__:
484 normed = util.normcase(filename)
484 normed = util.normcase(filename)
485 self.filefoldmap.pop(normed, None)
485 self.filefoldmap.pop(normed, None)
486
486
487 @propertycache
487 @propertycache
488 def filefoldmap(self):
488 def filefoldmap(self):
489 """Returns a dictionary mapping normalized case paths to their
489 """Returns a dictionary mapping normalized case paths to their
490 non-normalized versions.
490 non-normalized versions.
491 """
491 """
492 try:
492 try:
493 makefilefoldmap = parsers.make_file_foldmap
493 makefilefoldmap = parsers.make_file_foldmap
494 except AttributeError:
494 except AttributeError:
495 pass
495 pass
496 else:
496 else:
497 return makefilefoldmap(
497 return makefilefoldmap(
498 self._map, util.normcasespec, util.normcasefallback
498 self._map, util.normcasespec, util.normcasefallback
499 )
499 )
500
500
501 f = {}
501 f = {}
502 normcase = util.normcase
502 normcase = util.normcase
503 for name, s in self._map.items():
503 for name, s in self._map.items():
504 if not s.removed:
504 if not s.removed:
505 f[normcase(name)] = name
505 f[normcase(name)] = name
506 f[b'.'] = b'.' # prevents useless util.fspath() invocation
506 f[b'.'] = b'.' # prevents useless util.fspath() invocation
507 return f
507 return f
508
508
509 @propertycache
509 @propertycache
510 def dirfoldmap(self):
510 def dirfoldmap(self):
511 f = {}
511 f = {}
512 normcase = util.normcase
512 normcase = util.normcase
513 for name in self._dirs:
513 for name in self._dirs:
514 f[normcase(name)] = name
514 f[normcase(name)] = name
515 return f
515 return f
516
516
517 def hastrackeddir(self, d):
517 def hastrackeddir(self, d):
518 """
518 """
519 Returns True if the dirstate contains a tracked (not removed) file
519 Returns True if the dirstate contains a tracked (not removed) file
520 in this directory.
520 in this directory.
521 """
521 """
522 return d in self._dirs
522 return d in self._dirs
523
523
524 def hasdir(self, d):
524 def hasdir(self, d):
525 """
525 """
526 Returns True if the dirstate contains a file (tracked or removed)
526 Returns True if the dirstate contains a file (tracked or removed)
527 in this directory.
527 in this directory.
528 """
528 """
529 return d in self._alldirs
529 return d in self._alldirs
530
530
531 @propertycache
531 @propertycache
532 def _dirs(self):
532 def _dirs(self):
533 return pathutil.dirs(self._map, only_tracked=True)
533 return pathutil.dirs(self._map, only_tracked=True)
534
534
535 @propertycache
535 @propertycache
536 def _alldirs(self):
536 def _alldirs(self):
537 return pathutil.dirs(self._map)
537 return pathutil.dirs(self._map)
538
538
539 ### code related to manipulation of entries and copy-sources
539 ### code related to manipulation of entries and copy-sources
540
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 _drop_entry(self, f):
725 def _drop_entry(self, f):
726 self._map.drop_item_and_copy_source(f)
726 self._map.drop_item_and_copy_source(f)
727
727
728 def __setitem__(self, key, value):
728 def __setitem__(self, key, value):
729 assert isinstance(value, DirstateItem)
729 assert isinstance(value, DirstateItem)
730 self._map.set_dirstate_item(key, value)
730 self._map.set_dirstate_item(key, value)
General Comments 0
You need to be logged in to leave comments. Login now