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