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