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