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