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