##// END OF EJS Templates
rust-dirstatemap: remove `__settitem__`...
Raphaël Gomès -
r50005:d27d25b7 default
parent child Browse files
Show More
@@ -1,739 +1,735 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 _drop_entry(self, f):
104 def _drop_entry(self, f):
105 """remove any entry for file f
105 """remove any entry for file f
106
106
107 This should also drop associated copy information
107 This should also drop associated copy information
108
108
109 The fact we actually need to drop it is the responsability of the caller"""
109 The fact we actually need to drop it is the responsability of the caller"""
110
110
111 ### disk interaction
111 ### disk interaction
112
112
113 def _opendirstatefile(self):
113 def _opendirstatefile(self):
114 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
114 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
115 if self._pendingmode is not None and self._pendingmode != mode:
115 if self._pendingmode is not None and self._pendingmode != mode:
116 fp.close()
116 fp.close()
117 raise error.Abort(
117 raise error.Abort(
118 _(b'working directory state may be changed parallelly')
118 _(b'working directory state may be changed parallelly')
119 )
119 )
120 self._pendingmode = mode
120 self._pendingmode = mode
121 return fp
121 return fp
122
122
123 def _readdirstatefile(self, size=-1):
123 def _readdirstatefile(self, size=-1):
124 try:
124 try:
125 with self._opendirstatefile() as fp:
125 with self._opendirstatefile() as fp:
126 return fp.read(size)
126 return fp.read(size)
127 except IOError as err:
127 except IOError as err:
128 if err.errno != errno.ENOENT:
128 if err.errno != errno.ENOENT:
129 raise
129 raise
130 # File doesn't exist, so the current state is empty
130 # File doesn't exist, so the current state is empty
131 return b''
131 return b''
132
132
133 @property
133 @property
134 def docket(self):
134 def docket(self):
135 if not self._docket:
135 if not self._docket:
136 if not self._use_dirstate_v2:
136 if not self._use_dirstate_v2:
137 raise error.ProgrammingError(
137 raise error.ProgrammingError(
138 b'dirstate only has a docket in v2 format'
138 b'dirstate only has a docket in v2 format'
139 )
139 )
140 self._docket = docketmod.DirstateDocket.parse(
140 self._docket = docketmod.DirstateDocket.parse(
141 self._readdirstatefile(), self._nodeconstants
141 self._readdirstatefile(), self._nodeconstants
142 )
142 )
143 return self._docket
143 return self._docket
144
144
145 def write_v2_no_append(self, tr, st, meta, packed):
145 def write_v2_no_append(self, tr, st, meta, packed):
146 old_docket = self.docket
146 old_docket = self.docket
147 new_docket = docketmod.DirstateDocket.with_new_uuid(
147 new_docket = docketmod.DirstateDocket.with_new_uuid(
148 self.parents(), len(packed), meta
148 self.parents(), len(packed), meta
149 )
149 )
150 data_filename = new_docket.data_filename()
150 data_filename = new_docket.data_filename()
151 if tr:
151 if tr:
152 tr.add(data_filename, 0)
152 tr.add(data_filename, 0)
153 self._opener.write(data_filename, packed)
153 self._opener.write(data_filename, packed)
154 # Write the new docket after the new data file has been
154 # Write the new docket after the new data file has been
155 # written. Because `st` was opened with `atomictemp=True`,
155 # written. Because `st` was opened with `atomictemp=True`,
156 # the actual `.hg/dirstate` file is only affected on close.
156 # the actual `.hg/dirstate` file is only affected on close.
157 st.write(new_docket.serialize())
157 st.write(new_docket.serialize())
158 st.close()
158 st.close()
159 # Remove the old data file after the new docket pointing to
159 # Remove the old data file after the new docket pointing to
160 # the new data file was written.
160 # the new data file was written.
161 if old_docket.uuid:
161 if old_docket.uuid:
162 data_filename = old_docket.data_filename()
162 data_filename = old_docket.data_filename()
163 unlink = lambda _tr=None: self._opener.unlink(data_filename)
163 unlink = lambda _tr=None: self._opener.unlink(data_filename)
164 if tr:
164 if tr:
165 category = b"dirstate-v2-clean-" + old_docket.uuid
165 category = b"dirstate-v2-clean-" + old_docket.uuid
166 tr.addpostclose(category, unlink)
166 tr.addpostclose(category, unlink)
167 else:
167 else:
168 unlink()
168 unlink()
169 self._docket = new_docket
169 self._docket = new_docket
170
170
171 ### reading/setting parents
171 ### reading/setting parents
172
172
173 def parents(self):
173 def parents(self):
174 if not self._parents:
174 if not self._parents:
175 if self._use_dirstate_v2:
175 if self._use_dirstate_v2:
176 self._parents = self.docket.parents
176 self._parents = self.docket.parents
177 else:
177 else:
178 read_len = self._nodelen * 2
178 read_len = self._nodelen * 2
179 st = self._readdirstatefile(read_len)
179 st = self._readdirstatefile(read_len)
180 l = len(st)
180 l = len(st)
181 if l == read_len:
181 if l == read_len:
182 self._parents = (
182 self._parents = (
183 st[: self._nodelen],
183 st[: self._nodelen],
184 st[self._nodelen : 2 * self._nodelen],
184 st[self._nodelen : 2 * self._nodelen],
185 )
185 )
186 elif l == 0:
186 elif l == 0:
187 self._parents = (
187 self._parents = (
188 self._nodeconstants.nullid,
188 self._nodeconstants.nullid,
189 self._nodeconstants.nullid,
189 self._nodeconstants.nullid,
190 )
190 )
191 else:
191 else:
192 raise error.Abort(
192 raise error.Abort(
193 _(b'working directory state appears damaged!')
193 _(b'working directory state appears damaged!')
194 )
194 )
195
195
196 return self._parents
196 return self._parents
197
197
198
198
199 class dirstatemap(_dirstatemapcommon):
199 class dirstatemap(_dirstatemapcommon):
200 """Map encapsulating the dirstate's contents.
200 """Map encapsulating the dirstate's contents.
201
201
202 The dirstate contains the following state:
202 The dirstate contains the following state:
203
203
204 - `identity` is the identity of the dirstate file, which can be used to
204 - `identity` is the identity of the dirstate file, which can be used to
205 detect when changes have occurred to the dirstate file.
205 detect when changes have occurred to the dirstate file.
206
206
207 - `parents` is a pair containing the parents of the working copy. The
207 - `parents` is a pair containing the parents of the working copy. The
208 parents are updated by calling `setparents`.
208 parents are updated by calling `setparents`.
209
209
210 - the state map maps filenames to tuples of (state, mode, size, mtime),
210 - the state map maps filenames to tuples of (state, mode, size, mtime),
211 where state is a single character representing 'normal', 'added',
211 where state is a single character representing 'normal', 'added',
212 'removed', or 'merged'. It is read by treating the dirstate as a
212 'removed', or 'merged'. It is read by treating the dirstate as a
213 dict. File state is updated by calling various methods (see each
213 dict. File state is updated by calling various methods (see each
214 documentation for details):
214 documentation for details):
215
215
216 - `reset_state`,
216 - `reset_state`,
217 - `set_tracked`
217 - `set_tracked`
218 - `set_untracked`
218 - `set_untracked`
219 - `set_clean`
219 - `set_clean`
220 - `set_possibly_dirty`
220 - `set_possibly_dirty`
221
221
222 - `copymap` maps destination filenames to their source filename.
222 - `copymap` maps destination filenames to their source filename.
223
223
224 The dirstate also provides the following views onto the state:
224 The dirstate also provides the following views onto the state:
225
225
226 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
226 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
227 form that they appear as in the dirstate.
227 form that they appear as in the dirstate.
228
228
229 - `dirfoldmap` is a dict mapping normalized directory names to the
229 - `dirfoldmap` is a dict mapping normalized directory names to the
230 denormalized form that they appear as in the dirstate.
230 denormalized form that they appear as in the dirstate.
231 """
231 """
232
232
233 ### Core data storage and access
233 ### Core data storage and access
234
234
235 @propertycache
235 @propertycache
236 def _map(self):
236 def _map(self):
237 self._map = {}
237 self._map = {}
238 self.read()
238 self.read()
239 return self._map
239 return self._map
240
240
241 @propertycache
241 @propertycache
242 def copymap(self):
242 def copymap(self):
243 self.copymap = {}
243 self.copymap = {}
244 self._map
244 self._map
245 return self.copymap
245 return self.copymap
246
246
247 def clear(self):
247 def clear(self):
248 self._map.clear()
248 self._map.clear()
249 self.copymap.clear()
249 self.copymap.clear()
250 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
250 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
251 util.clearcachedproperty(self, b"_dirs")
251 util.clearcachedproperty(self, b"_dirs")
252 util.clearcachedproperty(self, b"_alldirs")
252 util.clearcachedproperty(self, b"_alldirs")
253 util.clearcachedproperty(self, b"filefoldmap")
253 util.clearcachedproperty(self, b"filefoldmap")
254 util.clearcachedproperty(self, b"dirfoldmap")
254 util.clearcachedproperty(self, b"dirfoldmap")
255
255
256 def items(self):
256 def items(self):
257 return self._map.items()
257 return self._map.items()
258
258
259 # forward for python2,3 compat
259 # forward for python2,3 compat
260 iteritems = items
260 iteritems = items
261
261
262 def debug_iter(self, all):
262 def debug_iter(self, all):
263 """
263 """
264 Return an iterator of (filename, state, mode, size, mtime) tuples
264 Return an iterator of (filename, state, mode, size, mtime) tuples
265
265
266 `all` is unused when Rust is not enabled
266 `all` is unused when Rust is not enabled
267 """
267 """
268 for (filename, item) in self.items():
268 for (filename, item) in self.items():
269 yield (filename, item.state, item.mode, item.size, item.mtime)
269 yield (filename, item.state, item.mode, item.size, item.mtime)
270
270
271 def keys(self):
271 def keys(self):
272 return self._map.keys()
272 return self._map.keys()
273
273
274 ### reading/setting parents
274 ### reading/setting parents
275
275
276 def setparents(self, p1, p2, fold_p2=False):
276 def setparents(self, p1, p2, fold_p2=False):
277 self._parents = (p1, p2)
277 self._parents = (p1, p2)
278 self._dirtyparents = True
278 self._dirtyparents = True
279 copies = {}
279 copies = {}
280 if fold_p2:
280 if fold_p2:
281 for f, s in self._map.items():
281 for f, s in self._map.items():
282 # Discard "merged" markers when moving away from a merge state
282 # Discard "merged" markers when moving away from a merge state
283 if s.p2_info:
283 if s.p2_info:
284 source = self.copymap.pop(f, None)
284 source = self.copymap.pop(f, None)
285 if source:
285 if source:
286 copies[f] = source
286 copies[f] = source
287 s.drop_merge_data()
287 s.drop_merge_data()
288 return copies
288 return copies
289
289
290 ### disk interaction
290 ### disk interaction
291
291
292 def read(self):
292 def read(self):
293 # ignore HG_PENDING because identity is used only for writing
293 # ignore HG_PENDING because identity is used only for writing
294 self.identity = util.filestat.frompath(
294 self.identity = util.filestat.frompath(
295 self._opener.join(self._filename)
295 self._opener.join(self._filename)
296 )
296 )
297
297
298 if self._use_dirstate_v2:
298 if self._use_dirstate_v2:
299 if not self.docket.uuid:
299 if not self.docket.uuid:
300 return
300 return
301 st = self._opener.read(self.docket.data_filename())
301 st = self._opener.read(self.docket.data_filename())
302 else:
302 else:
303 st = self._readdirstatefile()
303 st = self._readdirstatefile()
304
304
305 if not st:
305 if not st:
306 return
306 return
307
307
308 # TODO: adjust this estimate for dirstate-v2
308 # TODO: adjust this estimate for dirstate-v2
309 if util.safehasattr(parsers, b'dict_new_presized'):
309 if util.safehasattr(parsers, b'dict_new_presized'):
310 # Make an estimate of the number of files in the dirstate based on
310 # Make an estimate of the number of files in the dirstate based on
311 # its size. This trades wasting some memory for avoiding costly
311 # its size. This trades wasting some memory for avoiding costly
312 # resizes. Each entry have a prefix of 17 bytes followed by one or
312 # resizes. Each entry have a prefix of 17 bytes followed by one or
313 # two path names. Studies on various large-scale real-world repositories
313 # two path names. Studies on various large-scale real-world repositories
314 # found 54 bytes a reasonable upper limit for the average path names.
314 # found 54 bytes a reasonable upper limit for the average path names.
315 # Copy entries are ignored for the sake of this estimate.
315 # Copy entries are ignored for the sake of this estimate.
316 self._map = parsers.dict_new_presized(len(st) // 71)
316 self._map = parsers.dict_new_presized(len(st) // 71)
317
317
318 # Python's garbage collector triggers a GC each time a certain number
318 # Python's garbage collector triggers a GC each time a certain number
319 # of container objects (the number being defined by
319 # of container objects (the number being defined by
320 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
320 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
321 # for each file in the dirstate. The C version then immediately marks
321 # for each file in the dirstate. The C version then immediately marks
322 # them as not to be tracked by the collector. However, this has no
322 # them as not to be tracked by the collector. However, this has no
323 # effect on when GCs are triggered, only on what objects the GC looks
323 # effect on when GCs are triggered, only on what objects the GC looks
324 # into. This means that O(number of files) GCs are unavoidable.
324 # into. This means that O(number of files) GCs are unavoidable.
325 # Depending on when in the process's lifetime the dirstate is parsed,
325 # Depending on when in the process's lifetime the dirstate is parsed,
326 # this can get very expensive. As a workaround, disable GC while
326 # this can get very expensive. As a workaround, disable GC while
327 # parsing the dirstate.
327 # parsing the dirstate.
328 #
328 #
329 # (we cannot decorate the function directly since it is in a C module)
329 # (we cannot decorate the function directly since it is in a C module)
330 if self._use_dirstate_v2:
330 if self._use_dirstate_v2:
331 p = self.docket.parents
331 p = self.docket.parents
332 meta = self.docket.tree_metadata
332 meta = self.docket.tree_metadata
333 parse_dirstate = util.nogc(v2.parse_dirstate)
333 parse_dirstate = util.nogc(v2.parse_dirstate)
334 parse_dirstate(self._map, self.copymap, st, meta)
334 parse_dirstate(self._map, self.copymap, st, meta)
335 else:
335 else:
336 parse_dirstate = util.nogc(parsers.parse_dirstate)
336 parse_dirstate = util.nogc(parsers.parse_dirstate)
337 p = parse_dirstate(self._map, self.copymap, st)
337 p = parse_dirstate(self._map, self.copymap, st)
338 if not self._dirtyparents:
338 if not self._dirtyparents:
339 self.setparents(*p)
339 self.setparents(*p)
340
340
341 # Avoid excess attribute lookups by fast pathing certain checks
341 # Avoid excess attribute lookups by fast pathing certain checks
342 self.__contains__ = self._map.__contains__
342 self.__contains__ = self._map.__contains__
343 self.__getitem__ = self._map.__getitem__
343 self.__getitem__ = self._map.__getitem__
344 self.get = self._map.get
344 self.get = self._map.get
345
345
346 def write(self, tr, st):
346 def write(self, tr, st):
347 if self._use_dirstate_v2:
347 if self._use_dirstate_v2:
348 packed, meta = v2.pack_dirstate(self._map, self.copymap)
348 packed, meta = v2.pack_dirstate(self._map, self.copymap)
349 self.write_v2_no_append(tr, st, meta, packed)
349 self.write_v2_no_append(tr, st, meta, packed)
350 else:
350 else:
351 packed = parsers.pack_dirstate(
351 packed = parsers.pack_dirstate(
352 self._map, self.copymap, self.parents()
352 self._map, self.copymap, self.parents()
353 )
353 )
354 st.write(packed)
354 st.write(packed)
355 st.close()
355 st.close()
356 self._dirtyparents = False
356 self._dirtyparents = False
357
357
358 @propertycache
358 @propertycache
359 def identity(self):
359 def identity(self):
360 self._map
360 self._map
361 return self.identity
361 return self.identity
362
362
363 ### code related to maintaining and accessing "extra" property
363 ### code related to maintaining and accessing "extra" property
364 # (e.g. "has_dir")
364 # (e.g. "has_dir")
365
365
366 def _dirs_incr(self, filename, old_entry=None):
366 def _dirs_incr(self, filename, old_entry=None):
367 """incremente the dirstate counter if applicable"""
367 """incremente the dirstate counter if applicable"""
368 if (
368 if (
369 old_entry is None or old_entry.removed
369 old_entry is None or old_entry.removed
370 ) and "_dirs" in self.__dict__:
370 ) and "_dirs" in self.__dict__:
371 self._dirs.addpath(filename)
371 self._dirs.addpath(filename)
372 if old_entry is None and "_alldirs" in self.__dict__:
372 if old_entry is None and "_alldirs" in self.__dict__:
373 self._alldirs.addpath(filename)
373 self._alldirs.addpath(filename)
374
374
375 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
375 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
376 """decremente the dirstate counter if applicable"""
376 """decremente the dirstate counter if applicable"""
377 if old_entry is not None:
377 if old_entry is not None:
378 if "_dirs" in self.__dict__ and not old_entry.removed:
378 if "_dirs" in self.__dict__ and not old_entry.removed:
379 self._dirs.delpath(filename)
379 self._dirs.delpath(filename)
380 if "_alldirs" in self.__dict__ and not remove_variant:
380 if "_alldirs" in self.__dict__ and not remove_variant:
381 self._alldirs.delpath(filename)
381 self._alldirs.delpath(filename)
382 elif remove_variant and "_alldirs" in self.__dict__:
382 elif remove_variant and "_alldirs" in self.__dict__:
383 self._alldirs.addpath(filename)
383 self._alldirs.addpath(filename)
384 if "filefoldmap" in self.__dict__:
384 if "filefoldmap" in self.__dict__:
385 normed = util.normcase(filename)
385 normed = util.normcase(filename)
386 self.filefoldmap.pop(normed, None)
386 self.filefoldmap.pop(normed, None)
387
387
388 @propertycache
388 @propertycache
389 def filefoldmap(self):
389 def filefoldmap(self):
390 """Returns a dictionary mapping normalized case paths to their
390 """Returns a dictionary mapping normalized case paths to their
391 non-normalized versions.
391 non-normalized versions.
392 """
392 """
393 try:
393 try:
394 makefilefoldmap = parsers.make_file_foldmap
394 makefilefoldmap = parsers.make_file_foldmap
395 except AttributeError:
395 except AttributeError:
396 pass
396 pass
397 else:
397 else:
398 return makefilefoldmap(
398 return makefilefoldmap(
399 self._map, util.normcasespec, util.normcasefallback
399 self._map, util.normcasespec, util.normcasefallback
400 )
400 )
401
401
402 f = {}
402 f = {}
403 normcase = util.normcase
403 normcase = util.normcase
404 for name, s in self._map.items():
404 for name, s in self._map.items():
405 if not s.removed:
405 if not s.removed:
406 f[normcase(name)] = name
406 f[normcase(name)] = name
407 f[b'.'] = b'.' # prevents useless util.fspath() invocation
407 f[b'.'] = b'.' # prevents useless util.fspath() invocation
408 return f
408 return f
409
409
410 @propertycache
410 @propertycache
411 def dirfoldmap(self):
411 def dirfoldmap(self):
412 f = {}
412 f = {}
413 normcase = util.normcase
413 normcase = util.normcase
414 for name in self._dirs:
414 for name in self._dirs:
415 f[normcase(name)] = name
415 f[normcase(name)] = name
416 return f
416 return f
417
417
418 def hastrackeddir(self, d):
418 def hastrackeddir(self, d):
419 """
419 """
420 Returns True if the dirstate contains a tracked (not removed) file
420 Returns True if the dirstate contains a tracked (not removed) file
421 in this directory.
421 in this directory.
422 """
422 """
423 return d in self._dirs
423 return d in self._dirs
424
424
425 def hasdir(self, d):
425 def hasdir(self, d):
426 """
426 """
427 Returns True if the dirstate contains a file (tracked or removed)
427 Returns True if the dirstate contains a file (tracked or removed)
428 in this directory.
428 in this directory.
429 """
429 """
430 return d in self._alldirs
430 return d in self._alldirs
431
431
432 @propertycache
432 @propertycache
433 def _dirs(self):
433 def _dirs(self):
434 return pathutil.dirs(self._map, only_tracked=True)
434 return pathutil.dirs(self._map, only_tracked=True)
435
435
436 @propertycache
436 @propertycache
437 def _alldirs(self):
437 def _alldirs(self):
438 return pathutil.dirs(self._map)
438 return pathutil.dirs(self._map)
439
439
440 ### code related to manipulation of entries and copy-sources
440 ### code related to manipulation of entries and copy-sources
441
441
442 def reset_state(
442 def reset_state(
443 self,
443 self,
444 filename,
444 filename,
445 wc_tracked=False,
445 wc_tracked=False,
446 p1_tracked=False,
446 p1_tracked=False,
447 p2_info=False,
447 p2_info=False,
448 has_meaningful_mtime=True,
448 has_meaningful_mtime=True,
449 parentfiledata=None,
449 parentfiledata=None,
450 ):
450 ):
451 """Set a entry to a given state, diregarding all previous state
451 """Set a entry to a given state, diregarding all previous state
452
452
453 This is to be used by the part of the dirstate API dedicated to
453 This is to be used by the part of the dirstate API dedicated to
454 adjusting the dirstate after a update/merge.
454 adjusting the dirstate after a update/merge.
455
455
456 note: calling this might result to no entry existing at all if the
456 note: calling this might result to no entry existing at all if the
457 dirstate map does not see any point at having one for this file
457 dirstate map does not see any point at having one for this file
458 anymore.
458 anymore.
459 """
459 """
460 # copy information are now outdated
460 # copy information are now outdated
461 # (maybe new information should be in directly passed to this function)
461 # (maybe new information should be in directly passed to this function)
462 self.copymap.pop(filename, None)
462 self.copymap.pop(filename, None)
463
463
464 if not (p1_tracked or p2_info or wc_tracked):
464 if not (p1_tracked or p2_info or wc_tracked):
465 old_entry = self._map.get(filename)
465 old_entry = self._map.get(filename)
466 self._drop_entry(filename)
466 self._drop_entry(filename)
467 self._dirs_decr(filename, old_entry=old_entry)
467 self._dirs_decr(filename, old_entry=old_entry)
468 return
468 return
469
469
470 old_entry = self._map.get(filename)
470 old_entry = self._map.get(filename)
471 self._dirs_incr(filename, old_entry)
471 self._dirs_incr(filename, old_entry)
472 entry = DirstateItem(
472 entry = DirstateItem(
473 wc_tracked=wc_tracked,
473 wc_tracked=wc_tracked,
474 p1_tracked=p1_tracked,
474 p1_tracked=p1_tracked,
475 p2_info=p2_info,
475 p2_info=p2_info,
476 has_meaningful_mtime=has_meaningful_mtime,
476 has_meaningful_mtime=has_meaningful_mtime,
477 parentfiledata=parentfiledata,
477 parentfiledata=parentfiledata,
478 )
478 )
479 self._map[filename] = entry
479 self._map[filename] = entry
480
480
481 def set_tracked(self, filename):
481 def set_tracked(self, filename):
482 new = False
482 new = False
483 entry = self.get(filename)
483 entry = self.get(filename)
484 if entry is None:
484 if entry is None:
485 self._dirs_incr(filename)
485 self._dirs_incr(filename)
486 entry = DirstateItem(
486 entry = DirstateItem(
487 wc_tracked=True,
487 wc_tracked=True,
488 )
488 )
489
489
490 self._map[filename] = entry
490 self._map[filename] = entry
491 new = True
491 new = True
492 elif not entry.tracked:
492 elif not entry.tracked:
493 self._dirs_incr(filename, entry)
493 self._dirs_incr(filename, entry)
494 entry.set_tracked()
494 entry.set_tracked()
495 self._refresh_entry(filename, entry)
495 self._refresh_entry(filename, entry)
496 new = True
496 new = True
497 else:
497 else:
498 # XXX This is probably overkill for more case, but we need this to
498 # XXX This is probably overkill for more case, but we need this to
499 # fully replace the `normallookup` call with `set_tracked` one.
499 # fully replace the `normallookup` call with `set_tracked` one.
500 # Consider smoothing this in the future.
500 # Consider smoothing this in the future.
501 entry.set_possibly_dirty()
501 entry.set_possibly_dirty()
502 self._refresh_entry(filename, entry)
502 self._refresh_entry(filename, entry)
503 return new
503 return new
504
504
505 def set_untracked(self, f):
505 def set_untracked(self, f):
506 """Mark a file as no longer tracked in the dirstate map"""
506 """Mark a file as no longer tracked in the dirstate map"""
507 entry = self.get(f)
507 entry = self.get(f)
508 if entry is None:
508 if entry is None:
509 return False
509 return False
510 else:
510 else:
511 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
511 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
512 if not entry.p2_info:
512 if not entry.p2_info:
513 self.copymap.pop(f, None)
513 self.copymap.pop(f, None)
514 entry.set_untracked()
514 entry.set_untracked()
515 self._refresh_entry(f, entry)
515 self._refresh_entry(f, entry)
516 return True
516 return True
517
517
518 def set_clean(self, filename, mode, size, mtime):
518 def set_clean(self, filename, mode, size, mtime):
519 """mark a file as back to a clean state"""
519 """mark a file as back to a clean state"""
520 entry = self[filename]
520 entry = self[filename]
521 size = size & rangemask
521 size = size & rangemask
522 entry.set_clean(mode, size, mtime)
522 entry.set_clean(mode, size, mtime)
523 self._refresh_entry(filename, entry)
523 self._refresh_entry(filename, entry)
524 self.copymap.pop(filename, None)
524 self.copymap.pop(filename, None)
525
525
526 def set_possibly_dirty(self, filename):
526 def set_possibly_dirty(self, filename):
527 """record that the current state of the file on disk is unknown"""
527 """record that the current state of the file on disk is unknown"""
528 entry = self[filename]
528 entry = self[filename]
529 entry.set_possibly_dirty()
529 entry.set_possibly_dirty()
530 self._refresh_entry(filename, entry)
530 self._refresh_entry(filename, entry)
531
531
532 def _refresh_entry(self, f, entry):
532 def _refresh_entry(self, f, entry):
533 if not entry.any_tracked:
533 if not entry.any_tracked:
534 self._map.pop(f, None)
534 self._map.pop(f, None)
535
535
536 def _drop_entry(self, f):
536 def _drop_entry(self, f):
537 self._map.pop(f, None)
537 self._map.pop(f, None)
538 self.copymap.pop(f, None)
538 self.copymap.pop(f, None)
539
539
540
540
541 if rustmod is not None:
541 if rustmod is not None:
542
542
543 class dirstatemap(_dirstatemapcommon):
543 class dirstatemap(_dirstatemapcommon):
544
544
545 ### Core data storage and access
545 ### Core data storage and access
546
546
547 @propertycache
547 @propertycache
548 def _map(self):
548 def _map(self):
549 """
549 """
550 Fills the Dirstatemap when called.
550 Fills the Dirstatemap when called.
551 """
551 """
552 # ignore HG_PENDING because identity is used only for writing
552 # ignore HG_PENDING because identity is used only for writing
553 self.identity = util.filestat.frompath(
553 self.identity = util.filestat.frompath(
554 self._opener.join(self._filename)
554 self._opener.join(self._filename)
555 )
555 )
556
556
557 if self._use_dirstate_v2:
557 if self._use_dirstate_v2:
558 if self.docket.uuid:
558 if self.docket.uuid:
559 # TODO: use mmap when possible
559 # TODO: use mmap when possible
560 data = self._opener.read(self.docket.data_filename())
560 data = self._opener.read(self.docket.data_filename())
561 else:
561 else:
562 data = b''
562 data = b''
563 self._map = rustmod.DirstateMap.new_v2(
563 self._map = rustmod.DirstateMap.new_v2(
564 data, self.docket.data_size, self.docket.tree_metadata
564 data, self.docket.data_size, self.docket.tree_metadata
565 )
565 )
566 parents = self.docket.parents
566 parents = self.docket.parents
567 else:
567 else:
568 self._map, parents = rustmod.DirstateMap.new_v1(
568 self._map, parents = rustmod.DirstateMap.new_v1(
569 self._readdirstatefile()
569 self._readdirstatefile()
570 )
570 )
571
571
572 if parents and not self._dirtyparents:
572 if parents and not self._dirtyparents:
573 self.setparents(*parents)
573 self.setparents(*parents)
574
574
575 self.__contains__ = self._map.__contains__
575 self.__contains__ = self._map.__contains__
576 self.__getitem__ = self._map.__getitem__
576 self.__getitem__ = self._map.__getitem__
577 self.get = self._map.get
577 self.get = self._map.get
578 return self._map
578 return self._map
579
579
580 @property
580 @property
581 def copymap(self):
581 def copymap(self):
582 return self._map.copymap()
582 return self._map.copymap()
583
583
584 def debug_iter(self, all):
584 def debug_iter(self, all):
585 """
585 """
586 Return an iterator of (filename, state, mode, size, mtime) tuples
586 Return an iterator of (filename, state, mode, size, mtime) tuples
587
587
588 `all`: also include with `state == b' '` dirstate tree nodes that
588 `all`: also include with `state == b' '` dirstate tree nodes that
589 don't have an associated `DirstateItem`.
589 don't have an associated `DirstateItem`.
590
590
591 """
591 """
592 return self._map.debug_iter(all)
592 return self._map.debug_iter(all)
593
593
594 def clear(self):
594 def clear(self):
595 self._map.clear()
595 self._map.clear()
596 self.setparents(
596 self.setparents(
597 self._nodeconstants.nullid, self._nodeconstants.nullid
597 self._nodeconstants.nullid, self._nodeconstants.nullid
598 )
598 )
599 util.clearcachedproperty(self, b"_dirs")
599 util.clearcachedproperty(self, b"_dirs")
600 util.clearcachedproperty(self, b"_alldirs")
600 util.clearcachedproperty(self, b"_alldirs")
601 util.clearcachedproperty(self, b"dirfoldmap")
601 util.clearcachedproperty(self, b"dirfoldmap")
602
602
603 def items(self):
603 def items(self):
604 return self._map.items()
604 return self._map.items()
605
605
606 # forward for python2,3 compat
606 # forward for python2,3 compat
607 iteritems = items
607 iteritems = items
608
608
609 def keys(self):
609 def keys(self):
610 return iter(self._map)
610 return iter(self._map)
611
611
612 ### reading/setting parents
612 ### reading/setting parents
613
613
614 def setparents(self, p1, p2, fold_p2=False):
614 def setparents(self, p1, p2, fold_p2=False):
615 self._parents = (p1, p2)
615 self._parents = (p1, p2)
616 self._dirtyparents = True
616 self._dirtyparents = True
617 copies = {}
617 copies = {}
618 if fold_p2:
618 if fold_p2:
619 # Collect into an intermediate list to avoid a `RuntimeError`
619 # Collect into an intermediate list to avoid a `RuntimeError`
620 # exception due to mutation during iteration.
620 # exception due to mutation during iteration.
621 # TODO: move this the whole loop to Rust where `iter_mut`
621 # TODO: move this the whole loop to Rust where `iter_mut`
622 # enables in-place mutation of elements of a collection while
622 # enables in-place mutation of elements of a collection while
623 # iterating it, without mutating the collection itself.
623 # iterating it, without mutating the collection itself.
624 files_with_p2_info = [
624 files_with_p2_info = [
625 f for f, s in self._map.items() if s.p2_info
625 f for f, s in self._map.items() if s.p2_info
626 ]
626 ]
627 rust_map = self._map
627 rust_map = self._map
628 for f in files_with_p2_info:
628 for f in files_with_p2_info:
629 e = rust_map.get(f)
629 e = rust_map.get(f)
630 source = self.copymap.pop(f, None)
630 source = self.copymap.pop(f, None)
631 if source:
631 if source:
632 copies[f] = source
632 copies[f] = source
633 e.drop_merge_data()
633 e.drop_merge_data()
634 rust_map.set_dirstate_item(f, e)
634 rust_map.set_dirstate_item(f, e)
635 return copies
635 return copies
636
636
637 ### disk interaction
637 ### disk interaction
638
638
639 @propertycache
639 @propertycache
640 def identity(self):
640 def identity(self):
641 self._map
641 self._map
642 return self.identity
642 return self.identity
643
643
644 def write(self, tr, st):
644 def write(self, tr, st):
645 if not self._use_dirstate_v2:
645 if not self._use_dirstate_v2:
646 p1, p2 = self.parents()
646 p1, p2 = self.parents()
647 packed = self._map.write_v1(p1, p2)
647 packed = self._map.write_v1(p1, p2)
648 st.write(packed)
648 st.write(packed)
649 st.close()
649 st.close()
650 self._dirtyparents = False
650 self._dirtyparents = False
651 return
651 return
652
652
653 # We can only append to an existing data file if there is one
653 # We can only append to an existing data file if there is one
654 can_append = self.docket.uuid is not None
654 can_append = self.docket.uuid is not None
655 packed, meta, append = self._map.write_v2(can_append)
655 packed, meta, append = self._map.write_v2(can_append)
656 if append:
656 if append:
657 docket = self.docket
657 docket = self.docket
658 data_filename = docket.data_filename()
658 data_filename = docket.data_filename()
659 if tr:
659 if tr:
660 tr.add(data_filename, docket.data_size)
660 tr.add(data_filename, docket.data_size)
661 with self._opener(data_filename, b'r+b') as fp:
661 with self._opener(data_filename, b'r+b') as fp:
662 fp.seek(docket.data_size)
662 fp.seek(docket.data_size)
663 assert fp.tell() == docket.data_size
663 assert fp.tell() == docket.data_size
664 written = fp.write(packed)
664 written = fp.write(packed)
665 if written is not None: # py2 may return None
665 if written is not None: # py2 may return None
666 assert written == len(packed), (written, len(packed))
666 assert written == len(packed), (written, len(packed))
667 docket.data_size += len(packed)
667 docket.data_size += len(packed)
668 docket.parents = self.parents()
668 docket.parents = self.parents()
669 docket.tree_metadata = meta
669 docket.tree_metadata = meta
670 st.write(docket.serialize())
670 st.write(docket.serialize())
671 st.close()
671 st.close()
672 else:
672 else:
673 self.write_v2_no_append(tr, st, meta, packed)
673 self.write_v2_no_append(tr, st, meta, packed)
674 # Reload from the newly-written file
674 # Reload from the newly-written file
675 util.clearcachedproperty(self, b"_map")
675 util.clearcachedproperty(self, b"_map")
676 self._dirtyparents = False
676 self._dirtyparents = False
677
677
678 ### code related to maintaining and accessing "extra" property
678 ### code related to maintaining and accessing "extra" property
679 # (e.g. "has_dir")
679 # (e.g. "has_dir")
680
680
681 @propertycache
681 @propertycache
682 def filefoldmap(self):
682 def filefoldmap(self):
683 """Returns a dictionary mapping normalized case paths to their
683 """Returns a dictionary mapping normalized case paths to their
684 non-normalized versions.
684 non-normalized versions.
685 """
685 """
686 return self._map.filefoldmapasdict()
686 return self._map.filefoldmapasdict()
687
687
688 def hastrackeddir(self, d):
688 def hastrackeddir(self, d):
689 return self._map.hastrackeddir(d)
689 return self._map.hastrackeddir(d)
690
690
691 def hasdir(self, d):
691 def hasdir(self, d):
692 return self._map.hasdir(d)
692 return self._map.hasdir(d)
693
693
694 @propertycache
694 @propertycache
695 def dirfoldmap(self):
695 def dirfoldmap(self):
696 f = {}
696 f = {}
697 normcase = util.normcase
697 normcase = util.normcase
698 for name in self._map.tracked_dirs():
698 for name in self._map.tracked_dirs():
699 f[normcase(name)] = name
699 f[normcase(name)] = name
700 return f
700 return f
701
701
702 ### code related to manipulation of entries and copy-sources
702 ### code related to manipulation of entries and copy-sources
703
703
704 def set_tracked(self, f):
704 def set_tracked(self, f):
705 return self._map.set_tracked(f)
705 return self._map.set_tracked(f)
706
706
707 def set_untracked(self, f):
707 def set_untracked(self, f):
708 return self._map.set_untracked(f)
708 return self._map.set_untracked(f)
709
709
710 def set_clean(self, filename, mode, size, mtime):
710 def set_clean(self, filename, mode, size, mtime):
711 self._map.set_clean(filename, mode, size, mtime)
711 self._map.set_clean(filename, mode, size, mtime)
712
712
713 def set_possibly_dirty(self, f):
713 def set_possibly_dirty(self, f):
714 self._map.set_possibly_dirty(f)
714 self._map.set_possibly_dirty(f)
715
715
716 def reset_state(
716 def reset_state(
717 self,
717 self,
718 filename,
718 filename,
719 wc_tracked=False,
719 wc_tracked=False,
720 p1_tracked=False,
720 p1_tracked=False,
721 p2_info=False,
721 p2_info=False,
722 has_meaningful_mtime=True,
722 has_meaningful_mtime=True,
723 parentfiledata=None,
723 parentfiledata=None,
724 ):
724 ):
725 return self._map.reset_state(
725 return self._map.reset_state(
726 filename,
726 filename,
727 wc_tracked,
727 wc_tracked,
728 p1_tracked,
728 p1_tracked,
729 p2_info,
729 p2_info,
730 has_meaningful_mtime,
730 has_meaningful_mtime,
731 parentfiledata,
731 parentfiledata,
732 )
732 )
733
733
734 def _drop_entry(self, f):
734 def _drop_entry(self, f):
735 self._map.drop_item_and_copy_source(f)
735 self._map.drop_item_and_copy_source(f)
736
737 def __setitem__(self, key, value):
738 assert isinstance(value, DirstateItem)
739 self._map.set_dirstate_item(key, value)
General Comments 0
You need to be logged in to leave comments. Login now