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