##// END OF EJS Templates
dirstatemap: move `_refresh_entry` out of the common methods...
Raphaël Gomès -
r50008:4c562108 default
parent child Browse files
Show More
@@ -1,730 +1,728 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
6
7 import errno
7 import errno
8
8
9 from .i18n import _
9 from .i18n import _
10
10
11 from . import (
11 from . import (
12 error,
12 error,
13 pathutil,
13 pathutil,
14 policy,
14 policy,
15 txnutil,
15 txnutil,
16 util,
16 util,
17 )
17 )
18
18
19 from .dirstateutils import (
19 from .dirstateutils import (
20 docket as docketmod,
20 docket as docketmod,
21 v2,
21 v2,
22 )
22 )
23
23
24 parsers = policy.importmod('parsers')
24 parsers = policy.importmod('parsers')
25 rustmod = policy.importrust('dirstate')
25 rustmod = policy.importrust('dirstate')
26
26
27 propertycache = util.propertycache
27 propertycache = util.propertycache
28
28
29 if rustmod is None:
29 if rustmod is None:
30 DirstateItem = parsers.DirstateItem
30 DirstateItem = parsers.DirstateItem
31 else:
31 else:
32 DirstateItem = rustmod.DirstateItem
32 DirstateItem = rustmod.DirstateItem
33
33
34 rangemask = 0x7FFFFFFF
34 rangemask = 0x7FFFFFFF
35
35
36
36
37 class _dirstatemapcommon:
37 class _dirstatemapcommon:
38 """
38 """
39 Methods that are identical for both implementations of the dirstatemap
39 Methods that are identical for both implementations of the dirstatemap
40 class, with and without Rust extensions enabled.
40 class, with and without Rust extensions enabled.
41 """
41 """
42
42
43 # please pytype
43 # please pytype
44
44
45 _map = None
45 _map = None
46 copymap = None
46 copymap = None
47
47
48 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
48 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
49 self._use_dirstate_v2 = use_dirstate_v2
49 self._use_dirstate_v2 = use_dirstate_v2
50 self._nodeconstants = nodeconstants
50 self._nodeconstants = nodeconstants
51 self._ui = ui
51 self._ui = ui
52 self._opener = opener
52 self._opener = opener
53 self._root = root
53 self._root = root
54 self._filename = b'dirstate'
54 self._filename = b'dirstate'
55 self._nodelen = 20 # Also update Rust code when changing this!
55 self._nodelen = 20 # Also update Rust code when changing this!
56 self._parents = None
56 self._parents = None
57 self._dirtyparents = False
57 self._dirtyparents = False
58 self._docket = None
58 self._docket = None
59
59
60 # for consistent view between _pl() and _read() invocations
60 # for consistent view between _pl() and _read() invocations
61 self._pendingmode = None
61 self._pendingmode = None
62
62
63 def preload(self):
63 def preload(self):
64 """Loads the underlying data, if it's not already loaded"""
64 """Loads the underlying data, if it's not already loaded"""
65 self._map
65 self._map
66
66
67 def get(self, key, default=None):
67 def get(self, key, default=None):
68 return self._map.get(key, default)
68 return self._map.get(key, default)
69
69
70 def __len__(self):
70 def __len__(self):
71 return len(self._map)
71 return len(self._map)
72
72
73 def __iter__(self):
73 def __iter__(self):
74 return iter(self._map)
74 return iter(self._map)
75
75
76 def __contains__(self, key):
76 def __contains__(self, key):
77 return key in self._map
77 return key in self._map
78
78
79 def __getitem__(self, item):
79 def __getitem__(self, item):
80 return self._map[item]
80 return self._map[item]
81
81
82 ### sub-class utility method
82 ### sub-class utility method
83 #
83 #
84 # Use to allow for generic implementation of some method while still coping
84 # Use to allow for generic implementation of some method while still coping
85 # with minor difference between implementation.
85 # with minor difference between implementation.
86
86
87 def _dirs_incr(self, filename, old_entry=None):
87 def _dirs_incr(self, filename, old_entry=None):
88 """increment the dirstate counter if applicable
88 """increment the dirstate counter if applicable
89
89
90 This might be a no-op for some subclasses who deal with directory
90 This might be a no-op for some subclasses who deal with directory
91 tracking in a different way.
91 tracking in a different way.
92 """
92 """
93
93
94 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
94 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
95 """decrement the dirstate counter if applicable
95 """decrement the dirstate counter if applicable
96
96
97 This might be a no-op for some subclasses who deal with directory
97 This might be a no-op for some subclasses who deal with directory
98 tracking in a different way.
98 tracking in a different way.
99 """
99 """
100
100
101 def _refresh_entry(self, f, entry):
102 """record updated state of an entry"""
103
104 ### disk interaction
101 ### disk interaction
105
102
106 def _opendirstatefile(self):
103 def _opendirstatefile(self):
107 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
104 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
108 if self._pendingmode is not None and self._pendingmode != mode:
105 if self._pendingmode is not None and self._pendingmode != mode:
109 fp.close()
106 fp.close()
110 raise error.Abort(
107 raise error.Abort(
111 _(b'working directory state may be changed parallelly')
108 _(b'working directory state may be changed parallelly')
112 )
109 )
113 self._pendingmode = mode
110 self._pendingmode = mode
114 return fp
111 return fp
115
112
116 def _readdirstatefile(self, size=-1):
113 def _readdirstatefile(self, size=-1):
117 try:
114 try:
118 with self._opendirstatefile() as fp:
115 with self._opendirstatefile() as fp:
119 return fp.read(size)
116 return fp.read(size)
120 except IOError as err:
117 except IOError as err:
121 if err.errno != errno.ENOENT:
118 if err.errno != errno.ENOENT:
122 raise
119 raise
123 # File doesn't exist, so the current state is empty
120 # File doesn't exist, so the current state is empty
124 return b''
121 return b''
125
122
126 @property
123 @property
127 def docket(self):
124 def docket(self):
128 if not self._docket:
125 if not self._docket:
129 if not self._use_dirstate_v2:
126 if not self._use_dirstate_v2:
130 raise error.ProgrammingError(
127 raise error.ProgrammingError(
131 b'dirstate only has a docket in v2 format'
128 b'dirstate only has a docket in v2 format'
132 )
129 )
133 self._docket = docketmod.DirstateDocket.parse(
130 self._docket = docketmod.DirstateDocket.parse(
134 self._readdirstatefile(), self._nodeconstants
131 self._readdirstatefile(), self._nodeconstants
135 )
132 )
136 return self._docket
133 return self._docket
137
134
138 def write_v2_no_append(self, tr, st, meta, packed):
135 def write_v2_no_append(self, tr, st, meta, packed):
139 old_docket = self.docket
136 old_docket = self.docket
140 new_docket = docketmod.DirstateDocket.with_new_uuid(
137 new_docket = docketmod.DirstateDocket.with_new_uuid(
141 self.parents(), len(packed), meta
138 self.parents(), len(packed), meta
142 )
139 )
143 data_filename = new_docket.data_filename()
140 data_filename = new_docket.data_filename()
144 if tr:
141 if tr:
145 tr.add(data_filename, 0)
142 tr.add(data_filename, 0)
146 self._opener.write(data_filename, packed)
143 self._opener.write(data_filename, packed)
147 # Write the new docket after the new data file has been
144 # Write the new docket after the new data file has been
148 # written. Because `st` was opened with `atomictemp=True`,
145 # written. Because `st` was opened with `atomictemp=True`,
149 # the actual `.hg/dirstate` file is only affected on close.
146 # the actual `.hg/dirstate` file is only affected on close.
150 st.write(new_docket.serialize())
147 st.write(new_docket.serialize())
151 st.close()
148 st.close()
152 # Remove the old data file after the new docket pointing to
149 # Remove the old data file after the new docket pointing to
153 # the new data file was written.
150 # the new data file was written.
154 if old_docket.uuid:
151 if old_docket.uuid:
155 data_filename = old_docket.data_filename()
152 data_filename = old_docket.data_filename()
156 unlink = lambda _tr=None: self._opener.unlink(data_filename)
153 unlink = lambda _tr=None: self._opener.unlink(data_filename)
157 if tr:
154 if tr:
158 category = b"dirstate-v2-clean-" + old_docket.uuid
155 category = b"dirstate-v2-clean-" + old_docket.uuid
159 tr.addpostclose(category, unlink)
156 tr.addpostclose(category, unlink)
160 else:
157 else:
161 unlink()
158 unlink()
162 self._docket = new_docket
159 self._docket = new_docket
163
160
164 ### reading/setting parents
161 ### reading/setting parents
165
162
166 def parents(self):
163 def parents(self):
167 if not self._parents:
164 if not self._parents:
168 if self._use_dirstate_v2:
165 if self._use_dirstate_v2:
169 self._parents = self.docket.parents
166 self._parents = self.docket.parents
170 else:
167 else:
171 read_len = self._nodelen * 2
168 read_len = self._nodelen * 2
172 st = self._readdirstatefile(read_len)
169 st = self._readdirstatefile(read_len)
173 l = len(st)
170 l = len(st)
174 if l == read_len:
171 if l == read_len:
175 self._parents = (
172 self._parents = (
176 st[: self._nodelen],
173 st[: self._nodelen],
177 st[self._nodelen : 2 * self._nodelen],
174 st[self._nodelen : 2 * self._nodelen],
178 )
175 )
179 elif l == 0:
176 elif l == 0:
180 self._parents = (
177 self._parents = (
181 self._nodeconstants.nullid,
178 self._nodeconstants.nullid,
182 self._nodeconstants.nullid,
179 self._nodeconstants.nullid,
183 )
180 )
184 else:
181 else:
185 raise error.Abort(
182 raise error.Abort(
186 _(b'working directory state appears damaged!')
183 _(b'working directory state appears damaged!')
187 )
184 )
188
185
189 return self._parents
186 return self._parents
190
187
191
188
192 class dirstatemap(_dirstatemapcommon):
189 class dirstatemap(_dirstatemapcommon):
193 """Map encapsulating the dirstate's contents.
190 """Map encapsulating the dirstate's contents.
194
191
195 The dirstate contains the following state:
192 The dirstate contains the following state:
196
193
197 - `identity` is the identity of the dirstate file, which can be used to
194 - `identity` is the identity of the dirstate file, which can be used to
198 detect when changes have occurred to the dirstate file.
195 detect when changes have occurred to the dirstate file.
199
196
200 - `parents` is a pair containing the parents of the working copy. The
197 - `parents` is a pair containing the parents of the working copy. The
201 parents are updated by calling `setparents`.
198 parents are updated by calling `setparents`.
202
199
203 - the state map maps filenames to tuples of (state, mode, size, mtime),
200 - the state map maps filenames to tuples of (state, mode, size, mtime),
204 where state is a single character representing 'normal', 'added',
201 where state is a single character representing 'normal', 'added',
205 'removed', or 'merged'. It is read by treating the dirstate as a
202 'removed', or 'merged'. It is read by treating the dirstate as a
206 dict. File state is updated by calling various methods (see each
203 dict. File state is updated by calling various methods (see each
207 documentation for details):
204 documentation for details):
208
205
209 - `reset_state`,
206 - `reset_state`,
210 - `set_tracked`
207 - `set_tracked`
211 - `set_untracked`
208 - `set_untracked`
212 - `set_clean`
209 - `set_clean`
213 - `set_possibly_dirty`
210 - `set_possibly_dirty`
214
211
215 - `copymap` maps destination filenames to their source filename.
212 - `copymap` maps destination filenames to their source filename.
216
213
217 The dirstate also provides the following views onto the state:
214 The dirstate also provides the following views onto the state:
218
215
219 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
216 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
220 form that they appear as in the dirstate.
217 form that they appear as in the dirstate.
221
218
222 - `dirfoldmap` is a dict mapping normalized directory names to the
219 - `dirfoldmap` is a dict mapping normalized directory names to the
223 denormalized form that they appear as in the dirstate.
220 denormalized form that they appear as in the dirstate.
224 """
221 """
225
222
226 ### Core data storage and access
223 ### Core data storage and access
227
224
228 @propertycache
225 @propertycache
229 def _map(self):
226 def _map(self):
230 self._map = {}
227 self._map = {}
231 self.read()
228 self.read()
232 return self._map
229 return self._map
233
230
234 @propertycache
231 @propertycache
235 def copymap(self):
232 def copymap(self):
236 self.copymap = {}
233 self.copymap = {}
237 self._map
234 self._map
238 return self.copymap
235 return self.copymap
239
236
240 def clear(self):
237 def clear(self):
241 self._map.clear()
238 self._map.clear()
242 self.copymap.clear()
239 self.copymap.clear()
243 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
240 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
244 util.clearcachedproperty(self, b"_dirs")
241 util.clearcachedproperty(self, b"_dirs")
245 util.clearcachedproperty(self, b"_alldirs")
242 util.clearcachedproperty(self, b"_alldirs")
246 util.clearcachedproperty(self, b"filefoldmap")
243 util.clearcachedproperty(self, b"filefoldmap")
247 util.clearcachedproperty(self, b"dirfoldmap")
244 util.clearcachedproperty(self, b"dirfoldmap")
248
245
249 def items(self):
246 def items(self):
250 return self._map.items()
247 return self._map.items()
251
248
252 # forward for python2,3 compat
249 # forward for python2,3 compat
253 iteritems = items
250 iteritems = items
254
251
255 def debug_iter(self, all):
252 def debug_iter(self, all):
256 """
253 """
257 Return an iterator of (filename, state, mode, size, mtime) tuples
254 Return an iterator of (filename, state, mode, size, mtime) tuples
258
255
259 `all` is unused when Rust is not enabled
256 `all` is unused when Rust is not enabled
260 """
257 """
261 for (filename, item) in self.items():
258 for (filename, item) in self.items():
262 yield (filename, item.state, item.mode, item.size, item.mtime)
259 yield (filename, item.state, item.mode, item.size, item.mtime)
263
260
264 def keys(self):
261 def keys(self):
265 return self._map.keys()
262 return self._map.keys()
266
263
267 ### reading/setting parents
264 ### reading/setting parents
268
265
269 def setparents(self, p1, p2, fold_p2=False):
266 def setparents(self, p1, p2, fold_p2=False):
270 self._parents = (p1, p2)
267 self._parents = (p1, p2)
271 self._dirtyparents = True
268 self._dirtyparents = True
272 copies = {}
269 copies = {}
273 if fold_p2:
270 if fold_p2:
274 for f, s in self._map.items():
271 for f, s in self._map.items():
275 # Discard "merged" markers when moving away from a merge state
272 # Discard "merged" markers when moving away from a merge state
276 if s.p2_info:
273 if s.p2_info:
277 source = self.copymap.pop(f, None)
274 source = self.copymap.pop(f, None)
278 if source:
275 if source:
279 copies[f] = source
276 copies[f] = source
280 s.drop_merge_data()
277 s.drop_merge_data()
281 return copies
278 return copies
282
279
283 ### disk interaction
280 ### disk interaction
284
281
285 def read(self):
282 def read(self):
286 # ignore HG_PENDING because identity is used only for writing
283 # ignore HG_PENDING because identity is used only for writing
287 self.identity = util.filestat.frompath(
284 self.identity = util.filestat.frompath(
288 self._opener.join(self._filename)
285 self._opener.join(self._filename)
289 )
286 )
290
287
291 if self._use_dirstate_v2:
288 if self._use_dirstate_v2:
292 if not self.docket.uuid:
289 if not self.docket.uuid:
293 return
290 return
294 st = self._opener.read(self.docket.data_filename())
291 st = self._opener.read(self.docket.data_filename())
295 else:
292 else:
296 st = self._readdirstatefile()
293 st = self._readdirstatefile()
297
294
298 if not st:
295 if not st:
299 return
296 return
300
297
301 # TODO: adjust this estimate for dirstate-v2
298 # TODO: adjust this estimate for dirstate-v2
302 if util.safehasattr(parsers, b'dict_new_presized'):
299 if util.safehasattr(parsers, b'dict_new_presized'):
303 # Make an estimate of the number of files in the dirstate based on
300 # Make an estimate of the number of files in the dirstate based on
304 # its size. This trades wasting some memory for avoiding costly
301 # its size. This trades wasting some memory for avoiding costly
305 # resizes. Each entry have a prefix of 17 bytes followed by one or
302 # resizes. Each entry have a prefix of 17 bytes followed by one or
306 # two path names. Studies on various large-scale real-world repositories
303 # two path names. Studies on various large-scale real-world repositories
307 # found 54 bytes a reasonable upper limit for the average path names.
304 # found 54 bytes a reasonable upper limit for the average path names.
308 # Copy entries are ignored for the sake of this estimate.
305 # Copy entries are ignored for the sake of this estimate.
309 self._map = parsers.dict_new_presized(len(st) // 71)
306 self._map = parsers.dict_new_presized(len(st) // 71)
310
307
311 # Python's garbage collector triggers a GC each time a certain number
308 # Python's garbage collector triggers a GC each time a certain number
312 # of container objects (the number being defined by
309 # of container objects (the number being defined by
313 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
310 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
314 # for each file in the dirstate. The C version then immediately marks
311 # for each file in the dirstate. The C version then immediately marks
315 # them as not to be tracked by the collector. However, this has no
312 # them as not to be tracked by the collector. However, this has no
316 # effect on when GCs are triggered, only on what objects the GC looks
313 # effect on when GCs are triggered, only on what objects the GC looks
317 # into. This means that O(number of files) GCs are unavoidable.
314 # into. This means that O(number of files) GCs are unavoidable.
318 # Depending on when in the process's lifetime the dirstate is parsed,
315 # Depending on when in the process's lifetime the dirstate is parsed,
319 # this can get very expensive. As a workaround, disable GC while
316 # this can get very expensive. As a workaround, disable GC while
320 # parsing the dirstate.
317 # parsing the dirstate.
321 #
318 #
322 # (we cannot decorate the function directly since it is in a C module)
319 # (we cannot decorate the function directly since it is in a C module)
323 if self._use_dirstate_v2:
320 if self._use_dirstate_v2:
324 p = self.docket.parents
321 p = self.docket.parents
325 meta = self.docket.tree_metadata
322 meta = self.docket.tree_metadata
326 parse_dirstate = util.nogc(v2.parse_dirstate)
323 parse_dirstate = util.nogc(v2.parse_dirstate)
327 parse_dirstate(self._map, self.copymap, st, meta)
324 parse_dirstate(self._map, self.copymap, st, meta)
328 else:
325 else:
329 parse_dirstate = util.nogc(parsers.parse_dirstate)
326 parse_dirstate = util.nogc(parsers.parse_dirstate)
330 p = parse_dirstate(self._map, self.copymap, st)
327 p = parse_dirstate(self._map, self.copymap, st)
331 if not self._dirtyparents:
328 if not self._dirtyparents:
332 self.setparents(*p)
329 self.setparents(*p)
333
330
334 # Avoid excess attribute lookups by fast pathing certain checks
331 # Avoid excess attribute lookups by fast pathing certain checks
335 self.__contains__ = self._map.__contains__
332 self.__contains__ = self._map.__contains__
336 self.__getitem__ = self._map.__getitem__
333 self.__getitem__ = self._map.__getitem__
337 self.get = self._map.get
334 self.get = self._map.get
338
335
339 def write(self, tr, st):
336 def write(self, tr, st):
340 if self._use_dirstate_v2:
337 if self._use_dirstate_v2:
341 packed, meta = v2.pack_dirstate(self._map, self.copymap)
338 packed, meta = v2.pack_dirstate(self._map, self.copymap)
342 self.write_v2_no_append(tr, st, meta, packed)
339 self.write_v2_no_append(tr, st, meta, packed)
343 else:
340 else:
344 packed = parsers.pack_dirstate(
341 packed = parsers.pack_dirstate(
345 self._map, self.copymap, self.parents()
342 self._map, self.copymap, self.parents()
346 )
343 )
347 st.write(packed)
344 st.write(packed)
348 st.close()
345 st.close()
349 self._dirtyparents = False
346 self._dirtyparents = False
350
347
351 @propertycache
348 @propertycache
352 def identity(self):
349 def identity(self):
353 self._map
350 self._map
354 return self.identity
351 return self.identity
355
352
356 ### code related to maintaining and accessing "extra" property
353 ### code related to maintaining and accessing "extra" property
357 # (e.g. "has_dir")
354 # (e.g. "has_dir")
358
355
359 def _dirs_incr(self, filename, old_entry=None):
356 def _dirs_incr(self, filename, old_entry=None):
360 """incremente the dirstate counter if applicable"""
357 """incremente the dirstate counter if applicable"""
361 if (
358 if (
362 old_entry is None or old_entry.removed
359 old_entry is None or old_entry.removed
363 ) and "_dirs" in self.__dict__:
360 ) and "_dirs" in self.__dict__:
364 self._dirs.addpath(filename)
361 self._dirs.addpath(filename)
365 if old_entry is None and "_alldirs" in self.__dict__:
362 if old_entry is None and "_alldirs" in self.__dict__:
366 self._alldirs.addpath(filename)
363 self._alldirs.addpath(filename)
367
364
368 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
365 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
369 """decremente the dirstate counter if applicable"""
366 """decremente the dirstate counter if applicable"""
370 if old_entry is not None:
367 if old_entry is not None:
371 if "_dirs" in self.__dict__ and not old_entry.removed:
368 if "_dirs" in self.__dict__ and not old_entry.removed:
372 self._dirs.delpath(filename)
369 self._dirs.delpath(filename)
373 if "_alldirs" in self.__dict__ and not remove_variant:
370 if "_alldirs" in self.__dict__ and not remove_variant:
374 self._alldirs.delpath(filename)
371 self._alldirs.delpath(filename)
375 elif remove_variant and "_alldirs" in self.__dict__:
372 elif remove_variant and "_alldirs" in self.__dict__:
376 self._alldirs.addpath(filename)
373 self._alldirs.addpath(filename)
377 if "filefoldmap" in self.__dict__:
374 if "filefoldmap" in self.__dict__:
378 normed = util.normcase(filename)
375 normed = util.normcase(filename)
379 self.filefoldmap.pop(normed, None)
376 self.filefoldmap.pop(normed, None)
380
377
381 @propertycache
378 @propertycache
382 def filefoldmap(self):
379 def filefoldmap(self):
383 """Returns a dictionary mapping normalized case paths to their
380 """Returns a dictionary mapping normalized case paths to their
384 non-normalized versions.
381 non-normalized versions.
385 """
382 """
386 try:
383 try:
387 makefilefoldmap = parsers.make_file_foldmap
384 makefilefoldmap = parsers.make_file_foldmap
388 except AttributeError:
385 except AttributeError:
389 pass
386 pass
390 else:
387 else:
391 return makefilefoldmap(
388 return makefilefoldmap(
392 self._map, util.normcasespec, util.normcasefallback
389 self._map, util.normcasespec, util.normcasefallback
393 )
390 )
394
391
395 f = {}
392 f = {}
396 normcase = util.normcase
393 normcase = util.normcase
397 for name, s in self._map.items():
394 for name, s in self._map.items():
398 if not s.removed:
395 if not s.removed:
399 f[normcase(name)] = name
396 f[normcase(name)] = name
400 f[b'.'] = b'.' # prevents useless util.fspath() invocation
397 f[b'.'] = b'.' # prevents useless util.fspath() invocation
401 return f
398 return f
402
399
403 @propertycache
400 @propertycache
404 def dirfoldmap(self):
401 def dirfoldmap(self):
405 f = {}
402 f = {}
406 normcase = util.normcase
403 normcase = util.normcase
407 for name in self._dirs:
404 for name in self._dirs:
408 f[normcase(name)] = name
405 f[normcase(name)] = name
409 return f
406 return f
410
407
411 def hastrackeddir(self, d):
408 def hastrackeddir(self, d):
412 """
409 """
413 Returns True if the dirstate contains a tracked (not removed) file
410 Returns True if the dirstate contains a tracked (not removed) file
414 in this directory.
411 in this directory.
415 """
412 """
416 return d in self._dirs
413 return d in self._dirs
417
414
418 def hasdir(self, d):
415 def hasdir(self, d):
419 """
416 """
420 Returns True if the dirstate contains a file (tracked or removed)
417 Returns True if the dirstate contains a file (tracked or removed)
421 in this directory.
418 in this directory.
422 """
419 """
423 return d in self._alldirs
420 return d in self._alldirs
424
421
425 @propertycache
422 @propertycache
426 def _dirs(self):
423 def _dirs(self):
427 return pathutil.dirs(self._map, only_tracked=True)
424 return pathutil.dirs(self._map, only_tracked=True)
428
425
429 @propertycache
426 @propertycache
430 def _alldirs(self):
427 def _alldirs(self):
431 return pathutil.dirs(self._map)
428 return pathutil.dirs(self._map)
432
429
433 ### code related to manipulation of entries and copy-sources
430 ### code related to manipulation of entries and copy-sources
434
431
435 def reset_state(
432 def reset_state(
436 self,
433 self,
437 filename,
434 filename,
438 wc_tracked=False,
435 wc_tracked=False,
439 p1_tracked=False,
436 p1_tracked=False,
440 p2_info=False,
437 p2_info=False,
441 has_meaningful_mtime=True,
438 has_meaningful_mtime=True,
442 parentfiledata=None,
439 parentfiledata=None,
443 ):
440 ):
444 """Set a entry to a given state, diregarding all previous state
441 """Set a entry to a given state, diregarding all previous state
445
442
446 This is to be used by the part of the dirstate API dedicated to
443 This is to be used by the part of the dirstate API dedicated to
447 adjusting the dirstate after a update/merge.
444 adjusting the dirstate after a update/merge.
448
445
449 note: calling this might result to no entry existing at all if the
446 note: calling this might result to no entry existing at all if the
450 dirstate map does not see any point at having one for this file
447 dirstate map does not see any point at having one for this file
451 anymore.
448 anymore.
452 """
449 """
453 # copy information are now outdated
450 # copy information are now outdated
454 # (maybe new information should be in directly passed to this function)
451 # (maybe new information should be in directly passed to this function)
455 self.copymap.pop(filename, None)
452 self.copymap.pop(filename, None)
456
453
457 if not (p1_tracked or p2_info or wc_tracked):
454 if not (p1_tracked or p2_info or wc_tracked):
458 old_entry = self._map.get(filename)
455 old_entry = self._map.get(filename)
459 self._drop_entry(filename)
456 self._drop_entry(filename)
460 self._dirs_decr(filename, old_entry=old_entry)
457 self._dirs_decr(filename, old_entry=old_entry)
461 return
458 return
462
459
463 old_entry = self._map.get(filename)
460 old_entry = self._map.get(filename)
464 self._dirs_incr(filename, old_entry)
461 self._dirs_incr(filename, old_entry)
465 entry = DirstateItem(
462 entry = DirstateItem(
466 wc_tracked=wc_tracked,
463 wc_tracked=wc_tracked,
467 p1_tracked=p1_tracked,
464 p1_tracked=p1_tracked,
468 p2_info=p2_info,
465 p2_info=p2_info,
469 has_meaningful_mtime=has_meaningful_mtime,
466 has_meaningful_mtime=has_meaningful_mtime,
470 parentfiledata=parentfiledata,
467 parentfiledata=parentfiledata,
471 )
468 )
472 self._map[filename] = entry
469 self._map[filename] = entry
473
470
474 def set_tracked(self, filename):
471 def set_tracked(self, filename):
475 new = False
472 new = False
476 entry = self.get(filename)
473 entry = self.get(filename)
477 if entry is None:
474 if entry is None:
478 self._dirs_incr(filename)
475 self._dirs_incr(filename)
479 entry = DirstateItem(
476 entry = DirstateItem(
480 wc_tracked=True,
477 wc_tracked=True,
481 )
478 )
482
479
483 self._map[filename] = entry
480 self._map[filename] = entry
484 new = True
481 new = True
485 elif not entry.tracked:
482 elif not entry.tracked:
486 self._dirs_incr(filename, entry)
483 self._dirs_incr(filename, entry)
487 entry.set_tracked()
484 entry.set_tracked()
488 self._refresh_entry(filename, entry)
485 self._refresh_entry(filename, entry)
489 new = True
486 new = True
490 else:
487 else:
491 # XXX This is probably overkill for more case, but we need this to
488 # XXX This is probably overkill for more case, but we need this to
492 # fully replace the `normallookup` call with `set_tracked` one.
489 # fully replace the `normallookup` call with `set_tracked` one.
493 # Consider smoothing this in the future.
490 # Consider smoothing this in the future.
494 entry.set_possibly_dirty()
491 entry.set_possibly_dirty()
495 self._refresh_entry(filename, entry)
492 self._refresh_entry(filename, entry)
496 return new
493 return new
497
494
498 def set_untracked(self, f):
495 def set_untracked(self, f):
499 """Mark a file as no longer tracked in the dirstate map"""
496 """Mark a file as no longer tracked in the dirstate map"""
500 entry = self.get(f)
497 entry = self.get(f)
501 if entry is None:
498 if entry is None:
502 return False
499 return False
503 else:
500 else:
504 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
501 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
505 if not entry.p2_info:
502 if not entry.p2_info:
506 self.copymap.pop(f, None)
503 self.copymap.pop(f, None)
507 entry.set_untracked()
504 entry.set_untracked()
508 self._refresh_entry(f, entry)
505 self._refresh_entry(f, entry)
509 return True
506 return True
510
507
511 def set_clean(self, filename, mode, size, mtime):
508 def set_clean(self, filename, mode, size, mtime):
512 """mark a file as back to a clean state"""
509 """mark a file as back to a clean state"""
513 entry = self[filename]
510 entry = self[filename]
514 size = size & rangemask
511 size = size & rangemask
515 entry.set_clean(mode, size, mtime)
512 entry.set_clean(mode, size, mtime)
516 self._refresh_entry(filename, entry)
513 self._refresh_entry(filename, entry)
517 self.copymap.pop(filename, None)
514 self.copymap.pop(filename, None)
518
515
519 def set_possibly_dirty(self, filename):
516 def set_possibly_dirty(self, filename):
520 """record that the current state of the file on disk is unknown"""
517 """record that the current state of the file on disk is unknown"""
521 entry = self[filename]
518 entry = self[filename]
522 entry.set_possibly_dirty()
519 entry.set_possibly_dirty()
523 self._refresh_entry(filename, entry)
520 self._refresh_entry(filename, entry)
524
521
525 def _refresh_entry(self, f, entry):
522 def _refresh_entry(self, f, entry):
523 """record updated state of an entry"""
526 if not entry.any_tracked:
524 if not entry.any_tracked:
527 self._map.pop(f, None)
525 self._map.pop(f, None)
528
526
529 def _drop_entry(self, f):
527 def _drop_entry(self, f):
530 """remove any entry for file f
528 """remove any entry for file f
531
529
532 This should also drop associated copy information
530 This should also drop associated copy information
533
531
534 The fact we actually need to drop it is the responsability of the caller"""
532 The fact we actually need to drop it is the responsability of the caller"""
535 self._map.pop(f, None)
533 self._map.pop(f, None)
536 self.copymap.pop(f, None)
534 self.copymap.pop(f, None)
537
535
538
536
539 if rustmod is not None:
537 if rustmod is not None:
540
538
541 class dirstatemap(_dirstatemapcommon):
539 class dirstatemap(_dirstatemapcommon):
542
540
543 ### Core data storage and access
541 ### Core data storage and access
544
542
545 @propertycache
543 @propertycache
546 def _map(self):
544 def _map(self):
547 """
545 """
548 Fills the Dirstatemap when called.
546 Fills the Dirstatemap when called.
549 """
547 """
550 # ignore HG_PENDING because identity is used only for writing
548 # ignore HG_PENDING because identity is used only for writing
551 self.identity = util.filestat.frompath(
549 self.identity = util.filestat.frompath(
552 self._opener.join(self._filename)
550 self._opener.join(self._filename)
553 )
551 )
554
552
555 if self._use_dirstate_v2:
553 if self._use_dirstate_v2:
556 if self.docket.uuid:
554 if self.docket.uuid:
557 # TODO: use mmap when possible
555 # TODO: use mmap when possible
558 data = self._opener.read(self.docket.data_filename())
556 data = self._opener.read(self.docket.data_filename())
559 else:
557 else:
560 data = b''
558 data = b''
561 self._map = rustmod.DirstateMap.new_v2(
559 self._map = rustmod.DirstateMap.new_v2(
562 data, self.docket.data_size, self.docket.tree_metadata
560 data, self.docket.data_size, self.docket.tree_metadata
563 )
561 )
564 parents = self.docket.parents
562 parents = self.docket.parents
565 else:
563 else:
566 self._map, parents = rustmod.DirstateMap.new_v1(
564 self._map, parents = rustmod.DirstateMap.new_v1(
567 self._readdirstatefile()
565 self._readdirstatefile()
568 )
566 )
569
567
570 if parents and not self._dirtyparents:
568 if parents and not self._dirtyparents:
571 self.setparents(*parents)
569 self.setparents(*parents)
572
570
573 self.__contains__ = self._map.__contains__
571 self.__contains__ = self._map.__contains__
574 self.__getitem__ = self._map.__getitem__
572 self.__getitem__ = self._map.__getitem__
575 self.get = self._map.get
573 self.get = self._map.get
576 return self._map
574 return self._map
577
575
578 @property
576 @property
579 def copymap(self):
577 def copymap(self):
580 return self._map.copymap()
578 return self._map.copymap()
581
579
582 def debug_iter(self, all):
580 def debug_iter(self, all):
583 """
581 """
584 Return an iterator of (filename, state, mode, size, mtime) tuples
582 Return an iterator of (filename, state, mode, size, mtime) tuples
585
583
586 `all`: also include with `state == b' '` dirstate tree nodes that
584 `all`: also include with `state == b' '` dirstate tree nodes that
587 don't have an associated `DirstateItem`.
585 don't have an associated `DirstateItem`.
588
586
589 """
587 """
590 return self._map.debug_iter(all)
588 return self._map.debug_iter(all)
591
589
592 def clear(self):
590 def clear(self):
593 self._map.clear()
591 self._map.clear()
594 self.setparents(
592 self.setparents(
595 self._nodeconstants.nullid, self._nodeconstants.nullid
593 self._nodeconstants.nullid, self._nodeconstants.nullid
596 )
594 )
597 util.clearcachedproperty(self, b"_dirs")
595 util.clearcachedproperty(self, b"_dirs")
598 util.clearcachedproperty(self, b"_alldirs")
596 util.clearcachedproperty(self, b"_alldirs")
599 util.clearcachedproperty(self, b"dirfoldmap")
597 util.clearcachedproperty(self, b"dirfoldmap")
600
598
601 def items(self):
599 def items(self):
602 return self._map.items()
600 return self._map.items()
603
601
604 # forward for python2,3 compat
602 # forward for python2,3 compat
605 iteritems = items
603 iteritems = items
606
604
607 def keys(self):
605 def keys(self):
608 return iter(self._map)
606 return iter(self._map)
609
607
610 ### reading/setting parents
608 ### reading/setting parents
611
609
612 def setparents(self, p1, p2, fold_p2=False):
610 def setparents(self, p1, p2, fold_p2=False):
613 self._parents = (p1, p2)
611 self._parents = (p1, p2)
614 self._dirtyparents = True
612 self._dirtyparents = True
615 copies = {}
613 copies = {}
616 if fold_p2:
614 if fold_p2:
617 # Collect into an intermediate list to avoid a `RuntimeError`
615 # Collect into an intermediate list to avoid a `RuntimeError`
618 # exception due to mutation during iteration.
616 # exception due to mutation during iteration.
619 # TODO: move this the whole loop to Rust where `iter_mut`
617 # TODO: move this the whole loop to Rust where `iter_mut`
620 # enables in-place mutation of elements of a collection while
618 # enables in-place mutation of elements of a collection while
621 # iterating it, without mutating the collection itself.
619 # iterating it, without mutating the collection itself.
622 files_with_p2_info = [
620 files_with_p2_info = [
623 f for f, s in self._map.items() if s.p2_info
621 f for f, s in self._map.items() if s.p2_info
624 ]
622 ]
625 rust_map = self._map
623 rust_map = self._map
626 for f in files_with_p2_info:
624 for f in files_with_p2_info:
627 e = rust_map.get(f)
625 e = rust_map.get(f)
628 source = self.copymap.pop(f, None)
626 source = self.copymap.pop(f, None)
629 if source:
627 if source:
630 copies[f] = source
628 copies[f] = source
631 e.drop_merge_data()
629 e.drop_merge_data()
632 rust_map.set_dirstate_item(f, e)
630 rust_map.set_dirstate_item(f, e)
633 return copies
631 return copies
634
632
635 ### disk interaction
633 ### disk interaction
636
634
637 @propertycache
635 @propertycache
638 def identity(self):
636 def identity(self):
639 self._map
637 self._map
640 return self.identity
638 return self.identity
641
639
642 def write(self, tr, st):
640 def write(self, tr, st):
643 if not self._use_dirstate_v2:
641 if not self._use_dirstate_v2:
644 p1, p2 = self.parents()
642 p1, p2 = self.parents()
645 packed = self._map.write_v1(p1, p2)
643 packed = self._map.write_v1(p1, p2)
646 st.write(packed)
644 st.write(packed)
647 st.close()
645 st.close()
648 self._dirtyparents = False
646 self._dirtyparents = False
649 return
647 return
650
648
651 # We can only append to an existing data file if there is one
649 # We can only append to an existing data file if there is one
652 can_append = self.docket.uuid is not None
650 can_append = self.docket.uuid is not None
653 packed, meta, append = self._map.write_v2(can_append)
651 packed, meta, append = self._map.write_v2(can_append)
654 if append:
652 if append:
655 docket = self.docket
653 docket = self.docket
656 data_filename = docket.data_filename()
654 data_filename = docket.data_filename()
657 if tr:
655 if tr:
658 tr.add(data_filename, docket.data_size)
656 tr.add(data_filename, docket.data_size)
659 with self._opener(data_filename, b'r+b') as fp:
657 with self._opener(data_filename, b'r+b') as fp:
660 fp.seek(docket.data_size)
658 fp.seek(docket.data_size)
661 assert fp.tell() == docket.data_size
659 assert fp.tell() == docket.data_size
662 written = fp.write(packed)
660 written = fp.write(packed)
663 if written is not None: # py2 may return None
661 if written is not None: # py2 may return None
664 assert written == len(packed), (written, len(packed))
662 assert written == len(packed), (written, len(packed))
665 docket.data_size += len(packed)
663 docket.data_size += len(packed)
666 docket.parents = self.parents()
664 docket.parents = self.parents()
667 docket.tree_metadata = meta
665 docket.tree_metadata = meta
668 st.write(docket.serialize())
666 st.write(docket.serialize())
669 st.close()
667 st.close()
670 else:
668 else:
671 self.write_v2_no_append(tr, st, meta, packed)
669 self.write_v2_no_append(tr, st, meta, packed)
672 # Reload from the newly-written file
670 # Reload from the newly-written file
673 util.clearcachedproperty(self, b"_map")
671 util.clearcachedproperty(self, b"_map")
674 self._dirtyparents = False
672 self._dirtyparents = False
675
673
676 ### code related to maintaining and accessing "extra" property
674 ### code related to maintaining and accessing "extra" property
677 # (e.g. "has_dir")
675 # (e.g. "has_dir")
678
676
679 @propertycache
677 @propertycache
680 def filefoldmap(self):
678 def filefoldmap(self):
681 """Returns a dictionary mapping normalized case paths to their
679 """Returns a dictionary mapping normalized case paths to their
682 non-normalized versions.
680 non-normalized versions.
683 """
681 """
684 return self._map.filefoldmapasdict()
682 return self._map.filefoldmapasdict()
685
683
686 def hastrackeddir(self, d):
684 def hastrackeddir(self, d):
687 return self._map.hastrackeddir(d)
685 return self._map.hastrackeddir(d)
688
686
689 def hasdir(self, d):
687 def hasdir(self, d):
690 return self._map.hasdir(d)
688 return self._map.hasdir(d)
691
689
692 @propertycache
690 @propertycache
693 def dirfoldmap(self):
691 def dirfoldmap(self):
694 f = {}
692 f = {}
695 normcase = util.normcase
693 normcase = util.normcase
696 for name in self._map.tracked_dirs():
694 for name in self._map.tracked_dirs():
697 f[normcase(name)] = name
695 f[normcase(name)] = name
698 return f
696 return f
699
697
700 ### code related to manipulation of entries and copy-sources
698 ### code related to manipulation of entries and copy-sources
701
699
702 def set_tracked(self, f):
700 def set_tracked(self, f):
703 return self._map.set_tracked(f)
701 return self._map.set_tracked(f)
704
702
705 def set_untracked(self, f):
703 def set_untracked(self, f):
706 return self._map.set_untracked(f)
704 return self._map.set_untracked(f)
707
705
708 def set_clean(self, filename, mode, size, mtime):
706 def set_clean(self, filename, mode, size, mtime):
709 self._map.set_clean(filename, mode, size, mtime)
707 self._map.set_clean(filename, mode, size, mtime)
710
708
711 def set_possibly_dirty(self, f):
709 def set_possibly_dirty(self, f):
712 self._map.set_possibly_dirty(f)
710 self._map.set_possibly_dirty(f)
713
711
714 def reset_state(
712 def reset_state(
715 self,
713 self,
716 filename,
714 filename,
717 wc_tracked=False,
715 wc_tracked=False,
718 p1_tracked=False,
716 p1_tracked=False,
719 p2_info=False,
717 p2_info=False,
720 has_meaningful_mtime=True,
718 has_meaningful_mtime=True,
721 parentfiledata=None,
719 parentfiledata=None,
722 ):
720 ):
723 return self._map.reset_state(
721 return self._map.reset_state(
724 filename,
722 filename,
725 wc_tracked,
723 wc_tracked,
726 p1_tracked,
724 p1_tracked,
727 p2_info,
725 p2_info,
728 has_meaningful_mtime,
726 has_meaningful_mtime,
729 parentfiledata,
727 parentfiledata,
730 )
728 )
General Comments 0
You need to be logged in to leave comments. Login now