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