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