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