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