##// END OF EJS Templates
dirstatemap: use the default code to handle "added" case...
marmoute -
r48714:6816ae36 default
parent child Browse files
Show More
@@ -1,934 +1,933 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 DirstateItem = parsers.DirstateItem
30 DirstateItem = parsers.DirstateItem
31
31
32
32
33 # a special value used internally for `size` if the file come from the other parent
33 # a special value used internally for `size` if the file come from the other parent
34 FROM_P2 = -2
34 FROM_P2 = -2
35
35
36 # a special value used internally for `size` if the file is modified/merged/added
36 # a special value used internally for `size` if the file is modified/merged/added
37 NONNORMAL = -1
37 NONNORMAL = -1
38
38
39 # a special value used internally for `time` if the time is ambigeous
39 # a special value used internally for `time` if the time is ambigeous
40 AMBIGUOUS_TIME = -1
40 AMBIGUOUS_TIME = -1
41
41
42 rangemask = 0x7FFFFFFF
42 rangemask = 0x7FFFFFFF
43
43
44
44
45 class dirstatemap(object):
45 class dirstatemap(object):
46 """Map encapsulating the dirstate's contents.
46 """Map encapsulating the dirstate's contents.
47
47
48 The dirstate contains the following state:
48 The dirstate contains the following state:
49
49
50 - `identity` is the identity of the dirstate file, which can be used to
50 - `identity` is the identity of the dirstate file, which can be used to
51 detect when changes have occurred to the dirstate file.
51 detect when changes have occurred to the dirstate file.
52
52
53 - `parents` is a pair containing the parents of the working copy. The
53 - `parents` is a pair containing the parents of the working copy. The
54 parents are updated by calling `setparents`.
54 parents are updated by calling `setparents`.
55
55
56 - the state map maps filenames to tuples of (state, mode, size, mtime),
56 - the state map maps filenames to tuples of (state, mode, size, mtime),
57 where state is a single character representing 'normal', 'added',
57 where state is a single character representing 'normal', 'added',
58 'removed', or 'merged'. It is read by treating the dirstate as a
58 'removed', or 'merged'. It is read by treating the dirstate as a
59 dict. File state is updated by calling the `addfile`, `removefile` and
59 dict. File state is updated by calling the `addfile`, `removefile` and
60 `dropfile` methods.
60 `dropfile` methods.
61
61
62 - `copymap` maps destination filenames to their source filename.
62 - `copymap` maps destination filenames to their source filename.
63
63
64 The dirstate also provides the following views onto the state:
64 The dirstate also provides the following views onto the state:
65
65
66 - `nonnormalset` is a set of the filenames that have state other
66 - `nonnormalset` is a set of the filenames that have state other
67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
68
68
69 - `otherparentset` is a set of the filenames that are marked as coming
69 - `otherparentset` is a set of the filenames that are marked as coming
70 from the second parent when the dirstate is currently being merged.
70 from the second parent when the dirstate is currently being merged.
71
71
72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
73 form that they appear as in the dirstate.
73 form that they appear as in the dirstate.
74
74
75 - `dirfoldmap` is a dict mapping normalized directory names to the
75 - `dirfoldmap` is a dict mapping normalized directory names to the
76 denormalized form that they appear as in the dirstate.
76 denormalized form that they appear as in the dirstate.
77 """
77 """
78
78
79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
80 self._ui = ui
80 self._ui = ui
81 self._opener = opener
81 self._opener = opener
82 self._root = root
82 self._root = root
83 self._filename = b'dirstate'
83 self._filename = b'dirstate'
84 self._nodelen = 20
84 self._nodelen = 20
85 self._nodeconstants = nodeconstants
85 self._nodeconstants = nodeconstants
86 assert (
86 assert (
87 not use_dirstate_v2
87 not use_dirstate_v2
88 ), "should have detected unsupported requirement"
88 ), "should have detected unsupported requirement"
89
89
90 self._parents = None
90 self._parents = None
91 self._dirtyparents = False
91 self._dirtyparents = False
92
92
93 # for consistent view between _pl() and _read() invocations
93 # for consistent view between _pl() and _read() invocations
94 self._pendingmode = None
94 self._pendingmode = None
95
95
96 @propertycache
96 @propertycache
97 def _map(self):
97 def _map(self):
98 self._map = {}
98 self._map = {}
99 self.read()
99 self.read()
100 return self._map
100 return self._map
101
101
102 @propertycache
102 @propertycache
103 def copymap(self):
103 def copymap(self):
104 self.copymap = {}
104 self.copymap = {}
105 self._map
105 self._map
106 return self.copymap
106 return self.copymap
107
107
108 def clear(self):
108 def clear(self):
109 self._map.clear()
109 self._map.clear()
110 self.copymap.clear()
110 self.copymap.clear()
111 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
111 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
112 util.clearcachedproperty(self, b"_dirs")
112 util.clearcachedproperty(self, b"_dirs")
113 util.clearcachedproperty(self, b"_alldirs")
113 util.clearcachedproperty(self, b"_alldirs")
114 util.clearcachedproperty(self, b"filefoldmap")
114 util.clearcachedproperty(self, b"filefoldmap")
115 util.clearcachedproperty(self, b"dirfoldmap")
115 util.clearcachedproperty(self, b"dirfoldmap")
116 util.clearcachedproperty(self, b"nonnormalset")
116 util.clearcachedproperty(self, b"nonnormalset")
117 util.clearcachedproperty(self, b"otherparentset")
117 util.clearcachedproperty(self, b"otherparentset")
118
118
119 def items(self):
119 def items(self):
120 return pycompat.iteritems(self._map)
120 return pycompat.iteritems(self._map)
121
121
122 # forward for python2,3 compat
122 # forward for python2,3 compat
123 iteritems = items
123 iteritems = items
124
124
125 debug_iter = items
125 debug_iter = items
126
126
127 def __len__(self):
127 def __len__(self):
128 return len(self._map)
128 return len(self._map)
129
129
130 def __iter__(self):
130 def __iter__(self):
131 return iter(self._map)
131 return iter(self._map)
132
132
133 def get(self, key, default=None):
133 def get(self, key, default=None):
134 return self._map.get(key, default)
134 return self._map.get(key, default)
135
135
136 def __contains__(self, key):
136 def __contains__(self, key):
137 return key in self._map
137 return key in self._map
138
138
139 def __getitem__(self, key):
139 def __getitem__(self, key):
140 return self._map[key]
140 return self._map[key]
141
141
142 def keys(self):
142 def keys(self):
143 return self._map.keys()
143 return self._map.keys()
144
144
145 def preload(self):
145 def preload(self):
146 """Loads the underlying data, if it's not already loaded"""
146 """Loads the underlying data, if it's not already loaded"""
147 self._map
147 self._map
148
148
149 def _dirs_incr(self, filename, old_entry=None):
149 def _dirs_incr(self, filename, old_entry=None):
150 """incremente the dirstate counter if applicable"""
150 """incremente the dirstate counter if applicable"""
151 if (
151 if (
152 old_entry is None or old_entry.removed
152 old_entry is None or old_entry.removed
153 ) and "_dirs" in self.__dict__:
153 ) and "_dirs" in self.__dict__:
154 self._dirs.addpath(filename)
154 self._dirs.addpath(filename)
155 if old_entry is None and "_alldirs" in self.__dict__:
155 if old_entry is None and "_alldirs" in self.__dict__:
156 self._alldirs.addpath(filename)
156 self._alldirs.addpath(filename)
157
157
158 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
158 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
159 """decremente the dirstate counter if applicable"""
159 """decremente the dirstate counter if applicable"""
160 if old_entry is not None:
160 if old_entry is not None:
161 if "_dirs" in self.__dict__ and not old_entry.removed:
161 if "_dirs" in self.__dict__ and not old_entry.removed:
162 self._dirs.delpath(filename)
162 self._dirs.delpath(filename)
163 if "_alldirs" in self.__dict__ and not remove_variant:
163 if "_alldirs" in self.__dict__ and not remove_variant:
164 self._alldirs.delpath(filename)
164 self._alldirs.delpath(filename)
165 elif remove_variant and "_alldirs" in self.__dict__:
165 elif remove_variant and "_alldirs" in self.__dict__:
166 self._alldirs.addpath(filename)
166 self._alldirs.addpath(filename)
167 if "filefoldmap" in self.__dict__:
167 if "filefoldmap" in self.__dict__:
168 normed = util.normcase(filename)
168 normed = util.normcase(filename)
169 self.filefoldmap.pop(normed, None)
169 self.filefoldmap.pop(normed, None)
170
170
171 def set_possibly_dirty(self, filename):
171 def set_possibly_dirty(self, filename):
172 """record that the current state of the file on disk is unknown"""
172 """record that the current state of the file on disk is unknown"""
173 self[filename].set_possibly_dirty()
173 self[filename].set_possibly_dirty()
174
174
175 def addfile(
175 def addfile(
176 self,
176 self,
177 f,
177 f,
178 mode=0,
178 mode=0,
179 size=None,
179 size=None,
180 mtime=None,
180 mtime=None,
181 added=False,
181 added=False,
182 merged=False,
182 merged=False,
183 from_p2=False,
183 from_p2=False,
184 possibly_dirty=False,
184 possibly_dirty=False,
185 ):
185 ):
186 """Add a tracked file to the dirstate."""
186 """Add a tracked file to the dirstate."""
187 if added:
187 if added:
188 assert not merged
188 assert not merged
189 assert not possibly_dirty
189 assert not possibly_dirty
190 assert not from_p2
190 assert not from_p2
191 state = b'a'
191 state = b'a'
192 size = NONNORMAL
192 size = NONNORMAL
193 mtime = AMBIGUOUS_TIME
193 mtime = AMBIGUOUS_TIME
194 elif merged:
194 elif merged:
195 assert not possibly_dirty
195 assert not possibly_dirty
196 assert not from_p2
196 assert not from_p2
197 state = b'm'
197 state = b'm'
198 size = FROM_P2
198 size = FROM_P2
199 mtime = AMBIGUOUS_TIME
199 mtime = AMBIGUOUS_TIME
200 elif from_p2:
200 elif from_p2:
201 assert not possibly_dirty
201 assert not possibly_dirty
202 state = b'n'
202 state = b'n'
203 size = FROM_P2
203 size = FROM_P2
204 mtime = AMBIGUOUS_TIME
204 mtime = AMBIGUOUS_TIME
205 elif possibly_dirty:
205 elif possibly_dirty:
206 state = b'n'
206 state = b'n'
207 size = NONNORMAL
207 size = NONNORMAL
208 mtime = AMBIGUOUS_TIME
208 mtime = AMBIGUOUS_TIME
209 else:
209 else:
210 assert size != FROM_P2
210 assert size != FROM_P2
211 assert size != NONNORMAL
211 assert size != NONNORMAL
212 assert size is not None
212 assert size is not None
213 assert mtime is not None
213 assert mtime is not None
214
214
215 state = b'n'
215 state = b'n'
216 size = size & rangemask
216 size = size & rangemask
217 mtime = mtime & rangemask
217 mtime = mtime & rangemask
218 assert state is not None
218 assert state is not None
219 assert size is not None
219 assert size is not None
220 assert mtime is not None
220 assert mtime is not None
221 old_entry = self.get(f)
221 old_entry = self.get(f)
222 self._dirs_incr(f, old_entry)
222 self._dirs_incr(f, old_entry)
223 e = self._map[f] = DirstateItem.from_v1_data(state, mode, size, mtime)
223 e = self._map[f] = DirstateItem.from_v1_data(state, mode, size, mtime)
224 if e.dm_nonnormal:
224 if e.dm_nonnormal:
225 self.nonnormalset.add(f)
225 self.nonnormalset.add(f)
226 if e.dm_otherparent:
226 if e.dm_otherparent:
227 self.otherparentset.add(f)
227 self.otherparentset.add(f)
228
228
229 def reset_state(
229 def reset_state(
230 self,
230 self,
231 filename,
231 filename,
232 wc_tracked,
232 wc_tracked,
233 p1_tracked,
233 p1_tracked,
234 p2_tracked=False,
234 p2_tracked=False,
235 merged=False,
235 merged=False,
236 clean_p1=False,
236 clean_p1=False,
237 clean_p2=False,
237 clean_p2=False,
238 possibly_dirty=False,
238 possibly_dirty=False,
239 parentfiledata=None,
239 parentfiledata=None,
240 ):
240 ):
241 """Set a entry to a given state, diregarding all previous state
241 """Set a entry to a given state, diregarding all previous state
242
242
243 This is to be used by the part of the dirstate API dedicated to
243 This is to be used by the part of the dirstate API dedicated to
244 adjusting the dirstate after a update/merge.
244 adjusting the dirstate after a update/merge.
245
245
246 note: calling this might result to no entry existing at all if the
246 note: calling this might result to no entry existing at all if the
247 dirstate map does not see any point at having one for this file
247 dirstate map does not see any point at having one for this file
248 anymore.
248 anymore.
249 """
249 """
250 if merged and (clean_p1 or clean_p2):
250 if merged and (clean_p1 or clean_p2):
251 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
251 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
252 raise error.ProgrammingError(msg)
252 raise error.ProgrammingError(msg)
253 # copy information are now outdated
253 # copy information are now outdated
254 # (maybe new information should be in directly passed to this function)
254 # (maybe new information should be in directly passed to this function)
255 self.copymap.pop(filename, None)
255 self.copymap.pop(filename, None)
256
256
257 if not (p1_tracked or p2_tracked or wc_tracked):
257 if not (p1_tracked or p2_tracked or wc_tracked):
258 self.dropfile(filename)
258 self.dropfile(filename)
259 return
259 return
260 elif merged:
260 elif merged:
261 # XXX might be merged and removed ?
261 # XXX might be merged and removed ?
262 entry = self.get(filename)
262 entry = self.get(filename)
263 if entry is not None and entry.tracked:
263 if entry is not None and entry.tracked:
264 # XXX mostly replicate dirstate.other parent. We should get
264 # XXX mostly replicate dirstate.other parent. We should get
265 # the higher layer to pass us more reliable data where `merged`
265 # the higher layer to pass us more reliable data where `merged`
266 # actually mean merged. Dropping the else clause will show
266 # actually mean merged. Dropping the else clause will show
267 # failure in `test-graft.t`
267 # failure in `test-graft.t`
268 self.addfile(filename, merged=True)
268 self.addfile(filename, merged=True)
269 return
269 return
270 else:
270 else:
271 self.addfile(filename, from_p2=True)
271 self.addfile(filename, from_p2=True)
272 return
272 return
273 elif not (p1_tracked or p2_tracked) and wc_tracked:
273 elif not (p1_tracked or p2_tracked) and wc_tracked:
274 self.addfile(filename, added=True, possibly_dirty=possibly_dirty)
274 pass # file is added, nothing special to adjust
275 return
276 elif (p1_tracked or p2_tracked) and not wc_tracked:
275 elif (p1_tracked or p2_tracked) and not wc_tracked:
277 pass
276 pass
278 elif clean_p2 and wc_tracked:
277 elif clean_p2 and wc_tracked:
279 if p1_tracked or self.get(filename) is not None:
278 if p1_tracked or self.get(filename) is not None:
280 # XXX the `self.get` call is catching some case in
279 # XXX the `self.get` call is catching some case in
281 # `test-merge-remove.t` where the file is tracked in p1, the
280 # `test-merge-remove.t` where the file is tracked in p1, the
282 # p1_tracked argument is False.
281 # p1_tracked argument is False.
283 #
282 #
284 # In addition, this seems to be a case where the file is marked
283 # In addition, this seems to be a case where the file is marked
285 # as merged without actually being the result of a merge
284 # as merged without actually being the result of a merge
286 # action. So thing are not ideal here.
285 # action. So thing are not ideal here.
287 merged = True
286 merged = True
288 clean_p2 = False
287 clean_p2 = False
289 elif not p1_tracked and p2_tracked and wc_tracked:
288 elif not p1_tracked and p2_tracked and wc_tracked:
290 clean_p2 = True
289 clean_p2 = True
291 elif possibly_dirty:
290 elif possibly_dirty:
292 pass
291 pass
293 elif wc_tracked:
292 elif wc_tracked:
294 # this is a "normal" file
293 # this is a "normal" file
295 if parentfiledata is None:
294 if parentfiledata is None:
296 msg = b'failed to pass parentfiledata for a normal file: %s'
295 msg = b'failed to pass parentfiledata for a normal file: %s'
297 msg %= filename
296 msg %= filename
298 raise error.ProgrammingError(msg)
297 raise error.ProgrammingError(msg)
299 else:
298 else:
300 assert False, 'unreachable'
299 assert False, 'unreachable'
301
300
302 old_entry = self._map.get(filename)
301 old_entry = self._map.get(filename)
303 self._dirs_incr(filename, old_entry)
302 self._dirs_incr(filename, old_entry)
304 entry = DirstateItem(
303 entry = DirstateItem(
305 wc_tracked=wc_tracked,
304 wc_tracked=wc_tracked,
306 p1_tracked=p1_tracked,
305 p1_tracked=p1_tracked,
307 p2_tracked=p2_tracked,
306 p2_tracked=p2_tracked,
308 merged=merged,
307 merged=merged,
309 clean_p1=clean_p1,
308 clean_p1=clean_p1,
310 clean_p2=clean_p2,
309 clean_p2=clean_p2,
311 possibly_dirty=possibly_dirty,
310 possibly_dirty=possibly_dirty,
312 parentfiledata=parentfiledata,
311 parentfiledata=parentfiledata,
313 )
312 )
314 if entry.dm_nonnormal:
313 if entry.dm_nonnormal:
315 self.nonnormalset.add(filename)
314 self.nonnormalset.add(filename)
316 else:
315 else:
317 self.nonnormalset.discard(filename)
316 self.nonnormalset.discard(filename)
318 if entry.dm_otherparent:
317 if entry.dm_otherparent:
319 self.otherparentset.add(filename)
318 self.otherparentset.add(filename)
320 else:
319 else:
321 self.otherparentset.discard(filename)
320 self.otherparentset.discard(filename)
322 self._map[filename] = entry
321 self._map[filename] = entry
323
322
324 def set_untracked(self, f):
323 def set_untracked(self, f):
325 """Mark a file as no longer tracked in the dirstate map"""
324 """Mark a file as no longer tracked in the dirstate map"""
326 entry = self[f]
325 entry = self[f]
327 self._dirs_decr(f, old_entry=entry, remove_variant=True)
326 self._dirs_decr(f, old_entry=entry, remove_variant=True)
328 if entry.from_p2:
327 if entry.from_p2:
329 self.otherparentset.add(f)
328 self.otherparentset.add(f)
330 elif not entry.merged:
329 elif not entry.merged:
331 self.copymap.pop(f, None)
330 self.copymap.pop(f, None)
332 entry.set_untracked()
331 entry.set_untracked()
333 self.nonnormalset.add(f)
332 self.nonnormalset.add(f)
334
333
335 def dropfile(self, f):
334 def dropfile(self, f):
336 """
335 """
337 Remove a file from the dirstate. Returns True if the file was
336 Remove a file from the dirstate. Returns True if the file was
338 previously recorded.
337 previously recorded.
339 """
338 """
340 old_entry = self._map.pop(f, None)
339 old_entry = self._map.pop(f, None)
341 self._dirs_decr(f, old_entry=old_entry)
340 self._dirs_decr(f, old_entry=old_entry)
342 self.nonnormalset.discard(f)
341 self.nonnormalset.discard(f)
343 return old_entry is not None
342 return old_entry is not None
344
343
345 def clearambiguoustimes(self, files, now):
344 def clearambiguoustimes(self, files, now):
346 for f in files:
345 for f in files:
347 e = self.get(f)
346 e = self.get(f)
348 if e is not None and e.need_delay(now):
347 if e is not None and e.need_delay(now):
349 e.set_possibly_dirty()
348 e.set_possibly_dirty()
350 self.nonnormalset.add(f)
349 self.nonnormalset.add(f)
351
350
352 def nonnormalentries(self):
351 def nonnormalentries(self):
353 '''Compute the nonnormal dirstate entries from the dmap'''
352 '''Compute the nonnormal dirstate entries from the dmap'''
354 try:
353 try:
355 return parsers.nonnormalotherparententries(self._map)
354 return parsers.nonnormalotherparententries(self._map)
356 except AttributeError:
355 except AttributeError:
357 nonnorm = set()
356 nonnorm = set()
358 otherparent = set()
357 otherparent = set()
359 for fname, e in pycompat.iteritems(self._map):
358 for fname, e in pycompat.iteritems(self._map):
360 if e.dm_nonnormal:
359 if e.dm_nonnormal:
361 nonnorm.add(fname)
360 nonnorm.add(fname)
362 if e.from_p2:
361 if e.from_p2:
363 otherparent.add(fname)
362 otherparent.add(fname)
364 return nonnorm, otherparent
363 return nonnorm, otherparent
365
364
366 @propertycache
365 @propertycache
367 def filefoldmap(self):
366 def filefoldmap(self):
368 """Returns a dictionary mapping normalized case paths to their
367 """Returns a dictionary mapping normalized case paths to their
369 non-normalized versions.
368 non-normalized versions.
370 """
369 """
371 try:
370 try:
372 makefilefoldmap = parsers.make_file_foldmap
371 makefilefoldmap = parsers.make_file_foldmap
373 except AttributeError:
372 except AttributeError:
374 pass
373 pass
375 else:
374 else:
376 return makefilefoldmap(
375 return makefilefoldmap(
377 self._map, util.normcasespec, util.normcasefallback
376 self._map, util.normcasespec, util.normcasefallback
378 )
377 )
379
378
380 f = {}
379 f = {}
381 normcase = util.normcase
380 normcase = util.normcase
382 for name, s in pycompat.iteritems(self._map):
381 for name, s in pycompat.iteritems(self._map):
383 if not s.removed:
382 if not s.removed:
384 f[normcase(name)] = name
383 f[normcase(name)] = name
385 f[b'.'] = b'.' # prevents useless util.fspath() invocation
384 f[b'.'] = b'.' # prevents useless util.fspath() invocation
386 return f
385 return f
387
386
388 def hastrackeddir(self, d):
387 def hastrackeddir(self, d):
389 """
388 """
390 Returns True if the dirstate contains a tracked (not removed) file
389 Returns True if the dirstate contains a tracked (not removed) file
391 in this directory.
390 in this directory.
392 """
391 """
393 return d in self._dirs
392 return d in self._dirs
394
393
395 def hasdir(self, d):
394 def hasdir(self, d):
396 """
395 """
397 Returns True if the dirstate contains a file (tracked or removed)
396 Returns True if the dirstate contains a file (tracked or removed)
398 in this directory.
397 in this directory.
399 """
398 """
400 return d in self._alldirs
399 return d in self._alldirs
401
400
402 @propertycache
401 @propertycache
403 def _dirs(self):
402 def _dirs(self):
404 return pathutil.dirs(self._map, b'r')
403 return pathutil.dirs(self._map, b'r')
405
404
406 @propertycache
405 @propertycache
407 def _alldirs(self):
406 def _alldirs(self):
408 return pathutil.dirs(self._map)
407 return pathutil.dirs(self._map)
409
408
410 def _opendirstatefile(self):
409 def _opendirstatefile(self):
411 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
410 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
412 if self._pendingmode is not None and self._pendingmode != mode:
411 if self._pendingmode is not None and self._pendingmode != mode:
413 fp.close()
412 fp.close()
414 raise error.Abort(
413 raise error.Abort(
415 _(b'working directory state may be changed parallelly')
414 _(b'working directory state may be changed parallelly')
416 )
415 )
417 self._pendingmode = mode
416 self._pendingmode = mode
418 return fp
417 return fp
419
418
420 def parents(self):
419 def parents(self):
421 if not self._parents:
420 if not self._parents:
422 try:
421 try:
423 fp = self._opendirstatefile()
422 fp = self._opendirstatefile()
424 st = fp.read(2 * self._nodelen)
423 st = fp.read(2 * self._nodelen)
425 fp.close()
424 fp.close()
426 except IOError as err:
425 except IOError as err:
427 if err.errno != errno.ENOENT:
426 if err.errno != errno.ENOENT:
428 raise
427 raise
429 # File doesn't exist, so the current state is empty
428 # File doesn't exist, so the current state is empty
430 st = b''
429 st = b''
431
430
432 l = len(st)
431 l = len(st)
433 if l == self._nodelen * 2:
432 if l == self._nodelen * 2:
434 self._parents = (
433 self._parents = (
435 st[: self._nodelen],
434 st[: self._nodelen],
436 st[self._nodelen : 2 * self._nodelen],
435 st[self._nodelen : 2 * self._nodelen],
437 )
436 )
438 elif l == 0:
437 elif l == 0:
439 self._parents = (
438 self._parents = (
440 self._nodeconstants.nullid,
439 self._nodeconstants.nullid,
441 self._nodeconstants.nullid,
440 self._nodeconstants.nullid,
442 )
441 )
443 else:
442 else:
444 raise error.Abort(
443 raise error.Abort(
445 _(b'working directory state appears damaged!')
444 _(b'working directory state appears damaged!')
446 )
445 )
447
446
448 return self._parents
447 return self._parents
449
448
450 def setparents(self, p1, p2):
449 def setparents(self, p1, p2):
451 self._parents = (p1, p2)
450 self._parents = (p1, p2)
452 self._dirtyparents = True
451 self._dirtyparents = True
453
452
454 def read(self):
453 def read(self):
455 # ignore HG_PENDING because identity is used only for writing
454 # ignore HG_PENDING because identity is used only for writing
456 self.identity = util.filestat.frompath(
455 self.identity = util.filestat.frompath(
457 self._opener.join(self._filename)
456 self._opener.join(self._filename)
458 )
457 )
459
458
460 try:
459 try:
461 fp = self._opendirstatefile()
460 fp = self._opendirstatefile()
462 try:
461 try:
463 st = fp.read()
462 st = fp.read()
464 finally:
463 finally:
465 fp.close()
464 fp.close()
466 except IOError as err:
465 except IOError as err:
467 if err.errno != errno.ENOENT:
466 if err.errno != errno.ENOENT:
468 raise
467 raise
469 return
468 return
470 if not st:
469 if not st:
471 return
470 return
472
471
473 if util.safehasattr(parsers, b'dict_new_presized'):
472 if util.safehasattr(parsers, b'dict_new_presized'):
474 # Make an estimate of the number of files in the dirstate based on
473 # Make an estimate of the number of files in the dirstate based on
475 # its size. This trades wasting some memory for avoiding costly
474 # its size. This trades wasting some memory for avoiding costly
476 # resizes. Each entry have a prefix of 17 bytes followed by one or
475 # resizes. Each entry have a prefix of 17 bytes followed by one or
477 # two path names. Studies on various large-scale real-world repositories
476 # two path names. Studies on various large-scale real-world repositories
478 # found 54 bytes a reasonable upper limit for the average path names.
477 # found 54 bytes a reasonable upper limit for the average path names.
479 # Copy entries are ignored for the sake of this estimate.
478 # Copy entries are ignored for the sake of this estimate.
480 self._map = parsers.dict_new_presized(len(st) // 71)
479 self._map = parsers.dict_new_presized(len(st) // 71)
481
480
482 # Python's garbage collector triggers a GC each time a certain number
481 # Python's garbage collector triggers a GC each time a certain number
483 # of container objects (the number being defined by
482 # of container objects (the number being defined by
484 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
483 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
485 # for each file in the dirstate. The C version then immediately marks
484 # for each file in the dirstate. The C version then immediately marks
486 # them as not to be tracked by the collector. However, this has no
485 # them as not to be tracked by the collector. However, this has no
487 # effect on when GCs are triggered, only on what objects the GC looks
486 # effect on when GCs are triggered, only on what objects the GC looks
488 # into. This means that O(number of files) GCs are unavoidable.
487 # into. This means that O(number of files) GCs are unavoidable.
489 # Depending on when in the process's lifetime the dirstate is parsed,
488 # Depending on when in the process's lifetime the dirstate is parsed,
490 # this can get very expensive. As a workaround, disable GC while
489 # this can get very expensive. As a workaround, disable GC while
491 # parsing the dirstate.
490 # parsing the dirstate.
492 #
491 #
493 # (we cannot decorate the function directly since it is in a C module)
492 # (we cannot decorate the function directly since it is in a C module)
494 parse_dirstate = util.nogc(parsers.parse_dirstate)
493 parse_dirstate = util.nogc(parsers.parse_dirstate)
495 p = parse_dirstate(self._map, self.copymap, st)
494 p = parse_dirstate(self._map, self.copymap, st)
496 if not self._dirtyparents:
495 if not self._dirtyparents:
497 self.setparents(*p)
496 self.setparents(*p)
498
497
499 # Avoid excess attribute lookups by fast pathing certain checks
498 # Avoid excess attribute lookups by fast pathing certain checks
500 self.__contains__ = self._map.__contains__
499 self.__contains__ = self._map.__contains__
501 self.__getitem__ = self._map.__getitem__
500 self.__getitem__ = self._map.__getitem__
502 self.get = self._map.get
501 self.get = self._map.get
503
502
504 def write(self, _tr, st, now):
503 def write(self, _tr, st, now):
505 st.write(
504 st.write(
506 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
505 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
507 )
506 )
508 st.close()
507 st.close()
509 self._dirtyparents = False
508 self._dirtyparents = False
510 self.nonnormalset, self.otherparentset = self.nonnormalentries()
509 self.nonnormalset, self.otherparentset = self.nonnormalentries()
511
510
512 @propertycache
511 @propertycache
513 def nonnormalset(self):
512 def nonnormalset(self):
514 nonnorm, otherparents = self.nonnormalentries()
513 nonnorm, otherparents = self.nonnormalentries()
515 self.otherparentset = otherparents
514 self.otherparentset = otherparents
516 return nonnorm
515 return nonnorm
517
516
518 @propertycache
517 @propertycache
519 def otherparentset(self):
518 def otherparentset(self):
520 nonnorm, otherparents = self.nonnormalentries()
519 nonnorm, otherparents = self.nonnormalentries()
521 self.nonnormalset = nonnorm
520 self.nonnormalset = nonnorm
522 return otherparents
521 return otherparents
523
522
524 def non_normal_or_other_parent_paths(self):
523 def non_normal_or_other_parent_paths(self):
525 return self.nonnormalset.union(self.otherparentset)
524 return self.nonnormalset.union(self.otherparentset)
526
525
527 @propertycache
526 @propertycache
528 def identity(self):
527 def identity(self):
529 self._map
528 self._map
530 return self.identity
529 return self.identity
531
530
532 @propertycache
531 @propertycache
533 def dirfoldmap(self):
532 def dirfoldmap(self):
534 f = {}
533 f = {}
535 normcase = util.normcase
534 normcase = util.normcase
536 for name in self._dirs:
535 for name in self._dirs:
537 f[normcase(name)] = name
536 f[normcase(name)] = name
538 return f
537 return f
539
538
540
539
541 if rustmod is not None:
540 if rustmod is not None:
542
541
543 class dirstatemap(object):
542 class dirstatemap(object):
544 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
543 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
545 self._use_dirstate_v2 = use_dirstate_v2
544 self._use_dirstate_v2 = use_dirstate_v2
546 self._nodeconstants = nodeconstants
545 self._nodeconstants = nodeconstants
547 self._ui = ui
546 self._ui = ui
548 self._opener = opener
547 self._opener = opener
549 self._root = root
548 self._root = root
550 self._filename = b'dirstate'
549 self._filename = b'dirstate'
551 self._nodelen = 20 # Also update Rust code when changing this!
550 self._nodelen = 20 # Also update Rust code when changing this!
552 self._parents = None
551 self._parents = None
553 self._dirtyparents = False
552 self._dirtyparents = False
554 self._docket = None
553 self._docket = None
555
554
556 # for consistent view between _pl() and _read() invocations
555 # for consistent view between _pl() and _read() invocations
557 self._pendingmode = None
556 self._pendingmode = None
558
557
559 self._use_dirstate_tree = self._ui.configbool(
558 self._use_dirstate_tree = self._ui.configbool(
560 b"experimental",
559 b"experimental",
561 b"dirstate-tree.in-memory",
560 b"dirstate-tree.in-memory",
562 False,
561 False,
563 )
562 )
564
563
565 def addfile(
564 def addfile(
566 self,
565 self,
567 f,
566 f,
568 mode=0,
567 mode=0,
569 size=None,
568 size=None,
570 mtime=None,
569 mtime=None,
571 added=False,
570 added=False,
572 merged=False,
571 merged=False,
573 from_p2=False,
572 from_p2=False,
574 possibly_dirty=False,
573 possibly_dirty=False,
575 ):
574 ):
576 return self._rustmap.addfile(
575 return self._rustmap.addfile(
577 f,
576 f,
578 mode,
577 mode,
579 size,
578 size,
580 mtime,
579 mtime,
581 added,
580 added,
582 merged,
581 merged,
583 from_p2,
582 from_p2,
584 possibly_dirty,
583 possibly_dirty,
585 )
584 )
586
585
587 def reset_state(
586 def reset_state(
588 self,
587 self,
589 filename,
588 filename,
590 wc_tracked,
589 wc_tracked,
591 p1_tracked,
590 p1_tracked,
592 p2_tracked=False,
591 p2_tracked=False,
593 merged=False,
592 merged=False,
594 clean_p1=False,
593 clean_p1=False,
595 clean_p2=False,
594 clean_p2=False,
596 possibly_dirty=False,
595 possibly_dirty=False,
597 parentfiledata=None,
596 parentfiledata=None,
598 ):
597 ):
599 """Set a entry to a given state, disregarding all previous state
598 """Set a entry to a given state, disregarding all previous state
600
599
601 This is to be used by the part of the dirstate API dedicated to
600 This is to be used by the part of the dirstate API dedicated to
602 adjusting the dirstate after a update/merge.
601 adjusting the dirstate after a update/merge.
603
602
604 note: calling this might result to no entry existing at all if the
603 note: calling this might result to no entry existing at all if the
605 dirstate map does not see any point at having one for this file
604 dirstate map does not see any point at having one for this file
606 anymore.
605 anymore.
607 """
606 """
608 if merged and (clean_p1 or clean_p2):
607 if merged and (clean_p1 or clean_p2):
609 msg = (
608 msg = (
610 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
609 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
611 )
610 )
612 raise error.ProgrammingError(msg)
611 raise error.ProgrammingError(msg)
613 # copy information are now outdated
612 # copy information are now outdated
614 # (maybe new information should be in directly passed to this function)
613 # (maybe new information should be in directly passed to this function)
615 self.copymap.pop(filename, None)
614 self.copymap.pop(filename, None)
616
615
617 if not (p1_tracked or p2_tracked or wc_tracked):
616 if not (p1_tracked or p2_tracked or wc_tracked):
618 self.dropfile(filename)
617 self.dropfile(filename)
619 elif merged:
618 elif merged:
620 # XXX might be merged and removed ?
619 # XXX might be merged and removed ?
621 entry = self.get(filename)
620 entry = self.get(filename)
622 if entry is not None and entry.tracked:
621 if entry is not None and entry.tracked:
623 # XXX mostly replicate dirstate.other parent. We should get
622 # XXX mostly replicate dirstate.other parent. We should get
624 # the higher layer to pass us more reliable data where `merged`
623 # the higher layer to pass us more reliable data where `merged`
625 # actually mean merged. Dropping the else clause will show
624 # actually mean merged. Dropping the else clause will show
626 # failure in `test-graft.t`
625 # failure in `test-graft.t`
627 self.addfile(filename, merged=True)
626 self.addfile(filename, merged=True)
628 else:
627 else:
629 self.addfile(filename, from_p2=True)
628 self.addfile(filename, from_p2=True)
630 elif not (p1_tracked or p2_tracked) and wc_tracked:
629 elif not (p1_tracked or p2_tracked) and wc_tracked:
631 self.addfile(
630 self.addfile(
632 filename, added=True, possibly_dirty=possibly_dirty
631 filename, added=True, possibly_dirty=possibly_dirty
633 )
632 )
634 elif (p1_tracked or p2_tracked) and not wc_tracked:
633 elif (p1_tracked or p2_tracked) and not wc_tracked:
635 # XXX might be merged and removed ?
634 # XXX might be merged and removed ?
636 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
635 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
637 self.nonnormalset.add(filename)
636 self.nonnormalset.add(filename)
638 elif clean_p2 and wc_tracked:
637 elif clean_p2 and wc_tracked:
639 if p1_tracked or self.get(filename) is not None:
638 if p1_tracked or self.get(filename) is not None:
640 # XXX the `self.get` call is catching some case in
639 # XXX the `self.get` call is catching some case in
641 # `test-merge-remove.t` where the file is tracked in p1, the
640 # `test-merge-remove.t` where the file is tracked in p1, the
642 # p1_tracked argument is False.
641 # p1_tracked argument is False.
643 #
642 #
644 # In addition, this seems to be a case where the file is marked
643 # In addition, this seems to be a case where the file is marked
645 # as merged without actually being the result of a merge
644 # as merged without actually being the result of a merge
646 # action. So thing are not ideal here.
645 # action. So thing are not ideal here.
647 self.addfile(filename, merged=True)
646 self.addfile(filename, merged=True)
648 else:
647 else:
649 self.addfile(filename, from_p2=True)
648 self.addfile(filename, from_p2=True)
650 elif not p1_tracked and p2_tracked and wc_tracked:
649 elif not p1_tracked and p2_tracked and wc_tracked:
651 self.addfile(
650 self.addfile(
652 filename, from_p2=True, possibly_dirty=possibly_dirty
651 filename, from_p2=True, possibly_dirty=possibly_dirty
653 )
652 )
654 elif possibly_dirty:
653 elif possibly_dirty:
655 self.addfile(filename, possibly_dirty=possibly_dirty)
654 self.addfile(filename, possibly_dirty=possibly_dirty)
656 elif wc_tracked:
655 elif wc_tracked:
657 # this is a "normal" file
656 # this is a "normal" file
658 if parentfiledata is None:
657 if parentfiledata is None:
659 msg = b'failed to pass parentfiledata for a normal file: %s'
658 msg = b'failed to pass parentfiledata for a normal file: %s'
660 msg %= filename
659 msg %= filename
661 raise error.ProgrammingError(msg)
660 raise error.ProgrammingError(msg)
662 mode, size, mtime = parentfiledata
661 mode, size, mtime = parentfiledata
663 self.addfile(filename, mode=mode, size=size, mtime=mtime)
662 self.addfile(filename, mode=mode, size=size, mtime=mtime)
664 self.nonnormalset.discard(filename)
663 self.nonnormalset.discard(filename)
665 else:
664 else:
666 assert False, 'unreachable'
665 assert False, 'unreachable'
667
666
668 def set_untracked(self, f):
667 def set_untracked(self, f):
669 """Mark a file as no longer tracked in the dirstate map"""
668 """Mark a file as no longer tracked in the dirstate map"""
670 # in merge is only trigger more logic, so it "fine" to pass it.
669 # in merge is only trigger more logic, so it "fine" to pass it.
671 #
670 #
672 # the inner rust dirstate map code need to be adjusted once the API
671 # the inner rust dirstate map code need to be adjusted once the API
673 # for dirstate/dirstatemap/DirstateItem is a bit more settled
672 # for dirstate/dirstatemap/DirstateItem is a bit more settled
674 self._rustmap.removefile(f, in_merge=True)
673 self._rustmap.removefile(f, in_merge=True)
675
674
676 def removefile(self, *args, **kwargs):
675 def removefile(self, *args, **kwargs):
677 return self._rustmap.removefile(*args, **kwargs)
676 return self._rustmap.removefile(*args, **kwargs)
678
677
679 def dropfile(self, *args, **kwargs):
678 def dropfile(self, *args, **kwargs):
680 return self._rustmap.dropfile(*args, **kwargs)
679 return self._rustmap.dropfile(*args, **kwargs)
681
680
682 def clearambiguoustimes(self, *args, **kwargs):
681 def clearambiguoustimes(self, *args, **kwargs):
683 return self._rustmap.clearambiguoustimes(*args, **kwargs)
682 return self._rustmap.clearambiguoustimes(*args, **kwargs)
684
683
685 def nonnormalentries(self):
684 def nonnormalentries(self):
686 return self._rustmap.nonnormalentries()
685 return self._rustmap.nonnormalentries()
687
686
688 def get(self, *args, **kwargs):
687 def get(self, *args, **kwargs):
689 return self._rustmap.get(*args, **kwargs)
688 return self._rustmap.get(*args, **kwargs)
690
689
691 @property
690 @property
692 def copymap(self):
691 def copymap(self):
693 return self._rustmap.copymap()
692 return self._rustmap.copymap()
694
693
695 def directories(self):
694 def directories(self):
696 return self._rustmap.directories()
695 return self._rustmap.directories()
697
696
698 def debug_iter(self):
697 def debug_iter(self):
699 return self._rustmap.debug_iter()
698 return self._rustmap.debug_iter()
700
699
701 def preload(self):
700 def preload(self):
702 self._rustmap
701 self._rustmap
703
702
704 def clear(self):
703 def clear(self):
705 self._rustmap.clear()
704 self._rustmap.clear()
706 self.setparents(
705 self.setparents(
707 self._nodeconstants.nullid, self._nodeconstants.nullid
706 self._nodeconstants.nullid, self._nodeconstants.nullid
708 )
707 )
709 util.clearcachedproperty(self, b"_dirs")
708 util.clearcachedproperty(self, b"_dirs")
710 util.clearcachedproperty(self, b"_alldirs")
709 util.clearcachedproperty(self, b"_alldirs")
711 util.clearcachedproperty(self, b"dirfoldmap")
710 util.clearcachedproperty(self, b"dirfoldmap")
712
711
713 def items(self):
712 def items(self):
714 return self._rustmap.items()
713 return self._rustmap.items()
715
714
716 def keys(self):
715 def keys(self):
717 return iter(self._rustmap)
716 return iter(self._rustmap)
718
717
719 def __contains__(self, key):
718 def __contains__(self, key):
720 return key in self._rustmap
719 return key in self._rustmap
721
720
722 def __getitem__(self, item):
721 def __getitem__(self, item):
723 return self._rustmap[item]
722 return self._rustmap[item]
724
723
725 def __len__(self):
724 def __len__(self):
726 return len(self._rustmap)
725 return len(self._rustmap)
727
726
728 def __iter__(self):
727 def __iter__(self):
729 return iter(self._rustmap)
728 return iter(self._rustmap)
730
729
731 # forward for python2,3 compat
730 # forward for python2,3 compat
732 iteritems = items
731 iteritems = items
733
732
734 def _opendirstatefile(self):
733 def _opendirstatefile(self):
735 fp, mode = txnutil.trypending(
734 fp, mode = txnutil.trypending(
736 self._root, self._opener, self._filename
735 self._root, self._opener, self._filename
737 )
736 )
738 if self._pendingmode is not None and self._pendingmode != mode:
737 if self._pendingmode is not None and self._pendingmode != mode:
739 fp.close()
738 fp.close()
740 raise error.Abort(
739 raise error.Abort(
741 _(b'working directory state may be changed parallelly')
740 _(b'working directory state may be changed parallelly')
742 )
741 )
743 self._pendingmode = mode
742 self._pendingmode = mode
744 return fp
743 return fp
745
744
746 def _readdirstatefile(self, size=-1):
745 def _readdirstatefile(self, size=-1):
747 try:
746 try:
748 with self._opendirstatefile() as fp:
747 with self._opendirstatefile() as fp:
749 return fp.read(size)
748 return fp.read(size)
750 except IOError as err:
749 except IOError as err:
751 if err.errno != errno.ENOENT:
750 if err.errno != errno.ENOENT:
752 raise
751 raise
753 # File doesn't exist, so the current state is empty
752 # File doesn't exist, so the current state is empty
754 return b''
753 return b''
755
754
756 def setparents(self, p1, p2):
755 def setparents(self, p1, p2):
757 self._parents = (p1, p2)
756 self._parents = (p1, p2)
758 self._dirtyparents = True
757 self._dirtyparents = True
759
758
760 def parents(self):
759 def parents(self):
761 if not self._parents:
760 if not self._parents:
762 if self._use_dirstate_v2:
761 if self._use_dirstate_v2:
763 self._parents = self.docket.parents
762 self._parents = self.docket.parents
764 else:
763 else:
765 read_len = self._nodelen * 2
764 read_len = self._nodelen * 2
766 st = self._readdirstatefile(read_len)
765 st = self._readdirstatefile(read_len)
767 l = len(st)
766 l = len(st)
768 if l == read_len:
767 if l == read_len:
769 self._parents = (
768 self._parents = (
770 st[: self._nodelen],
769 st[: self._nodelen],
771 st[self._nodelen : 2 * self._nodelen],
770 st[self._nodelen : 2 * self._nodelen],
772 )
771 )
773 elif l == 0:
772 elif l == 0:
774 self._parents = (
773 self._parents = (
775 self._nodeconstants.nullid,
774 self._nodeconstants.nullid,
776 self._nodeconstants.nullid,
775 self._nodeconstants.nullid,
777 )
776 )
778 else:
777 else:
779 raise error.Abort(
778 raise error.Abort(
780 _(b'working directory state appears damaged!')
779 _(b'working directory state appears damaged!')
781 )
780 )
782
781
783 return self._parents
782 return self._parents
784
783
785 @property
784 @property
786 def docket(self):
785 def docket(self):
787 if not self._docket:
786 if not self._docket:
788 if not self._use_dirstate_v2:
787 if not self._use_dirstate_v2:
789 raise error.ProgrammingError(
788 raise error.ProgrammingError(
790 b'dirstate only has a docket in v2 format'
789 b'dirstate only has a docket in v2 format'
791 )
790 )
792 self._docket = docketmod.DirstateDocket.parse(
791 self._docket = docketmod.DirstateDocket.parse(
793 self._readdirstatefile(), self._nodeconstants
792 self._readdirstatefile(), self._nodeconstants
794 )
793 )
795 return self._docket
794 return self._docket
796
795
797 @propertycache
796 @propertycache
798 def _rustmap(self):
797 def _rustmap(self):
799 """
798 """
800 Fills the Dirstatemap when called.
799 Fills the Dirstatemap when called.
801 """
800 """
802 # ignore HG_PENDING because identity is used only for writing
801 # ignore HG_PENDING because identity is used only for writing
803 self.identity = util.filestat.frompath(
802 self.identity = util.filestat.frompath(
804 self._opener.join(self._filename)
803 self._opener.join(self._filename)
805 )
804 )
806
805
807 if self._use_dirstate_v2:
806 if self._use_dirstate_v2:
808 if self.docket.uuid:
807 if self.docket.uuid:
809 # TODO: use mmap when possible
808 # TODO: use mmap when possible
810 data = self._opener.read(self.docket.data_filename())
809 data = self._opener.read(self.docket.data_filename())
811 else:
810 else:
812 data = b''
811 data = b''
813 self._rustmap = rustmod.DirstateMap.new_v2(
812 self._rustmap = rustmod.DirstateMap.new_v2(
814 data, self.docket.data_size, self.docket.tree_metadata
813 data, self.docket.data_size, self.docket.tree_metadata
815 )
814 )
816 parents = self.docket.parents
815 parents = self.docket.parents
817 else:
816 else:
818 self._rustmap, parents = rustmod.DirstateMap.new_v1(
817 self._rustmap, parents = rustmod.DirstateMap.new_v1(
819 self._use_dirstate_tree, self._readdirstatefile()
818 self._use_dirstate_tree, self._readdirstatefile()
820 )
819 )
821
820
822 if parents and not self._dirtyparents:
821 if parents and not self._dirtyparents:
823 self.setparents(*parents)
822 self.setparents(*parents)
824
823
825 self.__contains__ = self._rustmap.__contains__
824 self.__contains__ = self._rustmap.__contains__
826 self.__getitem__ = self._rustmap.__getitem__
825 self.__getitem__ = self._rustmap.__getitem__
827 self.get = self._rustmap.get
826 self.get = self._rustmap.get
828 return self._rustmap
827 return self._rustmap
829
828
830 def write(self, tr, st, now):
829 def write(self, tr, st, now):
831 if not self._use_dirstate_v2:
830 if not self._use_dirstate_v2:
832 p1, p2 = self.parents()
831 p1, p2 = self.parents()
833 packed = self._rustmap.write_v1(p1, p2, now)
832 packed = self._rustmap.write_v1(p1, p2, now)
834 st.write(packed)
833 st.write(packed)
835 st.close()
834 st.close()
836 self._dirtyparents = False
835 self._dirtyparents = False
837 return
836 return
838
837
839 # We can only append to an existing data file if there is one
838 # We can only append to an existing data file if there is one
840 can_append = self.docket.uuid is not None
839 can_append = self.docket.uuid is not None
841 packed, meta, append = self._rustmap.write_v2(now, can_append)
840 packed, meta, append = self._rustmap.write_v2(now, can_append)
842 if append:
841 if append:
843 docket = self.docket
842 docket = self.docket
844 data_filename = docket.data_filename()
843 data_filename = docket.data_filename()
845 if tr:
844 if tr:
846 tr.add(data_filename, docket.data_size)
845 tr.add(data_filename, docket.data_size)
847 with self._opener(data_filename, b'r+b') as fp:
846 with self._opener(data_filename, b'r+b') as fp:
848 fp.seek(docket.data_size)
847 fp.seek(docket.data_size)
849 assert fp.tell() == docket.data_size
848 assert fp.tell() == docket.data_size
850 written = fp.write(packed)
849 written = fp.write(packed)
851 if written is not None: # py2 may return None
850 if written is not None: # py2 may return None
852 assert written == len(packed), (written, len(packed))
851 assert written == len(packed), (written, len(packed))
853 docket.data_size += len(packed)
852 docket.data_size += len(packed)
854 docket.parents = self.parents()
853 docket.parents = self.parents()
855 docket.tree_metadata = meta
854 docket.tree_metadata = meta
856 st.write(docket.serialize())
855 st.write(docket.serialize())
857 st.close()
856 st.close()
858 else:
857 else:
859 old_docket = self.docket
858 old_docket = self.docket
860 new_docket = docketmod.DirstateDocket.with_new_uuid(
859 new_docket = docketmod.DirstateDocket.with_new_uuid(
861 self.parents(), len(packed), meta
860 self.parents(), len(packed), meta
862 )
861 )
863 data_filename = new_docket.data_filename()
862 data_filename = new_docket.data_filename()
864 if tr:
863 if tr:
865 tr.add(data_filename, 0)
864 tr.add(data_filename, 0)
866 self._opener.write(data_filename, packed)
865 self._opener.write(data_filename, packed)
867 # Write the new docket after the new data file has been
866 # Write the new docket after the new data file has been
868 # written. Because `st` was opened with `atomictemp=True`,
867 # written. Because `st` was opened with `atomictemp=True`,
869 # the actual `.hg/dirstate` file is only affected on close.
868 # the actual `.hg/dirstate` file is only affected on close.
870 st.write(new_docket.serialize())
869 st.write(new_docket.serialize())
871 st.close()
870 st.close()
872 # Remove the old data file after the new docket pointing to
871 # Remove the old data file after the new docket pointing to
873 # the new data file was written.
872 # the new data file was written.
874 if old_docket.uuid:
873 if old_docket.uuid:
875 data_filename = old_docket.data_filename()
874 data_filename = old_docket.data_filename()
876 unlink = lambda _tr=None: self._opener.unlink(data_filename)
875 unlink = lambda _tr=None: self._opener.unlink(data_filename)
877 if tr:
876 if tr:
878 category = b"dirstate-v2-clean-" + old_docket.uuid
877 category = b"dirstate-v2-clean-" + old_docket.uuid
879 tr.addpostclose(category, unlink)
878 tr.addpostclose(category, unlink)
880 else:
879 else:
881 unlink()
880 unlink()
882 self._docket = new_docket
881 self._docket = new_docket
883 # Reload from the newly-written file
882 # Reload from the newly-written file
884 util.clearcachedproperty(self, b"_rustmap")
883 util.clearcachedproperty(self, b"_rustmap")
885 self._dirtyparents = False
884 self._dirtyparents = False
886
885
887 @propertycache
886 @propertycache
888 def filefoldmap(self):
887 def filefoldmap(self):
889 """Returns a dictionary mapping normalized case paths to their
888 """Returns a dictionary mapping normalized case paths to their
890 non-normalized versions.
889 non-normalized versions.
891 """
890 """
892 return self._rustmap.filefoldmapasdict()
891 return self._rustmap.filefoldmapasdict()
893
892
894 def hastrackeddir(self, d):
893 def hastrackeddir(self, d):
895 return self._rustmap.hastrackeddir(d)
894 return self._rustmap.hastrackeddir(d)
896
895
897 def hasdir(self, d):
896 def hasdir(self, d):
898 return self._rustmap.hasdir(d)
897 return self._rustmap.hasdir(d)
899
898
900 @propertycache
899 @propertycache
901 def identity(self):
900 def identity(self):
902 self._rustmap
901 self._rustmap
903 return self.identity
902 return self.identity
904
903
905 @property
904 @property
906 def nonnormalset(self):
905 def nonnormalset(self):
907 nonnorm = self._rustmap.non_normal_entries()
906 nonnorm = self._rustmap.non_normal_entries()
908 return nonnorm
907 return nonnorm
909
908
910 @propertycache
909 @propertycache
911 def otherparentset(self):
910 def otherparentset(self):
912 otherparents = self._rustmap.other_parent_entries()
911 otherparents = self._rustmap.other_parent_entries()
913 return otherparents
912 return otherparents
914
913
915 def non_normal_or_other_parent_paths(self):
914 def non_normal_or_other_parent_paths(self):
916 return self._rustmap.non_normal_or_other_parent_paths()
915 return self._rustmap.non_normal_or_other_parent_paths()
917
916
918 @propertycache
917 @propertycache
919 def dirfoldmap(self):
918 def dirfoldmap(self):
920 f = {}
919 f = {}
921 normcase = util.normcase
920 normcase = util.normcase
922 for name in self._rustmap.tracked_dirs():
921 for name in self._rustmap.tracked_dirs():
923 f[normcase(name)] = name
922 f[normcase(name)] = name
924 return f
923 return f
925
924
926 def set_possibly_dirty(self, filename):
925 def set_possibly_dirty(self, filename):
927 """record that the current state of the file on disk is unknown"""
926 """record that the current state of the file on disk is unknown"""
928 entry = self[filename]
927 entry = self[filename]
929 entry.set_possibly_dirty()
928 entry.set_possibly_dirty()
930 self._rustmap.set_v1(filename, entry)
929 self._rustmap.set_v1(filename, entry)
931
930
932 def __setitem__(self, key, value):
931 def __setitem__(self, key, value):
933 assert isinstance(value, DirstateItem)
932 assert isinstance(value, DirstateItem)
934 self._rustmap.set_v1(key, value)
933 self._rustmap.set_v1(key, value)
General Comments 0
You need to be logged in to leave comments. Login now