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