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