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