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