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