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