##// END OF EJS Templates
dirstatemap: conclude `reset_state` with logic using the new __init__...
marmoute -
r48708:4f0ebf83 default
parent child Browse files
Show More
@@ -1,926 +1,948 b''
1 # dirstatemap.py
1 # dirstatemap.py
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 import errno
8 import errno
9
9
10 from .i18n import _
10 from .i18n import _
11
11
12 from . import (
12 from . import (
13 error,
13 error,
14 pathutil,
14 pathutil,
15 policy,
15 policy,
16 pycompat,
16 pycompat,
17 txnutil,
17 txnutil,
18 util,
18 util,
19 )
19 )
20
20
21 from .dirstateutils import (
21 from .dirstateutils import (
22 docket as docketmod,
22 docket as docketmod,
23 )
23 )
24
24
25 parsers = policy.importmod('parsers')
25 parsers = policy.importmod('parsers')
26 rustmod = policy.importrust('dirstate')
26 rustmod = policy.importrust('dirstate')
27
27
28 propertycache = util.propertycache
28 propertycache = util.propertycache
29
29
30 DirstateItem = parsers.DirstateItem
30 DirstateItem = parsers.DirstateItem
31
31
32
32
33 # a special value used internally for `size` if the file come from the other parent
33 # a special value used internally for `size` if the file come from the other parent
34 FROM_P2 = -2
34 FROM_P2 = -2
35
35
36 # a special value used internally for `size` if the file is modified/merged/added
36 # a special value used internally for `size` if the file is modified/merged/added
37 NONNORMAL = -1
37 NONNORMAL = -1
38
38
39 # a special value used internally for `time` if the time is ambigeous
39 # a special value used internally for `time` if the time is ambigeous
40 AMBIGUOUS_TIME = -1
40 AMBIGUOUS_TIME = -1
41
41
42 rangemask = 0x7FFFFFFF
42 rangemask = 0x7FFFFFFF
43
43
44
44
45 class dirstatemap(object):
45 class dirstatemap(object):
46 """Map encapsulating the dirstate's contents.
46 """Map encapsulating the dirstate's contents.
47
47
48 The dirstate contains the following state:
48 The dirstate contains the following state:
49
49
50 - `identity` is the identity of the dirstate file, which can be used to
50 - `identity` is the identity of the dirstate file, which can be used to
51 detect when changes have occurred to the dirstate file.
51 detect when changes have occurred to the dirstate file.
52
52
53 - `parents` is a pair containing the parents of the working copy. The
53 - `parents` is a pair containing the parents of the working copy. The
54 parents are updated by calling `setparents`.
54 parents are updated by calling `setparents`.
55
55
56 - the state map maps filenames to tuples of (state, mode, size, mtime),
56 - the state map maps filenames to tuples of (state, mode, size, mtime),
57 where state is a single character representing 'normal', 'added',
57 where state is a single character representing 'normal', 'added',
58 'removed', or 'merged'. It is read by treating the dirstate as a
58 'removed', or 'merged'. It is read by treating the dirstate as a
59 dict. File state is updated by calling the `addfile`, `removefile` and
59 dict. File state is updated by calling the `addfile`, `removefile` and
60 `dropfile` methods.
60 `dropfile` methods.
61
61
62 - `copymap` maps destination filenames to their source filename.
62 - `copymap` maps destination filenames to their source filename.
63
63
64 The dirstate also provides the following views onto the state:
64 The dirstate also provides the following views onto the state:
65
65
66 - `nonnormalset` is a set of the filenames that have state other
66 - `nonnormalset` is a set of the filenames that have state other
67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
68
68
69 - `otherparentset` is a set of the filenames that are marked as coming
69 - `otherparentset` is a set of the filenames that are marked as coming
70 from the second parent when the dirstate is currently being merged.
70 from the second parent when the dirstate is currently being merged.
71
71
72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
73 form that they appear as in the dirstate.
73 form that they appear as in the dirstate.
74
74
75 - `dirfoldmap` is a dict mapping normalized directory names to the
75 - `dirfoldmap` is a dict mapping normalized directory names to the
76 denormalized form that they appear as in the dirstate.
76 denormalized form that they appear as in the dirstate.
77 """
77 """
78
78
79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
80 self._ui = ui
80 self._ui = ui
81 self._opener = opener
81 self._opener = opener
82 self._root = root
82 self._root = root
83 self._filename = b'dirstate'
83 self._filename = b'dirstate'
84 self._nodelen = 20
84 self._nodelen = 20
85 self._nodeconstants = nodeconstants
85 self._nodeconstants = nodeconstants
86 assert (
86 assert (
87 not use_dirstate_v2
87 not use_dirstate_v2
88 ), "should have detected unsupported requirement"
88 ), "should have detected unsupported requirement"
89
89
90 self._parents = None
90 self._parents = None
91 self._dirtyparents = False
91 self._dirtyparents = False
92
92
93 # for consistent view between _pl() and _read() invocations
93 # for consistent view between _pl() and _read() invocations
94 self._pendingmode = None
94 self._pendingmode = None
95
95
96 @propertycache
96 @propertycache
97 def _map(self):
97 def _map(self):
98 self._map = {}
98 self._map = {}
99 self.read()
99 self.read()
100 return self._map
100 return self._map
101
101
102 @propertycache
102 @propertycache
103 def copymap(self):
103 def copymap(self):
104 self.copymap = {}
104 self.copymap = {}
105 self._map
105 self._map
106 return self.copymap
106 return self.copymap
107
107
108 def clear(self):
108 def clear(self):
109 self._map.clear()
109 self._map.clear()
110 self.copymap.clear()
110 self.copymap.clear()
111 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
111 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
112 util.clearcachedproperty(self, b"_dirs")
112 util.clearcachedproperty(self, b"_dirs")
113 util.clearcachedproperty(self, b"_alldirs")
113 util.clearcachedproperty(self, b"_alldirs")
114 util.clearcachedproperty(self, b"filefoldmap")
114 util.clearcachedproperty(self, b"filefoldmap")
115 util.clearcachedproperty(self, b"dirfoldmap")
115 util.clearcachedproperty(self, b"dirfoldmap")
116 util.clearcachedproperty(self, b"nonnormalset")
116 util.clearcachedproperty(self, b"nonnormalset")
117 util.clearcachedproperty(self, b"otherparentset")
117 util.clearcachedproperty(self, b"otherparentset")
118
118
119 def items(self):
119 def items(self):
120 return pycompat.iteritems(self._map)
120 return pycompat.iteritems(self._map)
121
121
122 # forward for python2,3 compat
122 # forward for python2,3 compat
123 iteritems = items
123 iteritems = items
124
124
125 debug_iter = items
125 debug_iter = items
126
126
127 def __len__(self):
127 def __len__(self):
128 return len(self._map)
128 return len(self._map)
129
129
130 def __iter__(self):
130 def __iter__(self):
131 return iter(self._map)
131 return iter(self._map)
132
132
133 def get(self, key, default=None):
133 def get(self, key, default=None):
134 return self._map.get(key, default)
134 return self._map.get(key, default)
135
135
136 def __contains__(self, key):
136 def __contains__(self, key):
137 return key in self._map
137 return key in self._map
138
138
139 def __getitem__(self, key):
139 def __getitem__(self, key):
140 return self._map[key]
140 return self._map[key]
141
141
142 def keys(self):
142 def keys(self):
143 return self._map.keys()
143 return self._map.keys()
144
144
145 def preload(self):
145 def preload(self):
146 """Loads the underlying data, if it's not already loaded"""
146 """Loads the underlying data, if it's not already loaded"""
147 self._map
147 self._map
148
148
149 def _dirs_incr(self, filename, old_entry=None):
149 def _dirs_incr(self, filename, old_entry=None):
150 """incremente the dirstate counter if applicable"""
150 """incremente the dirstate counter if applicable"""
151 if (
151 if (
152 old_entry is None or old_entry.removed
152 old_entry is None or old_entry.removed
153 ) and "_dirs" in self.__dict__:
153 ) and "_dirs" in self.__dict__:
154 self._dirs.addpath(filename)
154 self._dirs.addpath(filename)
155 if old_entry is None and "_alldirs" in self.__dict__:
155 if old_entry is None and "_alldirs" in self.__dict__:
156 self._alldirs.addpath(filename)
156 self._alldirs.addpath(filename)
157
157
158 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
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__ and not remove_variant:
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__:
165 elif remove_variant and "_alldirs" in self.__dict__:
166 self._alldirs.addpath(filename)
166 self._alldirs.addpath(filename)
167 if "filefoldmap" in self.__dict__:
167 if "filefoldmap" in self.__dict__:
168 normed = util.normcase(filename)
168 normed = util.normcase(filename)
169 self.filefoldmap.pop(normed, None)
169 self.filefoldmap.pop(normed, None)
170
170
171 def set_possibly_dirty(self, filename):
171 def set_possibly_dirty(self, filename):
172 """record that the current state of the file on disk is unknown"""
172 """record that the current state of the file on disk is unknown"""
173 self[filename].set_possibly_dirty()
173 self[filename].set_possibly_dirty()
174
174
175 def addfile(
175 def addfile(
176 self,
176 self,
177 f,
177 f,
178 mode=0,
178 mode=0,
179 size=None,
179 size=None,
180 mtime=None,
180 mtime=None,
181 added=False,
181 added=False,
182 merged=False,
182 merged=False,
183 from_p2=False,
183 from_p2=False,
184 possibly_dirty=False,
184 possibly_dirty=False,
185 ):
185 ):
186 """Add a tracked file to the dirstate."""
186 """Add a tracked file to the dirstate."""
187 if added:
187 if added:
188 assert not merged
188 assert not merged
189 assert not possibly_dirty
189 assert not possibly_dirty
190 assert not from_p2
190 assert not from_p2
191 state = b'a'
191 state = b'a'
192 size = NONNORMAL
192 size = NONNORMAL
193 mtime = AMBIGUOUS_TIME
193 mtime = AMBIGUOUS_TIME
194 elif merged:
194 elif merged:
195 assert not possibly_dirty
195 assert not possibly_dirty
196 assert not from_p2
196 assert not from_p2
197 state = b'm'
197 state = b'm'
198 size = FROM_P2
198 size = FROM_P2
199 mtime = AMBIGUOUS_TIME
199 mtime = AMBIGUOUS_TIME
200 elif from_p2:
200 elif from_p2:
201 assert not possibly_dirty
201 assert not possibly_dirty
202 state = b'n'
202 state = b'n'
203 size = FROM_P2
203 size = FROM_P2
204 mtime = AMBIGUOUS_TIME
204 mtime = AMBIGUOUS_TIME
205 elif possibly_dirty:
205 elif possibly_dirty:
206 state = b'n'
206 state = b'n'
207 size = NONNORMAL
207 size = NONNORMAL
208 mtime = AMBIGUOUS_TIME
208 mtime = AMBIGUOUS_TIME
209 else:
209 else:
210 assert size != FROM_P2
210 assert size != FROM_P2
211 assert size != NONNORMAL
211 assert size != NONNORMAL
212 assert size is not None
212 assert size is not None
213 assert mtime is not None
213 assert mtime is not None
214
214
215 state = b'n'
215 state = b'n'
216 size = size & rangemask
216 size = size & rangemask
217 mtime = mtime & rangemask
217 mtime = mtime & rangemask
218 assert state is not None
218 assert state is not None
219 assert size is not None
219 assert size is not None
220 assert mtime is not None
220 assert mtime is not None
221 old_entry = self.get(f)
221 old_entry = self.get(f)
222 self._dirs_incr(f, old_entry)
222 self._dirs_incr(f, old_entry)
223 e = self._map[f] = DirstateItem.from_v1_data(state, mode, size, mtime)
223 e = self._map[f] = DirstateItem.from_v1_data(state, mode, size, mtime)
224 if e.dm_nonnormal:
224 if e.dm_nonnormal:
225 self.nonnormalset.add(f)
225 self.nonnormalset.add(f)
226 if e.dm_otherparent:
226 if e.dm_otherparent:
227 self.otherparentset.add(f)
227 self.otherparentset.add(f)
228
228
229 def reset_state(
229 def reset_state(
230 self,
230 self,
231 filename,
231 filename,
232 wc_tracked,
232 wc_tracked,
233 p1_tracked,
233 p1_tracked,
234 p2_tracked=False,
234 p2_tracked=False,
235 merged=False,
235 merged=False,
236 clean_p1=False,
236 clean_p1=False,
237 clean_p2=False,
237 clean_p2=False,
238 possibly_dirty=False,
238 possibly_dirty=False,
239 parentfiledata=None,
239 parentfiledata=None,
240 ):
240 ):
241 """Set a entry to a given state, diregarding all previous state
241 """Set a entry to a given state, diregarding all previous state
242
242
243 This is to be used by the part of the dirstate API dedicated to
243 This is to be used by the part of the dirstate API dedicated to
244 adjusting the dirstate after a update/merge.
244 adjusting the dirstate after a update/merge.
245
245
246 note: calling this might result to no entry existing at all if the
246 note: calling this might result to no entry existing at all if the
247 dirstate map does not see any point at having one for this file
247 dirstate map does not see any point at having one for this file
248 anymore.
248 anymore.
249 """
249 """
250 if merged and (clean_p1 or clean_p2):
250 if merged and (clean_p1 or clean_p2):
251 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
251 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
252 raise error.ProgrammingError(msg)
252 raise error.ProgrammingError(msg)
253 # copy information are now outdated
253 # copy information are now outdated
254 # (maybe new information should be in directly passed to this function)
254 # (maybe new information should be in directly passed to this function)
255 self.copymap.pop(filename, None)
255 self.copymap.pop(filename, None)
256
256
257 if not (p1_tracked or p2_tracked or wc_tracked):
257 if not (p1_tracked or p2_tracked or wc_tracked):
258 self.dropfile(filename)
258 self.dropfile(filename)
259 return
259 return
260 elif merged:
260 elif merged:
261 # XXX might be merged and removed ?
261 # XXX might be merged and removed ?
262 entry = self.get(filename)
262 entry = self.get(filename)
263 if entry is not None and entry.tracked:
263 if entry is not None and entry.tracked:
264 # XXX mostly replicate dirstate.other parent. We should get
264 # XXX mostly replicate dirstate.other parent. We should get
265 # the higher layer to pass us more reliable data where `merged`
265 # the higher layer to pass us more reliable data where `merged`
266 # actually mean merged. Dropping the else clause will show
266 # actually mean merged. Dropping the else clause will show
267 # failure in `test-graft.t`
267 # failure in `test-graft.t`
268 self.addfile(filename, merged=True)
268 self.addfile(filename, merged=True)
269 return
269 return
270 else:
270 else:
271 self.addfile(filename, from_p2=True)
271 self.addfile(filename, from_p2=True)
272 return
272 return
273 elif not (p1_tracked or p2_tracked) and wc_tracked:
273 elif not (p1_tracked or p2_tracked) and wc_tracked:
274 self.addfile(filename, added=True, possibly_dirty=possibly_dirty)
274 self.addfile(filename, added=True, possibly_dirty=possibly_dirty)
275 return
275 return
276 elif (p1_tracked or p2_tracked) and not wc_tracked:
276 elif (p1_tracked or p2_tracked) and not wc_tracked:
277 # XXX might be merged and removed ?
277 # XXX might be merged and removed ?
278 old_entry = self._map.get(filename)
278 old_entry = self._map.get(filename)
279 self._dirs_decr(filename, old_entry=old_entry, remove_variant=True)
279 self._dirs_decr(filename, old_entry=old_entry, remove_variant=True)
280 self._map[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
280 self._map[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
281 self.nonnormalset.add(filename)
281 self.nonnormalset.add(filename)
282 return
282 return
283 elif clean_p2 and wc_tracked:
283 elif clean_p2 and wc_tracked:
284 if p1_tracked or self.get(filename) is not None:
284 if p1_tracked or self.get(filename) is not None:
285 # XXX the `self.get` call is catching some case in
285 # XXX the `self.get` call is catching some case in
286 # `test-merge-remove.t` where the file is tracked in p1, the
286 # `test-merge-remove.t` where the file is tracked in p1, the
287 # p1_tracked argument is False.
287 # p1_tracked argument is False.
288 #
288 #
289 # In addition, this seems to be a case where the file is marked
289 # In addition, this seems to be a case where the file is marked
290 # as merged without actually being the result of a merge
290 # as merged without actually being the result of a merge
291 # action. So thing are not ideal here.
291 # action. So thing are not ideal here.
292 self.addfile(filename, merged=True)
292 self.addfile(filename, merged=True)
293 return
293 return
294 else:
294 else:
295 self.addfile(filename, from_p2=True)
295 self.addfile(filename, from_p2=True)
296 return
296 return
297 elif not p1_tracked and p2_tracked and wc_tracked:
297 elif not p1_tracked and p2_tracked and wc_tracked:
298 self.addfile(filename, from_p2=True, possibly_dirty=possibly_dirty)
298 self.addfile(filename, from_p2=True, possibly_dirty=possibly_dirty)
299 return
299 return
300 elif possibly_dirty:
300 elif possibly_dirty:
301 self.addfile(filename, possibly_dirty=possibly_dirty)
301 self.addfile(filename, possibly_dirty=possibly_dirty)
302 return
302 return
303 elif wc_tracked:
303 elif wc_tracked:
304 # this is a "normal" file
304 # this is a "normal" file
305 if parentfiledata is None:
305 if parentfiledata is None:
306 msg = b'failed to pass parentfiledata for a normal file: %s'
306 msg = b'failed to pass parentfiledata for a normal file: %s'
307 msg %= filename
307 msg %= filename
308 raise error.ProgrammingError(msg)
308 raise error.ProgrammingError(msg)
309 mode, size, mtime = parentfiledata
309 mode, size, mtime = parentfiledata
310 self.addfile(filename, mode=mode, size=size, mtime=mtime)
310 self.addfile(filename, mode=mode, size=size, mtime=mtime)
311 self.nonnormalset.discard(filename)
311 self.nonnormalset.discard(filename)
312 return
312 return
313 else:
313 else:
314 assert False, 'unreachable'
314 assert False, 'unreachable'
315
315
316 old_entry = self._map.get(filename)
317 self._dirs_incr(filename, old_entry)
318 entry = DirstateItem(
319 wc_tracked=wc_tracked,
320 p1_tracked=p1_tracked,
321 p2_tracked=p2_tracked,
322 merged=merged,
323 clean_p1=clean_p1,
324 clean_p2=clean_p2,
325 possibly_dirty=possibly_dirty,
326 parentfiledata=parentfiledata,
327 )
328 if entry.dm_nonnormal:
329 self.nonnormalset.add(filename)
330 else:
331 self.nonnormalset.discard(filename)
332 if entry.dm_otherparent:
333 self.otherparentset.add(filename)
334 else:
335 self.otherparentset.discard(filename)
336 self._map[filename] = entry
337
316 def set_untracked(self, f):
338 def set_untracked(self, f):
317 """Mark a file as no longer tracked in the dirstate map"""
339 """Mark a file as no longer tracked in the dirstate map"""
318 entry = self[f]
340 entry = self[f]
319 self._dirs_decr(f, old_entry=entry, remove_variant=True)
341 self._dirs_decr(f, old_entry=entry, remove_variant=True)
320 if entry.from_p2:
342 if entry.from_p2:
321 self.otherparentset.add(f)
343 self.otherparentset.add(f)
322 elif not entry.merged:
344 elif not entry.merged:
323 self.copymap.pop(f, None)
345 self.copymap.pop(f, None)
324 entry.set_untracked()
346 entry.set_untracked()
325 self.nonnormalset.add(f)
347 self.nonnormalset.add(f)
326
348
327 def dropfile(self, f):
349 def dropfile(self, f):
328 """
350 """
329 Remove a file from the dirstate. Returns True if the file was
351 Remove a file from the dirstate. Returns True if the file was
330 previously recorded.
352 previously recorded.
331 """
353 """
332 old_entry = self._map.pop(f, None)
354 old_entry = self._map.pop(f, None)
333 self._dirs_decr(f, old_entry=old_entry)
355 self._dirs_decr(f, old_entry=old_entry)
334 self.nonnormalset.discard(f)
356 self.nonnormalset.discard(f)
335 return old_entry is not None
357 return old_entry is not None
336
358
337 def clearambiguoustimes(self, files, now):
359 def clearambiguoustimes(self, files, now):
338 for f in files:
360 for f in files:
339 e = self.get(f)
361 e = self.get(f)
340 if e is not None and e.need_delay(now):
362 if e is not None and e.need_delay(now):
341 e.set_possibly_dirty()
363 e.set_possibly_dirty()
342 self.nonnormalset.add(f)
364 self.nonnormalset.add(f)
343
365
344 def nonnormalentries(self):
366 def nonnormalentries(self):
345 '''Compute the nonnormal dirstate entries from the dmap'''
367 '''Compute the nonnormal dirstate entries from the dmap'''
346 try:
368 try:
347 return parsers.nonnormalotherparententries(self._map)
369 return parsers.nonnormalotherparententries(self._map)
348 except AttributeError:
370 except AttributeError:
349 nonnorm = set()
371 nonnorm = set()
350 otherparent = set()
372 otherparent = set()
351 for fname, e in pycompat.iteritems(self._map):
373 for fname, e in pycompat.iteritems(self._map):
352 if e.dm_nonnormal:
374 if e.dm_nonnormal:
353 nonnorm.add(fname)
375 nonnorm.add(fname)
354 if e.from_p2:
376 if e.from_p2:
355 otherparent.add(fname)
377 otherparent.add(fname)
356 return nonnorm, otherparent
378 return nonnorm, otherparent
357
379
358 @propertycache
380 @propertycache
359 def filefoldmap(self):
381 def filefoldmap(self):
360 """Returns a dictionary mapping normalized case paths to their
382 """Returns a dictionary mapping normalized case paths to their
361 non-normalized versions.
383 non-normalized versions.
362 """
384 """
363 try:
385 try:
364 makefilefoldmap = parsers.make_file_foldmap
386 makefilefoldmap = parsers.make_file_foldmap
365 except AttributeError:
387 except AttributeError:
366 pass
388 pass
367 else:
389 else:
368 return makefilefoldmap(
390 return makefilefoldmap(
369 self._map, util.normcasespec, util.normcasefallback
391 self._map, util.normcasespec, util.normcasefallback
370 )
392 )
371
393
372 f = {}
394 f = {}
373 normcase = util.normcase
395 normcase = util.normcase
374 for name, s in pycompat.iteritems(self._map):
396 for name, s in pycompat.iteritems(self._map):
375 if not s.removed:
397 if not s.removed:
376 f[normcase(name)] = name
398 f[normcase(name)] = name
377 f[b'.'] = b'.' # prevents useless util.fspath() invocation
399 f[b'.'] = b'.' # prevents useless util.fspath() invocation
378 return f
400 return f
379
401
380 def hastrackeddir(self, d):
402 def hastrackeddir(self, d):
381 """
403 """
382 Returns True if the dirstate contains a tracked (not removed) file
404 Returns True if the dirstate contains a tracked (not removed) file
383 in this directory.
405 in this directory.
384 """
406 """
385 return d in self._dirs
407 return d in self._dirs
386
408
387 def hasdir(self, d):
409 def hasdir(self, d):
388 """
410 """
389 Returns True if the dirstate contains a file (tracked or removed)
411 Returns True if the dirstate contains a file (tracked or removed)
390 in this directory.
412 in this directory.
391 """
413 """
392 return d in self._alldirs
414 return d in self._alldirs
393
415
394 @propertycache
416 @propertycache
395 def _dirs(self):
417 def _dirs(self):
396 return pathutil.dirs(self._map, b'r')
418 return pathutil.dirs(self._map, b'r')
397
419
398 @propertycache
420 @propertycache
399 def _alldirs(self):
421 def _alldirs(self):
400 return pathutil.dirs(self._map)
422 return pathutil.dirs(self._map)
401
423
402 def _opendirstatefile(self):
424 def _opendirstatefile(self):
403 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
425 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
404 if self._pendingmode is not None and self._pendingmode != mode:
426 if self._pendingmode is not None and self._pendingmode != mode:
405 fp.close()
427 fp.close()
406 raise error.Abort(
428 raise error.Abort(
407 _(b'working directory state may be changed parallelly')
429 _(b'working directory state may be changed parallelly')
408 )
430 )
409 self._pendingmode = mode
431 self._pendingmode = mode
410 return fp
432 return fp
411
433
412 def parents(self):
434 def parents(self):
413 if not self._parents:
435 if not self._parents:
414 try:
436 try:
415 fp = self._opendirstatefile()
437 fp = self._opendirstatefile()
416 st = fp.read(2 * self._nodelen)
438 st = fp.read(2 * self._nodelen)
417 fp.close()
439 fp.close()
418 except IOError as err:
440 except IOError as err:
419 if err.errno != errno.ENOENT:
441 if err.errno != errno.ENOENT:
420 raise
442 raise
421 # File doesn't exist, so the current state is empty
443 # File doesn't exist, so the current state is empty
422 st = b''
444 st = b''
423
445
424 l = len(st)
446 l = len(st)
425 if l == self._nodelen * 2:
447 if l == self._nodelen * 2:
426 self._parents = (
448 self._parents = (
427 st[: self._nodelen],
449 st[: self._nodelen],
428 st[self._nodelen : 2 * self._nodelen],
450 st[self._nodelen : 2 * self._nodelen],
429 )
451 )
430 elif l == 0:
452 elif l == 0:
431 self._parents = (
453 self._parents = (
432 self._nodeconstants.nullid,
454 self._nodeconstants.nullid,
433 self._nodeconstants.nullid,
455 self._nodeconstants.nullid,
434 )
456 )
435 else:
457 else:
436 raise error.Abort(
458 raise error.Abort(
437 _(b'working directory state appears damaged!')
459 _(b'working directory state appears damaged!')
438 )
460 )
439
461
440 return self._parents
462 return self._parents
441
463
442 def setparents(self, p1, p2):
464 def setparents(self, p1, p2):
443 self._parents = (p1, p2)
465 self._parents = (p1, p2)
444 self._dirtyparents = True
466 self._dirtyparents = True
445
467
446 def read(self):
468 def read(self):
447 # ignore HG_PENDING because identity is used only for writing
469 # ignore HG_PENDING because identity is used only for writing
448 self.identity = util.filestat.frompath(
470 self.identity = util.filestat.frompath(
449 self._opener.join(self._filename)
471 self._opener.join(self._filename)
450 )
472 )
451
473
452 try:
474 try:
453 fp = self._opendirstatefile()
475 fp = self._opendirstatefile()
454 try:
476 try:
455 st = fp.read()
477 st = fp.read()
456 finally:
478 finally:
457 fp.close()
479 fp.close()
458 except IOError as err:
480 except IOError as err:
459 if err.errno != errno.ENOENT:
481 if err.errno != errno.ENOENT:
460 raise
482 raise
461 return
483 return
462 if not st:
484 if not st:
463 return
485 return
464
486
465 if util.safehasattr(parsers, b'dict_new_presized'):
487 if util.safehasattr(parsers, b'dict_new_presized'):
466 # Make an estimate of the number of files in the dirstate based on
488 # Make an estimate of the number of files in the dirstate based on
467 # its size. This trades wasting some memory for avoiding costly
489 # its size. This trades wasting some memory for avoiding costly
468 # resizes. Each entry have a prefix of 17 bytes followed by one or
490 # resizes. Each entry have a prefix of 17 bytes followed by one or
469 # two path names. Studies on various large-scale real-world repositories
491 # two path names. Studies on various large-scale real-world repositories
470 # found 54 bytes a reasonable upper limit for the average path names.
492 # found 54 bytes a reasonable upper limit for the average path names.
471 # Copy entries are ignored for the sake of this estimate.
493 # Copy entries are ignored for the sake of this estimate.
472 self._map = parsers.dict_new_presized(len(st) // 71)
494 self._map = parsers.dict_new_presized(len(st) // 71)
473
495
474 # Python's garbage collector triggers a GC each time a certain number
496 # Python's garbage collector triggers a GC each time a certain number
475 # of container objects (the number being defined by
497 # of container objects (the number being defined by
476 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
498 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
477 # for each file in the dirstate. The C version then immediately marks
499 # for each file in the dirstate. The C version then immediately marks
478 # them as not to be tracked by the collector. However, this has no
500 # them as not to be tracked by the collector. However, this has no
479 # effect on when GCs are triggered, only on what objects the GC looks
501 # effect on when GCs are triggered, only on what objects the GC looks
480 # into. This means that O(number of files) GCs are unavoidable.
502 # into. This means that O(number of files) GCs are unavoidable.
481 # Depending on when in the process's lifetime the dirstate is parsed,
503 # Depending on when in the process's lifetime the dirstate is parsed,
482 # this can get very expensive. As a workaround, disable GC while
504 # this can get very expensive. As a workaround, disable GC while
483 # parsing the dirstate.
505 # parsing the dirstate.
484 #
506 #
485 # (we cannot decorate the function directly since it is in a C module)
507 # (we cannot decorate the function directly since it is in a C module)
486 parse_dirstate = util.nogc(parsers.parse_dirstate)
508 parse_dirstate = util.nogc(parsers.parse_dirstate)
487 p = parse_dirstate(self._map, self.copymap, st)
509 p = parse_dirstate(self._map, self.copymap, st)
488 if not self._dirtyparents:
510 if not self._dirtyparents:
489 self.setparents(*p)
511 self.setparents(*p)
490
512
491 # Avoid excess attribute lookups by fast pathing certain checks
513 # Avoid excess attribute lookups by fast pathing certain checks
492 self.__contains__ = self._map.__contains__
514 self.__contains__ = self._map.__contains__
493 self.__getitem__ = self._map.__getitem__
515 self.__getitem__ = self._map.__getitem__
494 self.get = self._map.get
516 self.get = self._map.get
495
517
496 def write(self, _tr, st, now):
518 def write(self, _tr, st, now):
497 st.write(
519 st.write(
498 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
520 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
499 )
521 )
500 st.close()
522 st.close()
501 self._dirtyparents = False
523 self._dirtyparents = False
502 self.nonnormalset, self.otherparentset = self.nonnormalentries()
524 self.nonnormalset, self.otherparentset = self.nonnormalentries()
503
525
504 @propertycache
526 @propertycache
505 def nonnormalset(self):
527 def nonnormalset(self):
506 nonnorm, otherparents = self.nonnormalentries()
528 nonnorm, otherparents = self.nonnormalentries()
507 self.otherparentset = otherparents
529 self.otherparentset = otherparents
508 return nonnorm
530 return nonnorm
509
531
510 @propertycache
532 @propertycache
511 def otherparentset(self):
533 def otherparentset(self):
512 nonnorm, otherparents = self.nonnormalentries()
534 nonnorm, otherparents = self.nonnormalentries()
513 self.nonnormalset = nonnorm
535 self.nonnormalset = nonnorm
514 return otherparents
536 return otherparents
515
537
516 def non_normal_or_other_parent_paths(self):
538 def non_normal_or_other_parent_paths(self):
517 return self.nonnormalset.union(self.otherparentset)
539 return self.nonnormalset.union(self.otherparentset)
518
540
519 @propertycache
541 @propertycache
520 def identity(self):
542 def identity(self):
521 self._map
543 self._map
522 return self.identity
544 return self.identity
523
545
524 @propertycache
546 @propertycache
525 def dirfoldmap(self):
547 def dirfoldmap(self):
526 f = {}
548 f = {}
527 normcase = util.normcase
549 normcase = util.normcase
528 for name in self._dirs:
550 for name in self._dirs:
529 f[normcase(name)] = name
551 f[normcase(name)] = name
530 return f
552 return f
531
553
532
554
533 if rustmod is not None:
555 if rustmod is not None:
534
556
535 class dirstatemap(object):
557 class dirstatemap(object):
536 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
558 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
537 self._use_dirstate_v2 = use_dirstate_v2
559 self._use_dirstate_v2 = use_dirstate_v2
538 self._nodeconstants = nodeconstants
560 self._nodeconstants = nodeconstants
539 self._ui = ui
561 self._ui = ui
540 self._opener = opener
562 self._opener = opener
541 self._root = root
563 self._root = root
542 self._filename = b'dirstate'
564 self._filename = b'dirstate'
543 self._nodelen = 20 # Also update Rust code when changing this!
565 self._nodelen = 20 # Also update Rust code when changing this!
544 self._parents = None
566 self._parents = None
545 self._dirtyparents = False
567 self._dirtyparents = False
546 self._docket = None
568 self._docket = None
547
569
548 # for consistent view between _pl() and _read() invocations
570 # for consistent view between _pl() and _read() invocations
549 self._pendingmode = None
571 self._pendingmode = None
550
572
551 self._use_dirstate_tree = self._ui.configbool(
573 self._use_dirstate_tree = self._ui.configbool(
552 b"experimental",
574 b"experimental",
553 b"dirstate-tree.in-memory",
575 b"dirstate-tree.in-memory",
554 False,
576 False,
555 )
577 )
556
578
557 def addfile(
579 def addfile(
558 self,
580 self,
559 f,
581 f,
560 mode=0,
582 mode=0,
561 size=None,
583 size=None,
562 mtime=None,
584 mtime=None,
563 added=False,
585 added=False,
564 merged=False,
586 merged=False,
565 from_p2=False,
587 from_p2=False,
566 possibly_dirty=False,
588 possibly_dirty=False,
567 ):
589 ):
568 return self._rustmap.addfile(
590 return self._rustmap.addfile(
569 f,
591 f,
570 mode,
592 mode,
571 size,
593 size,
572 mtime,
594 mtime,
573 added,
595 added,
574 merged,
596 merged,
575 from_p2,
597 from_p2,
576 possibly_dirty,
598 possibly_dirty,
577 )
599 )
578
600
579 def reset_state(
601 def reset_state(
580 self,
602 self,
581 filename,
603 filename,
582 wc_tracked,
604 wc_tracked,
583 p1_tracked,
605 p1_tracked,
584 p2_tracked=False,
606 p2_tracked=False,
585 merged=False,
607 merged=False,
586 clean_p1=False,
608 clean_p1=False,
587 clean_p2=False,
609 clean_p2=False,
588 possibly_dirty=False,
610 possibly_dirty=False,
589 parentfiledata=None,
611 parentfiledata=None,
590 ):
612 ):
591 """Set a entry to a given state, disregarding all previous state
613 """Set a entry to a given state, disregarding all previous state
592
614
593 This is to be used by the part of the dirstate API dedicated to
615 This is to be used by the part of the dirstate API dedicated to
594 adjusting the dirstate after a update/merge.
616 adjusting the dirstate after a update/merge.
595
617
596 note: calling this might result to no entry existing at all if the
618 note: calling this might result to no entry existing at all if the
597 dirstate map does not see any point at having one for this file
619 dirstate map does not see any point at having one for this file
598 anymore.
620 anymore.
599 """
621 """
600 if merged and (clean_p1 or clean_p2):
622 if merged and (clean_p1 or clean_p2):
601 msg = (
623 msg = (
602 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
624 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
603 )
625 )
604 raise error.ProgrammingError(msg)
626 raise error.ProgrammingError(msg)
605 # copy information are now outdated
627 # copy information are now outdated
606 # (maybe new information should be in directly passed to this function)
628 # (maybe new information should be in directly passed to this function)
607 self.copymap.pop(filename, None)
629 self.copymap.pop(filename, None)
608
630
609 if not (p1_tracked or p2_tracked or wc_tracked):
631 if not (p1_tracked or p2_tracked or wc_tracked):
610 self.dropfile(filename)
632 self.dropfile(filename)
611 elif merged:
633 elif merged:
612 # XXX might be merged and removed ?
634 # XXX might be merged and removed ?
613 entry = self.get(filename)
635 entry = self.get(filename)
614 if entry is not None and entry.tracked:
636 if entry is not None and entry.tracked:
615 # XXX mostly replicate dirstate.other parent. We should get
637 # XXX mostly replicate dirstate.other parent. We should get
616 # the higher layer to pass us more reliable data where `merged`
638 # the higher layer to pass us more reliable data where `merged`
617 # actually mean merged. Dropping the else clause will show
639 # actually mean merged. Dropping the else clause will show
618 # failure in `test-graft.t`
640 # failure in `test-graft.t`
619 self.addfile(filename, merged=True)
641 self.addfile(filename, merged=True)
620 else:
642 else:
621 self.addfile(filename, from_p2=True)
643 self.addfile(filename, from_p2=True)
622 elif not (p1_tracked or p2_tracked) and wc_tracked:
644 elif not (p1_tracked or p2_tracked) and wc_tracked:
623 self.addfile(
645 self.addfile(
624 filename, added=True, possibly_dirty=possibly_dirty
646 filename, added=True, possibly_dirty=possibly_dirty
625 )
647 )
626 elif (p1_tracked or p2_tracked) and not wc_tracked:
648 elif (p1_tracked or p2_tracked) and not wc_tracked:
627 # XXX might be merged and removed ?
649 # XXX might be merged and removed ?
628 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
650 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
629 self.nonnormalset.add(filename)
651 self.nonnormalset.add(filename)
630 elif clean_p2 and wc_tracked:
652 elif clean_p2 and wc_tracked:
631 if p1_tracked or self.get(filename) is not None:
653 if p1_tracked or self.get(filename) is not None:
632 # XXX the `self.get` call is catching some case in
654 # XXX the `self.get` call is catching some case in
633 # `test-merge-remove.t` where the file is tracked in p1, the
655 # `test-merge-remove.t` where the file is tracked in p1, the
634 # p1_tracked argument is False.
656 # p1_tracked argument is False.
635 #
657 #
636 # In addition, this seems to be a case where the file is marked
658 # In addition, this seems to be a case where the file is marked
637 # as merged without actually being the result of a merge
659 # as merged without actually being the result of a merge
638 # action. So thing are not ideal here.
660 # action. So thing are not ideal here.
639 self.addfile(filename, merged=True)
661 self.addfile(filename, merged=True)
640 else:
662 else:
641 self.addfile(filename, from_p2=True)
663 self.addfile(filename, from_p2=True)
642 elif not p1_tracked and p2_tracked and wc_tracked:
664 elif not p1_tracked and p2_tracked and wc_tracked:
643 self.addfile(
665 self.addfile(
644 filename, from_p2=True, possibly_dirty=possibly_dirty
666 filename, from_p2=True, possibly_dirty=possibly_dirty
645 )
667 )
646 elif possibly_dirty:
668 elif possibly_dirty:
647 self.addfile(filename, possibly_dirty=possibly_dirty)
669 self.addfile(filename, possibly_dirty=possibly_dirty)
648 elif wc_tracked:
670 elif wc_tracked:
649 # this is a "normal" file
671 # this is a "normal" file
650 if parentfiledata is None:
672 if parentfiledata is None:
651 msg = b'failed to pass parentfiledata for a normal file: %s'
673 msg = b'failed to pass parentfiledata for a normal file: %s'
652 msg %= filename
674 msg %= filename
653 raise error.ProgrammingError(msg)
675 raise error.ProgrammingError(msg)
654 mode, size, mtime = parentfiledata
676 mode, size, mtime = parentfiledata
655 self.addfile(filename, mode=mode, size=size, mtime=mtime)
677 self.addfile(filename, mode=mode, size=size, mtime=mtime)
656 self.nonnormalset.discard(filename)
678 self.nonnormalset.discard(filename)
657 else:
679 else:
658 assert False, 'unreachable'
680 assert False, 'unreachable'
659
681
660 def set_untracked(self, f):
682 def set_untracked(self, f):
661 """Mark a file as no longer tracked in the dirstate map"""
683 """Mark a file as no longer tracked in the dirstate map"""
662 # in merge is only trigger more logic, so it "fine" to pass it.
684 # in merge is only trigger more logic, so it "fine" to pass it.
663 #
685 #
664 # the inner rust dirstate map code need to be adjusted once the API
686 # the inner rust dirstate map code need to be adjusted once the API
665 # for dirstate/dirstatemap/DirstateItem is a bit more settled
687 # for dirstate/dirstatemap/DirstateItem is a bit more settled
666 self._rustmap.removefile(f, in_merge=True)
688 self._rustmap.removefile(f, in_merge=True)
667
689
668 def removefile(self, *args, **kwargs):
690 def removefile(self, *args, **kwargs):
669 return self._rustmap.removefile(*args, **kwargs)
691 return self._rustmap.removefile(*args, **kwargs)
670
692
671 def dropfile(self, *args, **kwargs):
693 def dropfile(self, *args, **kwargs):
672 return self._rustmap.dropfile(*args, **kwargs)
694 return self._rustmap.dropfile(*args, **kwargs)
673
695
674 def clearambiguoustimes(self, *args, **kwargs):
696 def clearambiguoustimes(self, *args, **kwargs):
675 return self._rustmap.clearambiguoustimes(*args, **kwargs)
697 return self._rustmap.clearambiguoustimes(*args, **kwargs)
676
698
677 def nonnormalentries(self):
699 def nonnormalentries(self):
678 return self._rustmap.nonnormalentries()
700 return self._rustmap.nonnormalentries()
679
701
680 def get(self, *args, **kwargs):
702 def get(self, *args, **kwargs):
681 return self._rustmap.get(*args, **kwargs)
703 return self._rustmap.get(*args, **kwargs)
682
704
683 @property
705 @property
684 def copymap(self):
706 def copymap(self):
685 return self._rustmap.copymap()
707 return self._rustmap.copymap()
686
708
687 def directories(self):
709 def directories(self):
688 return self._rustmap.directories()
710 return self._rustmap.directories()
689
711
690 def debug_iter(self):
712 def debug_iter(self):
691 return self._rustmap.debug_iter()
713 return self._rustmap.debug_iter()
692
714
693 def preload(self):
715 def preload(self):
694 self._rustmap
716 self._rustmap
695
717
696 def clear(self):
718 def clear(self):
697 self._rustmap.clear()
719 self._rustmap.clear()
698 self.setparents(
720 self.setparents(
699 self._nodeconstants.nullid, self._nodeconstants.nullid
721 self._nodeconstants.nullid, self._nodeconstants.nullid
700 )
722 )
701 util.clearcachedproperty(self, b"_dirs")
723 util.clearcachedproperty(self, b"_dirs")
702 util.clearcachedproperty(self, b"_alldirs")
724 util.clearcachedproperty(self, b"_alldirs")
703 util.clearcachedproperty(self, b"dirfoldmap")
725 util.clearcachedproperty(self, b"dirfoldmap")
704
726
705 def items(self):
727 def items(self):
706 return self._rustmap.items()
728 return self._rustmap.items()
707
729
708 def keys(self):
730 def keys(self):
709 return iter(self._rustmap)
731 return iter(self._rustmap)
710
732
711 def __contains__(self, key):
733 def __contains__(self, key):
712 return key in self._rustmap
734 return key in self._rustmap
713
735
714 def __getitem__(self, item):
736 def __getitem__(self, item):
715 return self._rustmap[item]
737 return self._rustmap[item]
716
738
717 def __len__(self):
739 def __len__(self):
718 return len(self._rustmap)
740 return len(self._rustmap)
719
741
720 def __iter__(self):
742 def __iter__(self):
721 return iter(self._rustmap)
743 return iter(self._rustmap)
722
744
723 # forward for python2,3 compat
745 # forward for python2,3 compat
724 iteritems = items
746 iteritems = items
725
747
726 def _opendirstatefile(self):
748 def _opendirstatefile(self):
727 fp, mode = txnutil.trypending(
749 fp, mode = txnutil.trypending(
728 self._root, self._opener, self._filename
750 self._root, self._opener, self._filename
729 )
751 )
730 if self._pendingmode is not None and self._pendingmode != mode:
752 if self._pendingmode is not None and self._pendingmode != mode:
731 fp.close()
753 fp.close()
732 raise error.Abort(
754 raise error.Abort(
733 _(b'working directory state may be changed parallelly')
755 _(b'working directory state may be changed parallelly')
734 )
756 )
735 self._pendingmode = mode
757 self._pendingmode = mode
736 return fp
758 return fp
737
759
738 def _readdirstatefile(self, size=-1):
760 def _readdirstatefile(self, size=-1):
739 try:
761 try:
740 with self._opendirstatefile() as fp:
762 with self._opendirstatefile() as fp:
741 return fp.read(size)
763 return fp.read(size)
742 except IOError as err:
764 except IOError as err:
743 if err.errno != errno.ENOENT:
765 if err.errno != errno.ENOENT:
744 raise
766 raise
745 # File doesn't exist, so the current state is empty
767 # File doesn't exist, so the current state is empty
746 return b''
768 return b''
747
769
748 def setparents(self, p1, p2):
770 def setparents(self, p1, p2):
749 self._parents = (p1, p2)
771 self._parents = (p1, p2)
750 self._dirtyparents = True
772 self._dirtyparents = True
751
773
752 def parents(self):
774 def parents(self):
753 if not self._parents:
775 if not self._parents:
754 if self._use_dirstate_v2:
776 if self._use_dirstate_v2:
755 self._parents = self.docket.parents
777 self._parents = self.docket.parents
756 else:
778 else:
757 read_len = self._nodelen * 2
779 read_len = self._nodelen * 2
758 st = self._readdirstatefile(read_len)
780 st = self._readdirstatefile(read_len)
759 l = len(st)
781 l = len(st)
760 if l == read_len:
782 if l == read_len:
761 self._parents = (
783 self._parents = (
762 st[: self._nodelen],
784 st[: self._nodelen],
763 st[self._nodelen : 2 * self._nodelen],
785 st[self._nodelen : 2 * self._nodelen],
764 )
786 )
765 elif l == 0:
787 elif l == 0:
766 self._parents = (
788 self._parents = (
767 self._nodeconstants.nullid,
789 self._nodeconstants.nullid,
768 self._nodeconstants.nullid,
790 self._nodeconstants.nullid,
769 )
791 )
770 else:
792 else:
771 raise error.Abort(
793 raise error.Abort(
772 _(b'working directory state appears damaged!')
794 _(b'working directory state appears damaged!')
773 )
795 )
774
796
775 return self._parents
797 return self._parents
776
798
777 @property
799 @property
778 def docket(self):
800 def docket(self):
779 if not self._docket:
801 if not self._docket:
780 if not self._use_dirstate_v2:
802 if not self._use_dirstate_v2:
781 raise error.ProgrammingError(
803 raise error.ProgrammingError(
782 b'dirstate only has a docket in v2 format'
804 b'dirstate only has a docket in v2 format'
783 )
805 )
784 self._docket = docketmod.DirstateDocket.parse(
806 self._docket = docketmod.DirstateDocket.parse(
785 self._readdirstatefile(), self._nodeconstants
807 self._readdirstatefile(), self._nodeconstants
786 )
808 )
787 return self._docket
809 return self._docket
788
810
789 @propertycache
811 @propertycache
790 def _rustmap(self):
812 def _rustmap(self):
791 """
813 """
792 Fills the Dirstatemap when called.
814 Fills the Dirstatemap when called.
793 """
815 """
794 # ignore HG_PENDING because identity is used only for writing
816 # ignore HG_PENDING because identity is used only for writing
795 self.identity = util.filestat.frompath(
817 self.identity = util.filestat.frompath(
796 self._opener.join(self._filename)
818 self._opener.join(self._filename)
797 )
819 )
798
820
799 if self._use_dirstate_v2:
821 if self._use_dirstate_v2:
800 if self.docket.uuid:
822 if self.docket.uuid:
801 # TODO: use mmap when possible
823 # TODO: use mmap when possible
802 data = self._opener.read(self.docket.data_filename())
824 data = self._opener.read(self.docket.data_filename())
803 else:
825 else:
804 data = b''
826 data = b''
805 self._rustmap = rustmod.DirstateMap.new_v2(
827 self._rustmap = rustmod.DirstateMap.new_v2(
806 data, self.docket.data_size, self.docket.tree_metadata
828 data, self.docket.data_size, self.docket.tree_metadata
807 )
829 )
808 parents = self.docket.parents
830 parents = self.docket.parents
809 else:
831 else:
810 self._rustmap, parents = rustmod.DirstateMap.new_v1(
832 self._rustmap, parents = rustmod.DirstateMap.new_v1(
811 self._use_dirstate_tree, self._readdirstatefile()
833 self._use_dirstate_tree, self._readdirstatefile()
812 )
834 )
813
835
814 if parents and not self._dirtyparents:
836 if parents and not self._dirtyparents:
815 self.setparents(*parents)
837 self.setparents(*parents)
816
838
817 self.__contains__ = self._rustmap.__contains__
839 self.__contains__ = self._rustmap.__contains__
818 self.__getitem__ = self._rustmap.__getitem__
840 self.__getitem__ = self._rustmap.__getitem__
819 self.get = self._rustmap.get
841 self.get = self._rustmap.get
820 return self._rustmap
842 return self._rustmap
821
843
822 def write(self, tr, st, now):
844 def write(self, tr, st, now):
823 if not self._use_dirstate_v2:
845 if not self._use_dirstate_v2:
824 p1, p2 = self.parents()
846 p1, p2 = self.parents()
825 packed = self._rustmap.write_v1(p1, p2, now)
847 packed = self._rustmap.write_v1(p1, p2, now)
826 st.write(packed)
848 st.write(packed)
827 st.close()
849 st.close()
828 self._dirtyparents = False
850 self._dirtyparents = False
829 return
851 return
830
852
831 # We can only append to an existing data file if there is one
853 # We can only append to an existing data file if there is one
832 can_append = self.docket.uuid is not None
854 can_append = self.docket.uuid is not None
833 packed, meta, append = self._rustmap.write_v2(now, can_append)
855 packed, meta, append = self._rustmap.write_v2(now, can_append)
834 if append:
856 if append:
835 docket = self.docket
857 docket = self.docket
836 data_filename = docket.data_filename()
858 data_filename = docket.data_filename()
837 if tr:
859 if tr:
838 tr.add(data_filename, docket.data_size)
860 tr.add(data_filename, docket.data_size)
839 with self._opener(data_filename, b'r+b') as fp:
861 with self._opener(data_filename, b'r+b') as fp:
840 fp.seek(docket.data_size)
862 fp.seek(docket.data_size)
841 assert fp.tell() == docket.data_size
863 assert fp.tell() == docket.data_size
842 written = fp.write(packed)
864 written = fp.write(packed)
843 if written is not None: # py2 may return None
865 if written is not None: # py2 may return None
844 assert written == len(packed), (written, len(packed))
866 assert written == len(packed), (written, len(packed))
845 docket.data_size += len(packed)
867 docket.data_size += len(packed)
846 docket.parents = self.parents()
868 docket.parents = self.parents()
847 docket.tree_metadata = meta
869 docket.tree_metadata = meta
848 st.write(docket.serialize())
870 st.write(docket.serialize())
849 st.close()
871 st.close()
850 else:
872 else:
851 old_docket = self.docket
873 old_docket = self.docket
852 new_docket = docketmod.DirstateDocket.with_new_uuid(
874 new_docket = docketmod.DirstateDocket.with_new_uuid(
853 self.parents(), len(packed), meta
875 self.parents(), len(packed), meta
854 )
876 )
855 data_filename = new_docket.data_filename()
877 data_filename = new_docket.data_filename()
856 if tr:
878 if tr:
857 tr.add(data_filename, 0)
879 tr.add(data_filename, 0)
858 self._opener.write(data_filename, packed)
880 self._opener.write(data_filename, packed)
859 # Write the new docket after the new data file has been
881 # Write the new docket after the new data file has been
860 # written. Because `st` was opened with `atomictemp=True`,
882 # written. Because `st` was opened with `atomictemp=True`,
861 # the actual `.hg/dirstate` file is only affected on close.
883 # the actual `.hg/dirstate` file is only affected on close.
862 st.write(new_docket.serialize())
884 st.write(new_docket.serialize())
863 st.close()
885 st.close()
864 # Remove the old data file after the new docket pointing to
886 # Remove the old data file after the new docket pointing to
865 # the new data file was written.
887 # the new data file was written.
866 if old_docket.uuid:
888 if old_docket.uuid:
867 data_filename = old_docket.data_filename()
889 data_filename = old_docket.data_filename()
868 unlink = lambda _tr=None: self._opener.unlink(data_filename)
890 unlink = lambda _tr=None: self._opener.unlink(data_filename)
869 if tr:
891 if tr:
870 category = b"dirstate-v2-clean-" + old_docket.uuid
892 category = b"dirstate-v2-clean-" + old_docket.uuid
871 tr.addpostclose(category, unlink)
893 tr.addpostclose(category, unlink)
872 else:
894 else:
873 unlink()
895 unlink()
874 self._docket = new_docket
896 self._docket = new_docket
875 # Reload from the newly-written file
897 # Reload from the newly-written file
876 util.clearcachedproperty(self, b"_rustmap")
898 util.clearcachedproperty(self, b"_rustmap")
877 self._dirtyparents = False
899 self._dirtyparents = False
878
900
879 @propertycache
901 @propertycache
880 def filefoldmap(self):
902 def filefoldmap(self):
881 """Returns a dictionary mapping normalized case paths to their
903 """Returns a dictionary mapping normalized case paths to their
882 non-normalized versions.
904 non-normalized versions.
883 """
905 """
884 return self._rustmap.filefoldmapasdict()
906 return self._rustmap.filefoldmapasdict()
885
907
886 def hastrackeddir(self, d):
908 def hastrackeddir(self, d):
887 return self._rustmap.hastrackeddir(d)
909 return self._rustmap.hastrackeddir(d)
888
910
889 def hasdir(self, d):
911 def hasdir(self, d):
890 return self._rustmap.hasdir(d)
912 return self._rustmap.hasdir(d)
891
913
892 @propertycache
914 @propertycache
893 def identity(self):
915 def identity(self):
894 self._rustmap
916 self._rustmap
895 return self.identity
917 return self.identity
896
918
897 @property
919 @property
898 def nonnormalset(self):
920 def nonnormalset(self):
899 nonnorm = self._rustmap.non_normal_entries()
921 nonnorm = self._rustmap.non_normal_entries()
900 return nonnorm
922 return nonnorm
901
923
902 @propertycache
924 @propertycache
903 def otherparentset(self):
925 def otherparentset(self):
904 otherparents = self._rustmap.other_parent_entries()
926 otherparents = self._rustmap.other_parent_entries()
905 return otherparents
927 return otherparents
906
928
907 def non_normal_or_other_parent_paths(self):
929 def non_normal_or_other_parent_paths(self):
908 return self._rustmap.non_normal_or_other_parent_paths()
930 return self._rustmap.non_normal_or_other_parent_paths()
909
931
910 @propertycache
932 @propertycache
911 def dirfoldmap(self):
933 def dirfoldmap(self):
912 f = {}
934 f = {}
913 normcase = util.normcase
935 normcase = util.normcase
914 for name in self._rustmap.tracked_dirs():
936 for name in self._rustmap.tracked_dirs():
915 f[normcase(name)] = name
937 f[normcase(name)] = name
916 return f
938 return f
917
939
918 def set_possibly_dirty(self, filename):
940 def set_possibly_dirty(self, filename):
919 """record that the current state of the file on disk is unknown"""
941 """record that the current state of the file on disk is unknown"""
920 entry = self[filename]
942 entry = self[filename]
921 entry.set_possibly_dirty()
943 entry.set_possibly_dirty()
922 self._rustmap.set_v1(filename, entry)
944 self._rustmap.set_v1(filename, entry)
923
945
924 def __setitem__(self, key, value):
946 def __setitem__(self, key, value):
925 assert isinstance(value, DirstateItem)
947 assert isinstance(value, DirstateItem)
926 self._rustmap.set_v1(key, value)
948 self._rustmap.set_v1(key, value)
General Comments 0
You need to be logged in to leave comments. Login now