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