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