##// END OF EJS Templates
dirstate-item: use the properties in dirstatemap...
marmoute -
r48331:b8013cb7 default
parent child Browse files
Show More
@@ -1,687 +1,689 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 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[0] == b'n' and 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 entry[0] != b'r' 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[0] == b'n' and e[3] == now:
263 if e is not None and e.need_delay(now):
264 self._map[f] = DirstateItem(e[0], e[1], e[2], AMBIGUOUS_TIME)
264 self._map[f] = DirstateItem(
265 e.state, e.mode, e.size, AMBIGUOUS_TIME
266 )
265 self.nonnormalset.add(f)
267 self.nonnormalset.add(f)
266
268
267 def nonnormalentries(self):
269 def nonnormalentries(self):
268 '''Compute the nonnormal dirstate entries from the dmap'''
270 '''Compute the nonnormal dirstate entries from the dmap'''
269 try:
271 try:
270 return parsers.nonnormalotherparententries(self._map)
272 return parsers.nonnormalotherparententries(self._map)
271 except AttributeError:
273 except AttributeError:
272 nonnorm = set()
274 nonnorm = set()
273 otherparent = set()
275 otherparent = set()
274 for fname, e in pycompat.iteritems(self._map):
276 for fname, e in pycompat.iteritems(self._map):
275 if e[0] != b'n' or e[3] == AMBIGUOUS_TIME:
277 if e.state != b'n' or e.mtime == AMBIGUOUS_TIME:
276 nonnorm.add(fname)
278 nonnorm.add(fname)
277 if e[0] == b'n' and e[2] == FROM_P2:
279 if e.from_p2:
278 otherparent.add(fname)
280 otherparent.add(fname)
279 return nonnorm, otherparent
281 return nonnorm, otherparent
280
282
281 @propertycache
283 @propertycache
282 def filefoldmap(self):
284 def filefoldmap(self):
283 """Returns a dictionary mapping normalized case paths to their
285 """Returns a dictionary mapping normalized case paths to their
284 non-normalized versions.
286 non-normalized versions.
285 """
287 """
286 try:
288 try:
287 makefilefoldmap = parsers.make_file_foldmap
289 makefilefoldmap = parsers.make_file_foldmap
288 except AttributeError:
290 except AttributeError:
289 pass
291 pass
290 else:
292 else:
291 return makefilefoldmap(
293 return makefilefoldmap(
292 self._map, util.normcasespec, util.normcasefallback
294 self._map, util.normcasespec, util.normcasefallback
293 )
295 )
294
296
295 f = {}
297 f = {}
296 normcase = util.normcase
298 normcase = util.normcase
297 for name, s in pycompat.iteritems(self._map):
299 for name, s in pycompat.iteritems(self._map):
298 if s[0] != b'r':
300 if not s.removed:
299 f[normcase(name)] = name
301 f[normcase(name)] = name
300 f[b'.'] = b'.' # prevents useless util.fspath() invocation
302 f[b'.'] = b'.' # prevents useless util.fspath() invocation
301 return f
303 return f
302
304
303 def hastrackeddir(self, d):
305 def hastrackeddir(self, d):
304 """
306 """
305 Returns True if the dirstate contains a tracked (not removed) file
307 Returns True if the dirstate contains a tracked (not removed) file
306 in this directory.
308 in this directory.
307 """
309 """
308 return d in self._dirs
310 return d in self._dirs
309
311
310 def hasdir(self, d):
312 def hasdir(self, d):
311 """
313 """
312 Returns True if the dirstate contains a file (tracked or removed)
314 Returns True if the dirstate contains a file (tracked or removed)
313 in this directory.
315 in this directory.
314 """
316 """
315 return d in self._alldirs
317 return d in self._alldirs
316
318
317 @propertycache
319 @propertycache
318 def _dirs(self):
320 def _dirs(self):
319 return pathutil.dirs(self._map, b'r')
321 return pathutil.dirs(self._map, b'r')
320
322
321 @propertycache
323 @propertycache
322 def _alldirs(self):
324 def _alldirs(self):
323 return pathutil.dirs(self._map)
325 return pathutil.dirs(self._map)
324
326
325 def _opendirstatefile(self):
327 def _opendirstatefile(self):
326 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
328 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
327 if self._pendingmode is not None and self._pendingmode != mode:
329 if self._pendingmode is not None and self._pendingmode != mode:
328 fp.close()
330 fp.close()
329 raise error.Abort(
331 raise error.Abort(
330 _(b'working directory state may be changed parallelly')
332 _(b'working directory state may be changed parallelly')
331 )
333 )
332 self._pendingmode = mode
334 self._pendingmode = mode
333 return fp
335 return fp
334
336
335 def parents(self):
337 def parents(self):
336 if not self._parents:
338 if not self._parents:
337 try:
339 try:
338 fp = self._opendirstatefile()
340 fp = self._opendirstatefile()
339 st = fp.read(2 * self._nodelen)
341 st = fp.read(2 * self._nodelen)
340 fp.close()
342 fp.close()
341 except IOError as err:
343 except IOError as err:
342 if err.errno != errno.ENOENT:
344 if err.errno != errno.ENOENT:
343 raise
345 raise
344 # File doesn't exist, so the current state is empty
346 # File doesn't exist, so the current state is empty
345 st = b''
347 st = b''
346
348
347 l = len(st)
349 l = len(st)
348 if l == self._nodelen * 2:
350 if l == self._nodelen * 2:
349 self._parents = (
351 self._parents = (
350 st[: self._nodelen],
352 st[: self._nodelen],
351 st[self._nodelen : 2 * self._nodelen],
353 st[self._nodelen : 2 * self._nodelen],
352 )
354 )
353 elif l == 0:
355 elif l == 0:
354 self._parents = (
356 self._parents = (
355 self._nodeconstants.nullid,
357 self._nodeconstants.nullid,
356 self._nodeconstants.nullid,
358 self._nodeconstants.nullid,
357 )
359 )
358 else:
360 else:
359 raise error.Abort(
361 raise error.Abort(
360 _(b'working directory state appears damaged!')
362 _(b'working directory state appears damaged!')
361 )
363 )
362
364
363 return self._parents
365 return self._parents
364
366
365 def setparents(self, p1, p2):
367 def setparents(self, p1, p2):
366 self._parents = (p1, p2)
368 self._parents = (p1, p2)
367 self._dirtyparents = True
369 self._dirtyparents = True
368
370
369 def read(self):
371 def read(self):
370 # ignore HG_PENDING because identity is used only for writing
372 # ignore HG_PENDING because identity is used only for writing
371 self.identity = util.filestat.frompath(
373 self.identity = util.filestat.frompath(
372 self._opener.join(self._filename)
374 self._opener.join(self._filename)
373 )
375 )
374
376
375 try:
377 try:
376 fp = self._opendirstatefile()
378 fp = self._opendirstatefile()
377 try:
379 try:
378 st = fp.read()
380 st = fp.read()
379 finally:
381 finally:
380 fp.close()
382 fp.close()
381 except IOError as err:
383 except IOError as err:
382 if err.errno != errno.ENOENT:
384 if err.errno != errno.ENOENT:
383 raise
385 raise
384 return
386 return
385 if not st:
387 if not st:
386 return
388 return
387
389
388 if util.safehasattr(parsers, b'dict_new_presized'):
390 if util.safehasattr(parsers, b'dict_new_presized'):
389 # Make an estimate of the number of files in the dirstate based on
391 # Make an estimate of the number of files in the dirstate based on
390 # its size. This trades wasting some memory for avoiding costly
392 # its size. This trades wasting some memory for avoiding costly
391 # resizes. Each entry have a prefix of 17 bytes followed by one or
393 # resizes. Each entry have a prefix of 17 bytes followed by one or
392 # two path names. Studies on various large-scale real-world repositories
394 # two path names. Studies on various large-scale real-world repositories
393 # found 54 bytes a reasonable upper limit for the average path names.
395 # found 54 bytes a reasonable upper limit for the average path names.
394 # Copy entries are ignored for the sake of this estimate.
396 # Copy entries are ignored for the sake of this estimate.
395 self._map = parsers.dict_new_presized(len(st) // 71)
397 self._map = parsers.dict_new_presized(len(st) // 71)
396
398
397 # Python's garbage collector triggers a GC each time a certain number
399 # Python's garbage collector triggers a GC each time a certain number
398 # of container objects (the number being defined by
400 # of container objects (the number being defined by
399 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
401 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
400 # for each file in the dirstate. The C version then immediately marks
402 # for each file in the dirstate. The C version then immediately marks
401 # them as not to be tracked by the collector. However, this has no
403 # them as not to be tracked by the collector. However, this has no
402 # effect on when GCs are triggered, only on what objects the GC looks
404 # effect on when GCs are triggered, only on what objects the GC looks
403 # into. This means that O(number of files) GCs are unavoidable.
405 # into. This means that O(number of files) GCs are unavoidable.
404 # Depending on when in the process's lifetime the dirstate is parsed,
406 # Depending on when in the process's lifetime the dirstate is parsed,
405 # this can get very expensive. As a workaround, disable GC while
407 # this can get very expensive. As a workaround, disable GC while
406 # parsing the dirstate.
408 # parsing the dirstate.
407 #
409 #
408 # (we cannot decorate the function directly since it is in a C module)
410 # (we cannot decorate the function directly since it is in a C module)
409 parse_dirstate = util.nogc(parsers.parse_dirstate)
411 parse_dirstate = util.nogc(parsers.parse_dirstate)
410 p = parse_dirstate(self._map, self.copymap, st)
412 p = parse_dirstate(self._map, self.copymap, st)
411 if not self._dirtyparents:
413 if not self._dirtyparents:
412 self.setparents(*p)
414 self.setparents(*p)
413
415
414 # Avoid excess attribute lookups by fast pathing certain checks
416 # Avoid excess attribute lookups by fast pathing certain checks
415 self.__contains__ = self._map.__contains__
417 self.__contains__ = self._map.__contains__
416 self.__getitem__ = self._map.__getitem__
418 self.__getitem__ = self._map.__getitem__
417 self.get = self._map.get
419 self.get = self._map.get
418
420
419 def write(self, st, now):
421 def write(self, st, now):
420 st.write(
422 st.write(
421 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
423 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
422 )
424 )
423 st.close()
425 st.close()
424 self._dirtyparents = False
426 self._dirtyparents = False
425 self.nonnormalset, self.otherparentset = self.nonnormalentries()
427 self.nonnormalset, self.otherparentset = self.nonnormalentries()
426
428
427 @propertycache
429 @propertycache
428 def nonnormalset(self):
430 def nonnormalset(self):
429 nonnorm, otherparents = self.nonnormalentries()
431 nonnorm, otherparents = self.nonnormalentries()
430 self.otherparentset = otherparents
432 self.otherparentset = otherparents
431 return nonnorm
433 return nonnorm
432
434
433 @propertycache
435 @propertycache
434 def otherparentset(self):
436 def otherparentset(self):
435 nonnorm, otherparents = self.nonnormalentries()
437 nonnorm, otherparents = self.nonnormalentries()
436 self.nonnormalset = nonnorm
438 self.nonnormalset = nonnorm
437 return otherparents
439 return otherparents
438
440
439 def non_normal_or_other_parent_paths(self):
441 def non_normal_or_other_parent_paths(self):
440 return self.nonnormalset.union(self.otherparentset)
442 return self.nonnormalset.union(self.otherparentset)
441
443
442 @propertycache
444 @propertycache
443 def identity(self):
445 def identity(self):
444 self._map
446 self._map
445 return self.identity
447 return self.identity
446
448
447 @propertycache
449 @propertycache
448 def dirfoldmap(self):
450 def dirfoldmap(self):
449 f = {}
451 f = {}
450 normcase = util.normcase
452 normcase = util.normcase
451 for name in self._dirs:
453 for name in self._dirs:
452 f[normcase(name)] = name
454 f[normcase(name)] = name
453 return f
455 return f
454
456
455
457
456 if rustmod is not None:
458 if rustmod is not None:
457
459
458 class dirstatemap(object):
460 class dirstatemap(object):
459 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
461 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
460 self._use_dirstate_v2 = use_dirstate_v2
462 self._use_dirstate_v2 = use_dirstate_v2
461 self._nodeconstants = nodeconstants
463 self._nodeconstants = nodeconstants
462 self._ui = ui
464 self._ui = ui
463 self._opener = opener
465 self._opener = opener
464 self._root = root
466 self._root = root
465 self._filename = b'dirstate'
467 self._filename = b'dirstate'
466 self._nodelen = 20 # Also update Rust code when changing this!
468 self._nodelen = 20 # Also update Rust code when changing this!
467 self._parents = None
469 self._parents = None
468 self._dirtyparents = False
470 self._dirtyparents = False
469
471
470 # for consistent view between _pl() and _read() invocations
472 # for consistent view between _pl() and _read() invocations
471 self._pendingmode = None
473 self._pendingmode = None
472
474
473 self._use_dirstate_tree = self._ui.configbool(
475 self._use_dirstate_tree = self._ui.configbool(
474 b"experimental",
476 b"experimental",
475 b"dirstate-tree.in-memory",
477 b"dirstate-tree.in-memory",
476 False,
478 False,
477 )
479 )
478
480
479 def addfile(
481 def addfile(
480 self,
482 self,
481 f,
483 f,
482 mode=0,
484 mode=0,
483 size=None,
485 size=None,
484 mtime=None,
486 mtime=None,
485 added=False,
487 added=False,
486 merged=False,
488 merged=False,
487 from_p2=False,
489 from_p2=False,
488 possibly_dirty=False,
490 possibly_dirty=False,
489 ):
491 ):
490 return self._rustmap.addfile(
492 return self._rustmap.addfile(
491 f,
493 f,
492 mode,
494 mode,
493 size,
495 size,
494 mtime,
496 mtime,
495 added,
497 added,
496 merged,
498 merged,
497 from_p2,
499 from_p2,
498 possibly_dirty,
500 possibly_dirty,
499 )
501 )
500
502
501 def removefile(self, *args, **kwargs):
503 def removefile(self, *args, **kwargs):
502 return self._rustmap.removefile(*args, **kwargs)
504 return self._rustmap.removefile(*args, **kwargs)
503
505
504 def dropfile(self, *args, **kwargs):
506 def dropfile(self, *args, **kwargs):
505 return self._rustmap.dropfile(*args, **kwargs)
507 return self._rustmap.dropfile(*args, **kwargs)
506
508
507 def clearambiguoustimes(self, *args, **kwargs):
509 def clearambiguoustimes(self, *args, **kwargs):
508 return self._rustmap.clearambiguoustimes(*args, **kwargs)
510 return self._rustmap.clearambiguoustimes(*args, **kwargs)
509
511
510 def nonnormalentries(self):
512 def nonnormalentries(self):
511 return self._rustmap.nonnormalentries()
513 return self._rustmap.nonnormalentries()
512
514
513 def get(self, *args, **kwargs):
515 def get(self, *args, **kwargs):
514 return self._rustmap.get(*args, **kwargs)
516 return self._rustmap.get(*args, **kwargs)
515
517
516 @property
518 @property
517 def copymap(self):
519 def copymap(self):
518 return self._rustmap.copymap()
520 return self._rustmap.copymap()
519
521
520 def directories(self):
522 def directories(self):
521 return self._rustmap.directories()
523 return self._rustmap.directories()
522
524
523 def preload(self):
525 def preload(self):
524 self._rustmap
526 self._rustmap
525
527
526 def clear(self):
528 def clear(self):
527 self._rustmap.clear()
529 self._rustmap.clear()
528 self.setparents(
530 self.setparents(
529 self._nodeconstants.nullid, self._nodeconstants.nullid
531 self._nodeconstants.nullid, self._nodeconstants.nullid
530 )
532 )
531 util.clearcachedproperty(self, b"_dirs")
533 util.clearcachedproperty(self, b"_dirs")
532 util.clearcachedproperty(self, b"_alldirs")
534 util.clearcachedproperty(self, b"_alldirs")
533 util.clearcachedproperty(self, b"dirfoldmap")
535 util.clearcachedproperty(self, b"dirfoldmap")
534
536
535 def items(self):
537 def items(self):
536 return self._rustmap.items()
538 return self._rustmap.items()
537
539
538 def keys(self):
540 def keys(self):
539 return iter(self._rustmap)
541 return iter(self._rustmap)
540
542
541 def __contains__(self, key):
543 def __contains__(self, key):
542 return key in self._rustmap
544 return key in self._rustmap
543
545
544 def __getitem__(self, item):
546 def __getitem__(self, item):
545 return self._rustmap[item]
547 return self._rustmap[item]
546
548
547 def __len__(self):
549 def __len__(self):
548 return len(self._rustmap)
550 return len(self._rustmap)
549
551
550 def __iter__(self):
552 def __iter__(self):
551 return iter(self._rustmap)
553 return iter(self._rustmap)
552
554
553 # forward for python2,3 compat
555 # forward for python2,3 compat
554 iteritems = items
556 iteritems = items
555
557
556 def _opendirstatefile(self):
558 def _opendirstatefile(self):
557 fp, mode = txnutil.trypending(
559 fp, mode = txnutil.trypending(
558 self._root, self._opener, self._filename
560 self._root, self._opener, self._filename
559 )
561 )
560 if self._pendingmode is not None and self._pendingmode != mode:
562 if self._pendingmode is not None and self._pendingmode != mode:
561 fp.close()
563 fp.close()
562 raise error.Abort(
564 raise error.Abort(
563 _(b'working directory state may be changed parallelly')
565 _(b'working directory state may be changed parallelly')
564 )
566 )
565 self._pendingmode = mode
567 self._pendingmode = mode
566 return fp
568 return fp
567
569
568 def setparents(self, p1, p2):
570 def setparents(self, p1, p2):
569 self._parents = (p1, p2)
571 self._parents = (p1, p2)
570 self._dirtyparents = True
572 self._dirtyparents = True
571
573
572 def parents(self):
574 def parents(self):
573 if not self._parents:
575 if not self._parents:
574 if self._use_dirstate_v2:
576 if self._use_dirstate_v2:
575 offset = len(rustmod.V2_FORMAT_MARKER)
577 offset = len(rustmod.V2_FORMAT_MARKER)
576 else:
578 else:
577 offset = 0
579 offset = 0
578 read_len = offset + self._nodelen * 2
580 read_len = offset + self._nodelen * 2
579 try:
581 try:
580 fp = self._opendirstatefile()
582 fp = self._opendirstatefile()
581 st = fp.read(read_len)
583 st = fp.read(read_len)
582 fp.close()
584 fp.close()
583 except IOError as err:
585 except IOError as err:
584 if err.errno != errno.ENOENT:
586 if err.errno != errno.ENOENT:
585 raise
587 raise
586 # File doesn't exist, so the current state is empty
588 # File doesn't exist, so the current state is empty
587 st = b''
589 st = b''
588
590
589 l = len(st)
591 l = len(st)
590 if l == read_len:
592 if l == read_len:
591 st = st[offset:]
593 st = st[offset:]
592 self._parents = (
594 self._parents = (
593 st[: self._nodelen],
595 st[: self._nodelen],
594 st[self._nodelen : 2 * self._nodelen],
596 st[self._nodelen : 2 * self._nodelen],
595 )
597 )
596 elif l == 0:
598 elif l == 0:
597 self._parents = (
599 self._parents = (
598 self._nodeconstants.nullid,
600 self._nodeconstants.nullid,
599 self._nodeconstants.nullid,
601 self._nodeconstants.nullid,
600 )
602 )
601 else:
603 else:
602 raise error.Abort(
604 raise error.Abort(
603 _(b'working directory state appears damaged!')
605 _(b'working directory state appears damaged!')
604 )
606 )
605
607
606 return self._parents
608 return self._parents
607
609
608 @propertycache
610 @propertycache
609 def _rustmap(self):
611 def _rustmap(self):
610 """
612 """
611 Fills the Dirstatemap when called.
613 Fills the Dirstatemap when called.
612 """
614 """
613 # ignore HG_PENDING because identity is used only for writing
615 # ignore HG_PENDING because identity is used only for writing
614 self.identity = util.filestat.frompath(
616 self.identity = util.filestat.frompath(
615 self._opener.join(self._filename)
617 self._opener.join(self._filename)
616 )
618 )
617
619
618 try:
620 try:
619 fp = self._opendirstatefile()
621 fp = self._opendirstatefile()
620 try:
622 try:
621 st = fp.read()
623 st = fp.read()
622 finally:
624 finally:
623 fp.close()
625 fp.close()
624 except IOError as err:
626 except IOError as err:
625 if err.errno != errno.ENOENT:
627 if err.errno != errno.ENOENT:
626 raise
628 raise
627 st = b''
629 st = b''
628
630
629 self._rustmap, parents = rustmod.DirstateMap.new(
631 self._rustmap, parents = rustmod.DirstateMap.new(
630 self._use_dirstate_tree, self._use_dirstate_v2, st
632 self._use_dirstate_tree, self._use_dirstate_v2, st
631 )
633 )
632
634
633 if parents and not self._dirtyparents:
635 if parents and not self._dirtyparents:
634 self.setparents(*parents)
636 self.setparents(*parents)
635
637
636 self.__contains__ = self._rustmap.__contains__
638 self.__contains__ = self._rustmap.__contains__
637 self.__getitem__ = self._rustmap.__getitem__
639 self.__getitem__ = self._rustmap.__getitem__
638 self.get = self._rustmap.get
640 self.get = self._rustmap.get
639 return self._rustmap
641 return self._rustmap
640
642
641 def write(self, st, now):
643 def write(self, st, now):
642 parents = self.parents()
644 parents = self.parents()
643 packed = self._rustmap.write(
645 packed = self._rustmap.write(
644 self._use_dirstate_v2, parents[0], parents[1], now
646 self._use_dirstate_v2, parents[0], parents[1], now
645 )
647 )
646 st.write(packed)
648 st.write(packed)
647 st.close()
649 st.close()
648 self._dirtyparents = False
650 self._dirtyparents = False
649
651
650 @propertycache
652 @propertycache
651 def filefoldmap(self):
653 def filefoldmap(self):
652 """Returns a dictionary mapping normalized case paths to their
654 """Returns a dictionary mapping normalized case paths to their
653 non-normalized versions.
655 non-normalized versions.
654 """
656 """
655 return self._rustmap.filefoldmapasdict()
657 return self._rustmap.filefoldmapasdict()
656
658
657 def hastrackeddir(self, d):
659 def hastrackeddir(self, d):
658 return self._rustmap.hastrackeddir(d)
660 return self._rustmap.hastrackeddir(d)
659
661
660 def hasdir(self, d):
662 def hasdir(self, d):
661 return self._rustmap.hasdir(d)
663 return self._rustmap.hasdir(d)
662
664
663 @propertycache
665 @propertycache
664 def identity(self):
666 def identity(self):
665 self._rustmap
667 self._rustmap
666 return self.identity
668 return self.identity
667
669
668 @property
670 @property
669 def nonnormalset(self):
671 def nonnormalset(self):
670 nonnorm = self._rustmap.non_normal_entries()
672 nonnorm = self._rustmap.non_normal_entries()
671 return nonnorm
673 return nonnorm
672
674
673 @propertycache
675 @propertycache
674 def otherparentset(self):
676 def otherparentset(self):
675 otherparents = self._rustmap.other_parent_entries()
677 otherparents = self._rustmap.other_parent_entries()
676 return otherparents
678 return otherparents
677
679
678 def non_normal_or_other_parent_paths(self):
680 def non_normal_or_other_parent_paths(self):
679 return self._rustmap.non_normal_or_other_parent_paths()
681 return self._rustmap.non_normal_or_other_parent_paths()
680
682
681 @propertycache
683 @propertycache
682 def dirfoldmap(self):
684 def dirfoldmap(self):
683 f = {}
685 f = {}
684 normcase = util.normcase
686 normcase = util.normcase
685 for name, _pseudo_entry in self.directories():
687 for name, _pseudo_entry in self.directories():
686 f[normcase(name)] = name
688 f[normcase(name)] = name
687 return f
689 return f
General Comments 0
You need to be logged in to leave comments. Login now