##// END OF EJS Templates
dirstatemap: use the default code to handle "possibly_dirty" case...
marmoute -
r48710:625b6ddb default
parent child Browse files
Show More
@@ -1,944 +1,943 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 pass
302 return
303 elif wc_tracked:
302 elif wc_tracked:
304 # this is a "normal" file
303 # this is a "normal" file
305 if parentfiledata is None:
304 if parentfiledata is None:
306 msg = b'failed to pass parentfiledata for a normal file: %s'
305 msg = b'failed to pass parentfiledata for a normal file: %s'
307 msg %= filename
306 msg %= filename
308 raise error.ProgrammingError(msg)
307 raise error.ProgrammingError(msg)
309 else:
308 else:
310 assert False, 'unreachable'
309 assert False, 'unreachable'
311
310
312 old_entry = self._map.get(filename)
311 old_entry = self._map.get(filename)
313 self._dirs_incr(filename, old_entry)
312 self._dirs_incr(filename, old_entry)
314 entry = DirstateItem(
313 entry = DirstateItem(
315 wc_tracked=wc_tracked,
314 wc_tracked=wc_tracked,
316 p1_tracked=p1_tracked,
315 p1_tracked=p1_tracked,
317 p2_tracked=p2_tracked,
316 p2_tracked=p2_tracked,
318 merged=merged,
317 merged=merged,
319 clean_p1=clean_p1,
318 clean_p1=clean_p1,
320 clean_p2=clean_p2,
319 clean_p2=clean_p2,
321 possibly_dirty=possibly_dirty,
320 possibly_dirty=possibly_dirty,
322 parentfiledata=parentfiledata,
321 parentfiledata=parentfiledata,
323 )
322 )
324 if entry.dm_nonnormal:
323 if entry.dm_nonnormal:
325 self.nonnormalset.add(filename)
324 self.nonnormalset.add(filename)
326 else:
325 else:
327 self.nonnormalset.discard(filename)
326 self.nonnormalset.discard(filename)
328 if entry.dm_otherparent:
327 if entry.dm_otherparent:
329 self.otherparentset.add(filename)
328 self.otherparentset.add(filename)
330 else:
329 else:
331 self.otherparentset.discard(filename)
330 self.otherparentset.discard(filename)
332 self._map[filename] = entry
331 self._map[filename] = entry
333
332
334 def set_untracked(self, f):
333 def set_untracked(self, f):
335 """Mark a file as no longer tracked in the dirstate map"""
334 """Mark a file as no longer tracked in the dirstate map"""
336 entry = self[f]
335 entry = self[f]
337 self._dirs_decr(f, old_entry=entry, remove_variant=True)
336 self._dirs_decr(f, old_entry=entry, remove_variant=True)
338 if entry.from_p2:
337 if entry.from_p2:
339 self.otherparentset.add(f)
338 self.otherparentset.add(f)
340 elif not entry.merged:
339 elif not entry.merged:
341 self.copymap.pop(f, None)
340 self.copymap.pop(f, None)
342 entry.set_untracked()
341 entry.set_untracked()
343 self.nonnormalset.add(f)
342 self.nonnormalset.add(f)
344
343
345 def dropfile(self, f):
344 def dropfile(self, f):
346 """
345 """
347 Remove a file from the dirstate. Returns True if the file was
346 Remove a file from the dirstate. Returns True if the file was
348 previously recorded.
347 previously recorded.
349 """
348 """
350 old_entry = self._map.pop(f, None)
349 old_entry = self._map.pop(f, None)
351 self._dirs_decr(f, old_entry=old_entry)
350 self._dirs_decr(f, old_entry=old_entry)
352 self.nonnormalset.discard(f)
351 self.nonnormalset.discard(f)
353 return old_entry is not None
352 return old_entry is not None
354
353
355 def clearambiguoustimes(self, files, now):
354 def clearambiguoustimes(self, files, now):
356 for f in files:
355 for f in files:
357 e = self.get(f)
356 e = self.get(f)
358 if e is not None and e.need_delay(now):
357 if e is not None and e.need_delay(now):
359 e.set_possibly_dirty()
358 e.set_possibly_dirty()
360 self.nonnormalset.add(f)
359 self.nonnormalset.add(f)
361
360
362 def nonnormalentries(self):
361 def nonnormalentries(self):
363 '''Compute the nonnormal dirstate entries from the dmap'''
362 '''Compute the nonnormal dirstate entries from the dmap'''
364 try:
363 try:
365 return parsers.nonnormalotherparententries(self._map)
364 return parsers.nonnormalotherparententries(self._map)
366 except AttributeError:
365 except AttributeError:
367 nonnorm = set()
366 nonnorm = set()
368 otherparent = set()
367 otherparent = set()
369 for fname, e in pycompat.iteritems(self._map):
368 for fname, e in pycompat.iteritems(self._map):
370 if e.dm_nonnormal:
369 if e.dm_nonnormal:
371 nonnorm.add(fname)
370 nonnorm.add(fname)
372 if e.from_p2:
371 if e.from_p2:
373 otherparent.add(fname)
372 otherparent.add(fname)
374 return nonnorm, otherparent
373 return nonnorm, otherparent
375
374
376 @propertycache
375 @propertycache
377 def filefoldmap(self):
376 def filefoldmap(self):
378 """Returns a dictionary mapping normalized case paths to their
377 """Returns a dictionary mapping normalized case paths to their
379 non-normalized versions.
378 non-normalized versions.
380 """
379 """
381 try:
380 try:
382 makefilefoldmap = parsers.make_file_foldmap
381 makefilefoldmap = parsers.make_file_foldmap
383 except AttributeError:
382 except AttributeError:
384 pass
383 pass
385 else:
384 else:
386 return makefilefoldmap(
385 return makefilefoldmap(
387 self._map, util.normcasespec, util.normcasefallback
386 self._map, util.normcasespec, util.normcasefallback
388 )
387 )
389
388
390 f = {}
389 f = {}
391 normcase = util.normcase
390 normcase = util.normcase
392 for name, s in pycompat.iteritems(self._map):
391 for name, s in pycompat.iteritems(self._map):
393 if not s.removed:
392 if not s.removed:
394 f[normcase(name)] = name
393 f[normcase(name)] = name
395 f[b'.'] = b'.' # prevents useless util.fspath() invocation
394 f[b'.'] = b'.' # prevents useless util.fspath() invocation
396 return f
395 return f
397
396
398 def hastrackeddir(self, d):
397 def hastrackeddir(self, d):
399 """
398 """
400 Returns True if the dirstate contains a tracked (not removed) file
399 Returns True if the dirstate contains a tracked (not removed) file
401 in this directory.
400 in this directory.
402 """
401 """
403 return d in self._dirs
402 return d in self._dirs
404
403
405 def hasdir(self, d):
404 def hasdir(self, d):
406 """
405 """
407 Returns True if the dirstate contains a file (tracked or removed)
406 Returns True if the dirstate contains a file (tracked or removed)
408 in this directory.
407 in this directory.
409 """
408 """
410 return d in self._alldirs
409 return d in self._alldirs
411
410
412 @propertycache
411 @propertycache
413 def _dirs(self):
412 def _dirs(self):
414 return pathutil.dirs(self._map, b'r')
413 return pathutil.dirs(self._map, b'r')
415
414
416 @propertycache
415 @propertycache
417 def _alldirs(self):
416 def _alldirs(self):
418 return pathutil.dirs(self._map)
417 return pathutil.dirs(self._map)
419
418
420 def _opendirstatefile(self):
419 def _opendirstatefile(self):
421 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
420 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
422 if self._pendingmode is not None and self._pendingmode != mode:
421 if self._pendingmode is not None and self._pendingmode != mode:
423 fp.close()
422 fp.close()
424 raise error.Abort(
423 raise error.Abort(
425 _(b'working directory state may be changed parallelly')
424 _(b'working directory state may be changed parallelly')
426 )
425 )
427 self._pendingmode = mode
426 self._pendingmode = mode
428 return fp
427 return fp
429
428
430 def parents(self):
429 def parents(self):
431 if not self._parents:
430 if not self._parents:
432 try:
431 try:
433 fp = self._opendirstatefile()
432 fp = self._opendirstatefile()
434 st = fp.read(2 * self._nodelen)
433 st = fp.read(2 * self._nodelen)
435 fp.close()
434 fp.close()
436 except IOError as err:
435 except IOError as err:
437 if err.errno != errno.ENOENT:
436 if err.errno != errno.ENOENT:
438 raise
437 raise
439 # File doesn't exist, so the current state is empty
438 # File doesn't exist, so the current state is empty
440 st = b''
439 st = b''
441
440
442 l = len(st)
441 l = len(st)
443 if l == self._nodelen * 2:
442 if l == self._nodelen * 2:
444 self._parents = (
443 self._parents = (
445 st[: self._nodelen],
444 st[: self._nodelen],
446 st[self._nodelen : 2 * self._nodelen],
445 st[self._nodelen : 2 * self._nodelen],
447 )
446 )
448 elif l == 0:
447 elif l == 0:
449 self._parents = (
448 self._parents = (
450 self._nodeconstants.nullid,
449 self._nodeconstants.nullid,
451 self._nodeconstants.nullid,
450 self._nodeconstants.nullid,
452 )
451 )
453 else:
452 else:
454 raise error.Abort(
453 raise error.Abort(
455 _(b'working directory state appears damaged!')
454 _(b'working directory state appears damaged!')
456 )
455 )
457
456
458 return self._parents
457 return self._parents
459
458
460 def setparents(self, p1, p2):
459 def setparents(self, p1, p2):
461 self._parents = (p1, p2)
460 self._parents = (p1, p2)
462 self._dirtyparents = True
461 self._dirtyparents = True
463
462
464 def read(self):
463 def read(self):
465 # ignore HG_PENDING because identity is used only for writing
464 # ignore HG_PENDING because identity is used only for writing
466 self.identity = util.filestat.frompath(
465 self.identity = util.filestat.frompath(
467 self._opener.join(self._filename)
466 self._opener.join(self._filename)
468 )
467 )
469
468
470 try:
469 try:
471 fp = self._opendirstatefile()
470 fp = self._opendirstatefile()
472 try:
471 try:
473 st = fp.read()
472 st = fp.read()
474 finally:
473 finally:
475 fp.close()
474 fp.close()
476 except IOError as err:
475 except IOError as err:
477 if err.errno != errno.ENOENT:
476 if err.errno != errno.ENOENT:
478 raise
477 raise
479 return
478 return
480 if not st:
479 if not st:
481 return
480 return
482
481
483 if util.safehasattr(parsers, b'dict_new_presized'):
482 if util.safehasattr(parsers, b'dict_new_presized'):
484 # Make an estimate of the number of files in the dirstate based on
483 # Make an estimate of the number of files in the dirstate based on
485 # its size. This trades wasting some memory for avoiding costly
484 # its size. This trades wasting some memory for avoiding costly
486 # resizes. Each entry have a prefix of 17 bytes followed by one or
485 # resizes. Each entry have a prefix of 17 bytes followed by one or
487 # two path names. Studies on various large-scale real-world repositories
486 # two path names. Studies on various large-scale real-world repositories
488 # found 54 bytes a reasonable upper limit for the average path names.
487 # found 54 bytes a reasonable upper limit for the average path names.
489 # Copy entries are ignored for the sake of this estimate.
488 # Copy entries are ignored for the sake of this estimate.
490 self._map = parsers.dict_new_presized(len(st) // 71)
489 self._map = parsers.dict_new_presized(len(st) // 71)
491
490
492 # Python's garbage collector triggers a GC each time a certain number
491 # Python's garbage collector triggers a GC each time a certain number
493 # of container objects (the number being defined by
492 # of container objects (the number being defined by
494 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
493 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
495 # for each file in the dirstate. The C version then immediately marks
494 # for each file in the dirstate. The C version then immediately marks
496 # them as not to be tracked by the collector. However, this has no
495 # them as not to be tracked by the collector. However, this has no
497 # effect on when GCs are triggered, only on what objects the GC looks
496 # effect on when GCs are triggered, only on what objects the GC looks
498 # into. This means that O(number of files) GCs are unavoidable.
497 # into. This means that O(number of files) GCs are unavoidable.
499 # Depending on when in the process's lifetime the dirstate is parsed,
498 # Depending on when in the process's lifetime the dirstate is parsed,
500 # this can get very expensive. As a workaround, disable GC while
499 # this can get very expensive. As a workaround, disable GC while
501 # parsing the dirstate.
500 # parsing the dirstate.
502 #
501 #
503 # (we cannot decorate the function directly since it is in a C module)
502 # (we cannot decorate the function directly since it is in a C module)
504 parse_dirstate = util.nogc(parsers.parse_dirstate)
503 parse_dirstate = util.nogc(parsers.parse_dirstate)
505 p = parse_dirstate(self._map, self.copymap, st)
504 p = parse_dirstate(self._map, self.copymap, st)
506 if not self._dirtyparents:
505 if not self._dirtyparents:
507 self.setparents(*p)
506 self.setparents(*p)
508
507
509 # Avoid excess attribute lookups by fast pathing certain checks
508 # Avoid excess attribute lookups by fast pathing certain checks
510 self.__contains__ = self._map.__contains__
509 self.__contains__ = self._map.__contains__
511 self.__getitem__ = self._map.__getitem__
510 self.__getitem__ = self._map.__getitem__
512 self.get = self._map.get
511 self.get = self._map.get
513
512
514 def write(self, _tr, st, now):
513 def write(self, _tr, st, now):
515 st.write(
514 st.write(
516 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
515 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
517 )
516 )
518 st.close()
517 st.close()
519 self._dirtyparents = False
518 self._dirtyparents = False
520 self.nonnormalset, self.otherparentset = self.nonnormalentries()
519 self.nonnormalset, self.otherparentset = self.nonnormalentries()
521
520
522 @propertycache
521 @propertycache
523 def nonnormalset(self):
522 def nonnormalset(self):
524 nonnorm, otherparents = self.nonnormalentries()
523 nonnorm, otherparents = self.nonnormalentries()
525 self.otherparentset = otherparents
524 self.otherparentset = otherparents
526 return nonnorm
525 return nonnorm
527
526
528 @propertycache
527 @propertycache
529 def otherparentset(self):
528 def otherparentset(self):
530 nonnorm, otherparents = self.nonnormalentries()
529 nonnorm, otherparents = self.nonnormalentries()
531 self.nonnormalset = nonnorm
530 self.nonnormalset = nonnorm
532 return otherparents
531 return otherparents
533
532
534 def non_normal_or_other_parent_paths(self):
533 def non_normal_or_other_parent_paths(self):
535 return self.nonnormalset.union(self.otherparentset)
534 return self.nonnormalset.union(self.otherparentset)
536
535
537 @propertycache
536 @propertycache
538 def identity(self):
537 def identity(self):
539 self._map
538 self._map
540 return self.identity
539 return self.identity
541
540
542 @propertycache
541 @propertycache
543 def dirfoldmap(self):
542 def dirfoldmap(self):
544 f = {}
543 f = {}
545 normcase = util.normcase
544 normcase = util.normcase
546 for name in self._dirs:
545 for name in self._dirs:
547 f[normcase(name)] = name
546 f[normcase(name)] = name
548 return f
547 return f
549
548
550
549
551 if rustmod is not None:
550 if rustmod is not None:
552
551
553 class dirstatemap(object):
552 class dirstatemap(object):
554 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
553 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
555 self._use_dirstate_v2 = use_dirstate_v2
554 self._use_dirstate_v2 = use_dirstate_v2
556 self._nodeconstants = nodeconstants
555 self._nodeconstants = nodeconstants
557 self._ui = ui
556 self._ui = ui
558 self._opener = opener
557 self._opener = opener
559 self._root = root
558 self._root = root
560 self._filename = b'dirstate'
559 self._filename = b'dirstate'
561 self._nodelen = 20 # Also update Rust code when changing this!
560 self._nodelen = 20 # Also update Rust code when changing this!
562 self._parents = None
561 self._parents = None
563 self._dirtyparents = False
562 self._dirtyparents = False
564 self._docket = None
563 self._docket = None
565
564
566 # for consistent view between _pl() and _read() invocations
565 # for consistent view between _pl() and _read() invocations
567 self._pendingmode = None
566 self._pendingmode = None
568
567
569 self._use_dirstate_tree = self._ui.configbool(
568 self._use_dirstate_tree = self._ui.configbool(
570 b"experimental",
569 b"experimental",
571 b"dirstate-tree.in-memory",
570 b"dirstate-tree.in-memory",
572 False,
571 False,
573 )
572 )
574
573
575 def addfile(
574 def addfile(
576 self,
575 self,
577 f,
576 f,
578 mode=0,
577 mode=0,
579 size=None,
578 size=None,
580 mtime=None,
579 mtime=None,
581 added=False,
580 added=False,
582 merged=False,
581 merged=False,
583 from_p2=False,
582 from_p2=False,
584 possibly_dirty=False,
583 possibly_dirty=False,
585 ):
584 ):
586 return self._rustmap.addfile(
585 return self._rustmap.addfile(
587 f,
586 f,
588 mode,
587 mode,
589 size,
588 size,
590 mtime,
589 mtime,
591 added,
590 added,
592 merged,
591 merged,
593 from_p2,
592 from_p2,
594 possibly_dirty,
593 possibly_dirty,
595 )
594 )
596
595
597 def reset_state(
596 def reset_state(
598 self,
597 self,
599 filename,
598 filename,
600 wc_tracked,
599 wc_tracked,
601 p1_tracked,
600 p1_tracked,
602 p2_tracked=False,
601 p2_tracked=False,
603 merged=False,
602 merged=False,
604 clean_p1=False,
603 clean_p1=False,
605 clean_p2=False,
604 clean_p2=False,
606 possibly_dirty=False,
605 possibly_dirty=False,
607 parentfiledata=None,
606 parentfiledata=None,
608 ):
607 ):
609 """Set a entry to a given state, disregarding all previous state
608 """Set a entry to a given state, disregarding all previous state
610
609
611 This is to be used by the part of the dirstate API dedicated to
610 This is to be used by the part of the dirstate API dedicated to
612 adjusting the dirstate after a update/merge.
611 adjusting the dirstate after a update/merge.
613
612
614 note: calling this might result to no entry existing at all if the
613 note: calling this might result to no entry existing at all if the
615 dirstate map does not see any point at having one for this file
614 dirstate map does not see any point at having one for this file
616 anymore.
615 anymore.
617 """
616 """
618 if merged and (clean_p1 or clean_p2):
617 if merged and (clean_p1 or clean_p2):
619 msg = (
618 msg = (
620 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
619 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
621 )
620 )
622 raise error.ProgrammingError(msg)
621 raise error.ProgrammingError(msg)
623 # copy information are now outdated
622 # copy information are now outdated
624 # (maybe new information should be in directly passed to this function)
623 # (maybe new information should be in directly passed to this function)
625 self.copymap.pop(filename, None)
624 self.copymap.pop(filename, None)
626
625
627 if not (p1_tracked or p2_tracked or wc_tracked):
626 if not (p1_tracked or p2_tracked or wc_tracked):
628 self.dropfile(filename)
627 self.dropfile(filename)
629 elif merged:
628 elif merged:
630 # XXX might be merged and removed ?
629 # XXX might be merged and removed ?
631 entry = self.get(filename)
630 entry = self.get(filename)
632 if entry is not None and entry.tracked:
631 if entry is not None and entry.tracked:
633 # XXX mostly replicate dirstate.other parent. We should get
632 # XXX mostly replicate dirstate.other parent. We should get
634 # the higher layer to pass us more reliable data where `merged`
633 # the higher layer to pass us more reliable data where `merged`
635 # actually mean merged. Dropping the else clause will show
634 # actually mean merged. Dropping the else clause will show
636 # failure in `test-graft.t`
635 # failure in `test-graft.t`
637 self.addfile(filename, merged=True)
636 self.addfile(filename, merged=True)
638 else:
637 else:
639 self.addfile(filename, from_p2=True)
638 self.addfile(filename, from_p2=True)
640 elif not (p1_tracked or p2_tracked) and wc_tracked:
639 elif not (p1_tracked or p2_tracked) and wc_tracked:
641 self.addfile(
640 self.addfile(
642 filename, added=True, possibly_dirty=possibly_dirty
641 filename, added=True, possibly_dirty=possibly_dirty
643 )
642 )
644 elif (p1_tracked or p2_tracked) and not wc_tracked:
643 elif (p1_tracked or p2_tracked) and not wc_tracked:
645 # XXX might be merged and removed ?
644 # XXX might be merged and removed ?
646 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
645 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
647 self.nonnormalset.add(filename)
646 self.nonnormalset.add(filename)
648 elif clean_p2 and wc_tracked:
647 elif clean_p2 and wc_tracked:
649 if p1_tracked or self.get(filename) is not None:
648 if p1_tracked or self.get(filename) is not None:
650 # XXX the `self.get` call is catching some case in
649 # XXX the `self.get` call is catching some case in
651 # `test-merge-remove.t` where the file is tracked in p1, the
650 # `test-merge-remove.t` where the file is tracked in p1, the
652 # p1_tracked argument is False.
651 # p1_tracked argument is False.
653 #
652 #
654 # In addition, this seems to be a case where the file is marked
653 # In addition, this seems to be a case where the file is marked
655 # as merged without actually being the result of a merge
654 # as merged without actually being the result of a merge
656 # action. So thing are not ideal here.
655 # action. So thing are not ideal here.
657 self.addfile(filename, merged=True)
656 self.addfile(filename, merged=True)
658 else:
657 else:
659 self.addfile(filename, from_p2=True)
658 self.addfile(filename, from_p2=True)
660 elif not p1_tracked and p2_tracked and wc_tracked:
659 elif not p1_tracked and p2_tracked and wc_tracked:
661 self.addfile(
660 self.addfile(
662 filename, from_p2=True, possibly_dirty=possibly_dirty
661 filename, from_p2=True, possibly_dirty=possibly_dirty
663 )
662 )
664 elif possibly_dirty:
663 elif possibly_dirty:
665 self.addfile(filename, possibly_dirty=possibly_dirty)
664 self.addfile(filename, possibly_dirty=possibly_dirty)
666 elif wc_tracked:
665 elif wc_tracked:
667 # this is a "normal" file
666 # this is a "normal" file
668 if parentfiledata is None:
667 if parentfiledata is None:
669 msg = b'failed to pass parentfiledata for a normal file: %s'
668 msg = b'failed to pass parentfiledata for a normal file: %s'
670 msg %= filename
669 msg %= filename
671 raise error.ProgrammingError(msg)
670 raise error.ProgrammingError(msg)
672 mode, size, mtime = parentfiledata
671 mode, size, mtime = parentfiledata
673 self.addfile(filename, mode=mode, size=size, mtime=mtime)
672 self.addfile(filename, mode=mode, size=size, mtime=mtime)
674 self.nonnormalset.discard(filename)
673 self.nonnormalset.discard(filename)
675 else:
674 else:
676 assert False, 'unreachable'
675 assert False, 'unreachable'
677
676
678 def set_untracked(self, f):
677 def set_untracked(self, f):
679 """Mark a file as no longer tracked in the dirstate map"""
678 """Mark a file as no longer tracked in the dirstate map"""
680 # in merge is only trigger more logic, so it "fine" to pass it.
679 # in merge is only trigger more logic, so it "fine" to pass it.
681 #
680 #
682 # the inner rust dirstate map code need to be adjusted once the API
681 # the inner rust dirstate map code need to be adjusted once the API
683 # for dirstate/dirstatemap/DirstateItem is a bit more settled
682 # for dirstate/dirstatemap/DirstateItem is a bit more settled
684 self._rustmap.removefile(f, in_merge=True)
683 self._rustmap.removefile(f, in_merge=True)
685
684
686 def removefile(self, *args, **kwargs):
685 def removefile(self, *args, **kwargs):
687 return self._rustmap.removefile(*args, **kwargs)
686 return self._rustmap.removefile(*args, **kwargs)
688
687
689 def dropfile(self, *args, **kwargs):
688 def dropfile(self, *args, **kwargs):
690 return self._rustmap.dropfile(*args, **kwargs)
689 return self._rustmap.dropfile(*args, **kwargs)
691
690
692 def clearambiguoustimes(self, *args, **kwargs):
691 def clearambiguoustimes(self, *args, **kwargs):
693 return self._rustmap.clearambiguoustimes(*args, **kwargs)
692 return self._rustmap.clearambiguoustimes(*args, **kwargs)
694
693
695 def nonnormalentries(self):
694 def nonnormalentries(self):
696 return self._rustmap.nonnormalentries()
695 return self._rustmap.nonnormalentries()
697
696
698 def get(self, *args, **kwargs):
697 def get(self, *args, **kwargs):
699 return self._rustmap.get(*args, **kwargs)
698 return self._rustmap.get(*args, **kwargs)
700
699
701 @property
700 @property
702 def copymap(self):
701 def copymap(self):
703 return self._rustmap.copymap()
702 return self._rustmap.copymap()
704
703
705 def directories(self):
704 def directories(self):
706 return self._rustmap.directories()
705 return self._rustmap.directories()
707
706
708 def debug_iter(self):
707 def debug_iter(self):
709 return self._rustmap.debug_iter()
708 return self._rustmap.debug_iter()
710
709
711 def preload(self):
710 def preload(self):
712 self._rustmap
711 self._rustmap
713
712
714 def clear(self):
713 def clear(self):
715 self._rustmap.clear()
714 self._rustmap.clear()
716 self.setparents(
715 self.setparents(
717 self._nodeconstants.nullid, self._nodeconstants.nullid
716 self._nodeconstants.nullid, self._nodeconstants.nullid
718 )
717 )
719 util.clearcachedproperty(self, b"_dirs")
718 util.clearcachedproperty(self, b"_dirs")
720 util.clearcachedproperty(self, b"_alldirs")
719 util.clearcachedproperty(self, b"_alldirs")
721 util.clearcachedproperty(self, b"dirfoldmap")
720 util.clearcachedproperty(self, b"dirfoldmap")
722
721
723 def items(self):
722 def items(self):
724 return self._rustmap.items()
723 return self._rustmap.items()
725
724
726 def keys(self):
725 def keys(self):
727 return iter(self._rustmap)
726 return iter(self._rustmap)
728
727
729 def __contains__(self, key):
728 def __contains__(self, key):
730 return key in self._rustmap
729 return key in self._rustmap
731
730
732 def __getitem__(self, item):
731 def __getitem__(self, item):
733 return self._rustmap[item]
732 return self._rustmap[item]
734
733
735 def __len__(self):
734 def __len__(self):
736 return len(self._rustmap)
735 return len(self._rustmap)
737
736
738 def __iter__(self):
737 def __iter__(self):
739 return iter(self._rustmap)
738 return iter(self._rustmap)
740
739
741 # forward for python2,3 compat
740 # forward for python2,3 compat
742 iteritems = items
741 iteritems = items
743
742
744 def _opendirstatefile(self):
743 def _opendirstatefile(self):
745 fp, mode = txnutil.trypending(
744 fp, mode = txnutil.trypending(
746 self._root, self._opener, self._filename
745 self._root, self._opener, self._filename
747 )
746 )
748 if self._pendingmode is not None and self._pendingmode != mode:
747 if self._pendingmode is not None and self._pendingmode != mode:
749 fp.close()
748 fp.close()
750 raise error.Abort(
749 raise error.Abort(
751 _(b'working directory state may be changed parallelly')
750 _(b'working directory state may be changed parallelly')
752 )
751 )
753 self._pendingmode = mode
752 self._pendingmode = mode
754 return fp
753 return fp
755
754
756 def _readdirstatefile(self, size=-1):
755 def _readdirstatefile(self, size=-1):
757 try:
756 try:
758 with self._opendirstatefile() as fp:
757 with self._opendirstatefile() as fp:
759 return fp.read(size)
758 return fp.read(size)
760 except IOError as err:
759 except IOError as err:
761 if err.errno != errno.ENOENT:
760 if err.errno != errno.ENOENT:
762 raise
761 raise
763 # File doesn't exist, so the current state is empty
762 # File doesn't exist, so the current state is empty
764 return b''
763 return b''
765
764
766 def setparents(self, p1, p2):
765 def setparents(self, p1, p2):
767 self._parents = (p1, p2)
766 self._parents = (p1, p2)
768 self._dirtyparents = True
767 self._dirtyparents = True
769
768
770 def parents(self):
769 def parents(self):
771 if not self._parents:
770 if not self._parents:
772 if self._use_dirstate_v2:
771 if self._use_dirstate_v2:
773 self._parents = self.docket.parents
772 self._parents = self.docket.parents
774 else:
773 else:
775 read_len = self._nodelen * 2
774 read_len = self._nodelen * 2
776 st = self._readdirstatefile(read_len)
775 st = self._readdirstatefile(read_len)
777 l = len(st)
776 l = len(st)
778 if l == read_len:
777 if l == read_len:
779 self._parents = (
778 self._parents = (
780 st[: self._nodelen],
779 st[: self._nodelen],
781 st[self._nodelen : 2 * self._nodelen],
780 st[self._nodelen : 2 * self._nodelen],
782 )
781 )
783 elif l == 0:
782 elif l == 0:
784 self._parents = (
783 self._parents = (
785 self._nodeconstants.nullid,
784 self._nodeconstants.nullid,
786 self._nodeconstants.nullid,
785 self._nodeconstants.nullid,
787 )
786 )
788 else:
787 else:
789 raise error.Abort(
788 raise error.Abort(
790 _(b'working directory state appears damaged!')
789 _(b'working directory state appears damaged!')
791 )
790 )
792
791
793 return self._parents
792 return self._parents
794
793
795 @property
794 @property
796 def docket(self):
795 def docket(self):
797 if not self._docket:
796 if not self._docket:
798 if not self._use_dirstate_v2:
797 if not self._use_dirstate_v2:
799 raise error.ProgrammingError(
798 raise error.ProgrammingError(
800 b'dirstate only has a docket in v2 format'
799 b'dirstate only has a docket in v2 format'
801 )
800 )
802 self._docket = docketmod.DirstateDocket.parse(
801 self._docket = docketmod.DirstateDocket.parse(
803 self._readdirstatefile(), self._nodeconstants
802 self._readdirstatefile(), self._nodeconstants
804 )
803 )
805 return self._docket
804 return self._docket
806
805
807 @propertycache
806 @propertycache
808 def _rustmap(self):
807 def _rustmap(self):
809 """
808 """
810 Fills the Dirstatemap when called.
809 Fills the Dirstatemap when called.
811 """
810 """
812 # ignore HG_PENDING because identity is used only for writing
811 # ignore HG_PENDING because identity is used only for writing
813 self.identity = util.filestat.frompath(
812 self.identity = util.filestat.frompath(
814 self._opener.join(self._filename)
813 self._opener.join(self._filename)
815 )
814 )
816
815
817 if self._use_dirstate_v2:
816 if self._use_dirstate_v2:
818 if self.docket.uuid:
817 if self.docket.uuid:
819 # TODO: use mmap when possible
818 # TODO: use mmap when possible
820 data = self._opener.read(self.docket.data_filename())
819 data = self._opener.read(self.docket.data_filename())
821 else:
820 else:
822 data = b''
821 data = b''
823 self._rustmap = rustmod.DirstateMap.new_v2(
822 self._rustmap = rustmod.DirstateMap.new_v2(
824 data, self.docket.data_size, self.docket.tree_metadata
823 data, self.docket.data_size, self.docket.tree_metadata
825 )
824 )
826 parents = self.docket.parents
825 parents = self.docket.parents
827 else:
826 else:
828 self._rustmap, parents = rustmod.DirstateMap.new_v1(
827 self._rustmap, parents = rustmod.DirstateMap.new_v1(
829 self._use_dirstate_tree, self._readdirstatefile()
828 self._use_dirstate_tree, self._readdirstatefile()
830 )
829 )
831
830
832 if parents and not self._dirtyparents:
831 if parents and not self._dirtyparents:
833 self.setparents(*parents)
832 self.setparents(*parents)
834
833
835 self.__contains__ = self._rustmap.__contains__
834 self.__contains__ = self._rustmap.__contains__
836 self.__getitem__ = self._rustmap.__getitem__
835 self.__getitem__ = self._rustmap.__getitem__
837 self.get = self._rustmap.get
836 self.get = self._rustmap.get
838 return self._rustmap
837 return self._rustmap
839
838
840 def write(self, tr, st, now):
839 def write(self, tr, st, now):
841 if not self._use_dirstate_v2:
840 if not self._use_dirstate_v2:
842 p1, p2 = self.parents()
841 p1, p2 = self.parents()
843 packed = self._rustmap.write_v1(p1, p2, now)
842 packed = self._rustmap.write_v1(p1, p2, now)
844 st.write(packed)
843 st.write(packed)
845 st.close()
844 st.close()
846 self._dirtyparents = False
845 self._dirtyparents = False
847 return
846 return
848
847
849 # We can only append to an existing data file if there is one
848 # We can only append to an existing data file if there is one
850 can_append = self.docket.uuid is not None
849 can_append = self.docket.uuid is not None
851 packed, meta, append = self._rustmap.write_v2(now, can_append)
850 packed, meta, append = self._rustmap.write_v2(now, can_append)
852 if append:
851 if append:
853 docket = self.docket
852 docket = self.docket
854 data_filename = docket.data_filename()
853 data_filename = docket.data_filename()
855 if tr:
854 if tr:
856 tr.add(data_filename, docket.data_size)
855 tr.add(data_filename, docket.data_size)
857 with self._opener(data_filename, b'r+b') as fp:
856 with self._opener(data_filename, b'r+b') as fp:
858 fp.seek(docket.data_size)
857 fp.seek(docket.data_size)
859 assert fp.tell() == docket.data_size
858 assert fp.tell() == docket.data_size
860 written = fp.write(packed)
859 written = fp.write(packed)
861 if written is not None: # py2 may return None
860 if written is not None: # py2 may return None
862 assert written == len(packed), (written, len(packed))
861 assert written == len(packed), (written, len(packed))
863 docket.data_size += len(packed)
862 docket.data_size += len(packed)
864 docket.parents = self.parents()
863 docket.parents = self.parents()
865 docket.tree_metadata = meta
864 docket.tree_metadata = meta
866 st.write(docket.serialize())
865 st.write(docket.serialize())
867 st.close()
866 st.close()
868 else:
867 else:
869 old_docket = self.docket
868 old_docket = self.docket
870 new_docket = docketmod.DirstateDocket.with_new_uuid(
869 new_docket = docketmod.DirstateDocket.with_new_uuid(
871 self.parents(), len(packed), meta
870 self.parents(), len(packed), meta
872 )
871 )
873 data_filename = new_docket.data_filename()
872 data_filename = new_docket.data_filename()
874 if tr:
873 if tr:
875 tr.add(data_filename, 0)
874 tr.add(data_filename, 0)
876 self._opener.write(data_filename, packed)
875 self._opener.write(data_filename, packed)
877 # Write the new docket after the new data file has been
876 # Write the new docket after the new data file has been
878 # written. Because `st` was opened with `atomictemp=True`,
877 # written. Because `st` was opened with `atomictemp=True`,
879 # the actual `.hg/dirstate` file is only affected on close.
878 # the actual `.hg/dirstate` file is only affected on close.
880 st.write(new_docket.serialize())
879 st.write(new_docket.serialize())
881 st.close()
880 st.close()
882 # Remove the old data file after the new docket pointing to
881 # Remove the old data file after the new docket pointing to
883 # the new data file was written.
882 # the new data file was written.
884 if old_docket.uuid:
883 if old_docket.uuid:
885 data_filename = old_docket.data_filename()
884 data_filename = old_docket.data_filename()
886 unlink = lambda _tr=None: self._opener.unlink(data_filename)
885 unlink = lambda _tr=None: self._opener.unlink(data_filename)
887 if tr:
886 if tr:
888 category = b"dirstate-v2-clean-" + old_docket.uuid
887 category = b"dirstate-v2-clean-" + old_docket.uuid
889 tr.addpostclose(category, unlink)
888 tr.addpostclose(category, unlink)
890 else:
889 else:
891 unlink()
890 unlink()
892 self._docket = new_docket
891 self._docket = new_docket
893 # Reload from the newly-written file
892 # Reload from the newly-written file
894 util.clearcachedproperty(self, b"_rustmap")
893 util.clearcachedproperty(self, b"_rustmap")
895 self._dirtyparents = False
894 self._dirtyparents = False
896
895
897 @propertycache
896 @propertycache
898 def filefoldmap(self):
897 def filefoldmap(self):
899 """Returns a dictionary mapping normalized case paths to their
898 """Returns a dictionary mapping normalized case paths to their
900 non-normalized versions.
899 non-normalized versions.
901 """
900 """
902 return self._rustmap.filefoldmapasdict()
901 return self._rustmap.filefoldmapasdict()
903
902
904 def hastrackeddir(self, d):
903 def hastrackeddir(self, d):
905 return self._rustmap.hastrackeddir(d)
904 return self._rustmap.hastrackeddir(d)
906
905
907 def hasdir(self, d):
906 def hasdir(self, d):
908 return self._rustmap.hasdir(d)
907 return self._rustmap.hasdir(d)
909
908
910 @propertycache
909 @propertycache
911 def identity(self):
910 def identity(self):
912 self._rustmap
911 self._rustmap
913 return self.identity
912 return self.identity
914
913
915 @property
914 @property
916 def nonnormalset(self):
915 def nonnormalset(self):
917 nonnorm = self._rustmap.non_normal_entries()
916 nonnorm = self._rustmap.non_normal_entries()
918 return nonnorm
917 return nonnorm
919
918
920 @propertycache
919 @propertycache
921 def otherparentset(self):
920 def otherparentset(self):
922 otherparents = self._rustmap.other_parent_entries()
921 otherparents = self._rustmap.other_parent_entries()
923 return otherparents
922 return otherparents
924
923
925 def non_normal_or_other_parent_paths(self):
924 def non_normal_or_other_parent_paths(self):
926 return self._rustmap.non_normal_or_other_parent_paths()
925 return self._rustmap.non_normal_or_other_parent_paths()
927
926
928 @propertycache
927 @propertycache
929 def dirfoldmap(self):
928 def dirfoldmap(self):
930 f = {}
929 f = {}
931 normcase = util.normcase
930 normcase = util.normcase
932 for name in self._rustmap.tracked_dirs():
931 for name in self._rustmap.tracked_dirs():
933 f[normcase(name)] = name
932 f[normcase(name)] = name
934 return f
933 return f
935
934
936 def set_possibly_dirty(self, filename):
935 def set_possibly_dirty(self, filename):
937 """record that the current state of the file on disk is unknown"""
936 """record that the current state of the file on disk is unknown"""
938 entry = self[filename]
937 entry = self[filename]
939 entry.set_possibly_dirty()
938 entry.set_possibly_dirty()
940 self._rustmap.set_v1(filename, entry)
939 self._rustmap.set_v1(filename, entry)
941
940
942 def __setitem__(self, key, value):
941 def __setitem__(self, key, value):
943 assert isinstance(value, DirstateItem)
942 assert isinstance(value, DirstateItem)
944 self._rustmap.set_v1(key, value)
943 self._rustmap.set_v1(key, value)
General Comments 0
You need to be logged in to leave comments. Login now