##// END OF EJS Templates
dirstatemap: use the default code to handle "clean-p2" case...
marmoute -
r48712:3429f48d default
parent child Browse files
Show More
@@ -1,942 +1,939 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 self.addfile(filename, added=True, possibly_dirty=possibly_dirty)
275 return
275 return
276 elif (p1_tracked or p2_tracked) and not wc_tracked:
276 elif (p1_tracked or p2_tracked) and not wc_tracked:
277 # XXX might be merged and removed ?
277 # XXX might be merged and removed ?
278 old_entry = self._map.get(filename)
278 old_entry = self._map.get(filename)
279 self._dirs_decr(filename, old_entry=old_entry, remove_variant=True)
279 self._dirs_decr(filename, old_entry=old_entry, remove_variant=True)
280 self._map[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
280 self._map[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
281 self.nonnormalset.add(filename)
281 self.nonnormalset.add(filename)
282 return
282 return
283 elif clean_p2 and wc_tracked:
283 elif clean_p2 and wc_tracked:
284 if p1_tracked or self.get(filename) is not None:
284 if p1_tracked or self.get(filename) is not None:
285 # XXX the `self.get` call is catching some case in
285 # XXX the `self.get` call is catching some case in
286 # `test-merge-remove.t` where the file is tracked in p1, the
286 # `test-merge-remove.t` where the file is tracked in p1, the
287 # p1_tracked argument is False.
287 # p1_tracked argument is False.
288 #
288 #
289 # In addition, this seems to be a case where the file is marked
289 # In addition, this seems to be a case where the file is marked
290 # as merged without actually being the result of a merge
290 # as merged without actually being the result of a merge
291 # action. So thing are not ideal here.
291 # action. So thing are not ideal here.
292 self.addfile(filename, merged=True)
292 merged = True
293 return
293 clean_p2 = False
294 else:
295 self.addfile(filename, from_p2=True)
296 return
297 elif not p1_tracked and p2_tracked and wc_tracked:
294 elif not p1_tracked and p2_tracked and wc_tracked:
298 clean_p2 = True
295 clean_p2 = True
299 elif possibly_dirty:
296 elif possibly_dirty:
300 pass
297 pass
301 elif wc_tracked:
298 elif wc_tracked:
302 # this is a "normal" file
299 # this is a "normal" file
303 if parentfiledata is None:
300 if parentfiledata is None:
304 msg = b'failed to pass parentfiledata for a normal file: %s'
301 msg = b'failed to pass parentfiledata for a normal file: %s'
305 msg %= filename
302 msg %= filename
306 raise error.ProgrammingError(msg)
303 raise error.ProgrammingError(msg)
307 else:
304 else:
308 assert False, 'unreachable'
305 assert False, 'unreachable'
309
306
310 old_entry = self._map.get(filename)
307 old_entry = self._map.get(filename)
311 self._dirs_incr(filename, old_entry)
308 self._dirs_incr(filename, old_entry)
312 entry = DirstateItem(
309 entry = DirstateItem(
313 wc_tracked=wc_tracked,
310 wc_tracked=wc_tracked,
314 p1_tracked=p1_tracked,
311 p1_tracked=p1_tracked,
315 p2_tracked=p2_tracked,
312 p2_tracked=p2_tracked,
316 merged=merged,
313 merged=merged,
317 clean_p1=clean_p1,
314 clean_p1=clean_p1,
318 clean_p2=clean_p2,
315 clean_p2=clean_p2,
319 possibly_dirty=possibly_dirty,
316 possibly_dirty=possibly_dirty,
320 parentfiledata=parentfiledata,
317 parentfiledata=parentfiledata,
321 )
318 )
322 if entry.dm_nonnormal:
319 if entry.dm_nonnormal:
323 self.nonnormalset.add(filename)
320 self.nonnormalset.add(filename)
324 else:
321 else:
325 self.nonnormalset.discard(filename)
322 self.nonnormalset.discard(filename)
326 if entry.dm_otherparent:
323 if entry.dm_otherparent:
327 self.otherparentset.add(filename)
324 self.otherparentset.add(filename)
328 else:
325 else:
329 self.otherparentset.discard(filename)
326 self.otherparentset.discard(filename)
330 self._map[filename] = entry
327 self._map[filename] = entry
331
328
332 def set_untracked(self, f):
329 def set_untracked(self, f):
333 """Mark a file as no longer tracked in the dirstate map"""
330 """Mark a file as no longer tracked in the dirstate map"""
334 entry = self[f]
331 entry = self[f]
335 self._dirs_decr(f, old_entry=entry, remove_variant=True)
332 self._dirs_decr(f, old_entry=entry, remove_variant=True)
336 if entry.from_p2:
333 if entry.from_p2:
337 self.otherparentset.add(f)
334 self.otherparentset.add(f)
338 elif not entry.merged:
335 elif not entry.merged:
339 self.copymap.pop(f, None)
336 self.copymap.pop(f, None)
340 entry.set_untracked()
337 entry.set_untracked()
341 self.nonnormalset.add(f)
338 self.nonnormalset.add(f)
342
339
343 def dropfile(self, f):
340 def dropfile(self, f):
344 """
341 """
345 Remove a file from the dirstate. Returns True if the file was
342 Remove a file from the dirstate. Returns True if the file was
346 previously recorded.
343 previously recorded.
347 """
344 """
348 old_entry = self._map.pop(f, None)
345 old_entry = self._map.pop(f, None)
349 self._dirs_decr(f, old_entry=old_entry)
346 self._dirs_decr(f, old_entry=old_entry)
350 self.nonnormalset.discard(f)
347 self.nonnormalset.discard(f)
351 return old_entry is not None
348 return old_entry is not None
352
349
353 def clearambiguoustimes(self, files, now):
350 def clearambiguoustimes(self, files, now):
354 for f in files:
351 for f in files:
355 e = self.get(f)
352 e = self.get(f)
356 if e is not None and e.need_delay(now):
353 if e is not None and e.need_delay(now):
357 e.set_possibly_dirty()
354 e.set_possibly_dirty()
358 self.nonnormalset.add(f)
355 self.nonnormalset.add(f)
359
356
360 def nonnormalentries(self):
357 def nonnormalentries(self):
361 '''Compute the nonnormal dirstate entries from the dmap'''
358 '''Compute the nonnormal dirstate entries from the dmap'''
362 try:
359 try:
363 return parsers.nonnormalotherparententries(self._map)
360 return parsers.nonnormalotherparententries(self._map)
364 except AttributeError:
361 except AttributeError:
365 nonnorm = set()
362 nonnorm = set()
366 otherparent = set()
363 otherparent = set()
367 for fname, e in pycompat.iteritems(self._map):
364 for fname, e in pycompat.iteritems(self._map):
368 if e.dm_nonnormal:
365 if e.dm_nonnormal:
369 nonnorm.add(fname)
366 nonnorm.add(fname)
370 if e.from_p2:
367 if e.from_p2:
371 otherparent.add(fname)
368 otherparent.add(fname)
372 return nonnorm, otherparent
369 return nonnorm, otherparent
373
370
374 @propertycache
371 @propertycache
375 def filefoldmap(self):
372 def filefoldmap(self):
376 """Returns a dictionary mapping normalized case paths to their
373 """Returns a dictionary mapping normalized case paths to their
377 non-normalized versions.
374 non-normalized versions.
378 """
375 """
379 try:
376 try:
380 makefilefoldmap = parsers.make_file_foldmap
377 makefilefoldmap = parsers.make_file_foldmap
381 except AttributeError:
378 except AttributeError:
382 pass
379 pass
383 else:
380 else:
384 return makefilefoldmap(
381 return makefilefoldmap(
385 self._map, util.normcasespec, util.normcasefallback
382 self._map, util.normcasespec, util.normcasefallback
386 )
383 )
387
384
388 f = {}
385 f = {}
389 normcase = util.normcase
386 normcase = util.normcase
390 for name, s in pycompat.iteritems(self._map):
387 for name, s in pycompat.iteritems(self._map):
391 if not s.removed:
388 if not s.removed:
392 f[normcase(name)] = name
389 f[normcase(name)] = name
393 f[b'.'] = b'.' # prevents useless util.fspath() invocation
390 f[b'.'] = b'.' # prevents useless util.fspath() invocation
394 return f
391 return f
395
392
396 def hastrackeddir(self, d):
393 def hastrackeddir(self, d):
397 """
394 """
398 Returns True if the dirstate contains a tracked (not removed) file
395 Returns True if the dirstate contains a tracked (not removed) file
399 in this directory.
396 in this directory.
400 """
397 """
401 return d in self._dirs
398 return d in self._dirs
402
399
403 def hasdir(self, d):
400 def hasdir(self, d):
404 """
401 """
405 Returns True if the dirstate contains a file (tracked or removed)
402 Returns True if the dirstate contains a file (tracked or removed)
406 in this directory.
403 in this directory.
407 """
404 """
408 return d in self._alldirs
405 return d in self._alldirs
409
406
410 @propertycache
407 @propertycache
411 def _dirs(self):
408 def _dirs(self):
412 return pathutil.dirs(self._map, b'r')
409 return pathutil.dirs(self._map, b'r')
413
410
414 @propertycache
411 @propertycache
415 def _alldirs(self):
412 def _alldirs(self):
416 return pathutil.dirs(self._map)
413 return pathutil.dirs(self._map)
417
414
418 def _opendirstatefile(self):
415 def _opendirstatefile(self):
419 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
416 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
420 if self._pendingmode is not None and self._pendingmode != mode:
417 if self._pendingmode is not None and self._pendingmode != mode:
421 fp.close()
418 fp.close()
422 raise error.Abort(
419 raise error.Abort(
423 _(b'working directory state may be changed parallelly')
420 _(b'working directory state may be changed parallelly')
424 )
421 )
425 self._pendingmode = mode
422 self._pendingmode = mode
426 return fp
423 return fp
427
424
428 def parents(self):
425 def parents(self):
429 if not self._parents:
426 if not self._parents:
430 try:
427 try:
431 fp = self._opendirstatefile()
428 fp = self._opendirstatefile()
432 st = fp.read(2 * self._nodelen)
429 st = fp.read(2 * self._nodelen)
433 fp.close()
430 fp.close()
434 except IOError as err:
431 except IOError as err:
435 if err.errno != errno.ENOENT:
432 if err.errno != errno.ENOENT:
436 raise
433 raise
437 # File doesn't exist, so the current state is empty
434 # File doesn't exist, so the current state is empty
438 st = b''
435 st = b''
439
436
440 l = len(st)
437 l = len(st)
441 if l == self._nodelen * 2:
438 if l == self._nodelen * 2:
442 self._parents = (
439 self._parents = (
443 st[: self._nodelen],
440 st[: self._nodelen],
444 st[self._nodelen : 2 * self._nodelen],
441 st[self._nodelen : 2 * self._nodelen],
445 )
442 )
446 elif l == 0:
443 elif l == 0:
447 self._parents = (
444 self._parents = (
448 self._nodeconstants.nullid,
445 self._nodeconstants.nullid,
449 self._nodeconstants.nullid,
446 self._nodeconstants.nullid,
450 )
447 )
451 else:
448 else:
452 raise error.Abort(
449 raise error.Abort(
453 _(b'working directory state appears damaged!')
450 _(b'working directory state appears damaged!')
454 )
451 )
455
452
456 return self._parents
453 return self._parents
457
454
458 def setparents(self, p1, p2):
455 def setparents(self, p1, p2):
459 self._parents = (p1, p2)
456 self._parents = (p1, p2)
460 self._dirtyparents = True
457 self._dirtyparents = True
461
458
462 def read(self):
459 def read(self):
463 # ignore HG_PENDING because identity is used only for writing
460 # ignore HG_PENDING because identity is used only for writing
464 self.identity = util.filestat.frompath(
461 self.identity = util.filestat.frompath(
465 self._opener.join(self._filename)
462 self._opener.join(self._filename)
466 )
463 )
467
464
468 try:
465 try:
469 fp = self._opendirstatefile()
466 fp = self._opendirstatefile()
470 try:
467 try:
471 st = fp.read()
468 st = fp.read()
472 finally:
469 finally:
473 fp.close()
470 fp.close()
474 except IOError as err:
471 except IOError as err:
475 if err.errno != errno.ENOENT:
472 if err.errno != errno.ENOENT:
476 raise
473 raise
477 return
474 return
478 if not st:
475 if not st:
479 return
476 return
480
477
481 if util.safehasattr(parsers, b'dict_new_presized'):
478 if util.safehasattr(parsers, b'dict_new_presized'):
482 # Make an estimate of the number of files in the dirstate based on
479 # Make an estimate of the number of files in the dirstate based on
483 # its size. This trades wasting some memory for avoiding costly
480 # its size. This trades wasting some memory for avoiding costly
484 # resizes. Each entry have a prefix of 17 bytes followed by one or
481 # resizes. Each entry have a prefix of 17 bytes followed by one or
485 # two path names. Studies on various large-scale real-world repositories
482 # two path names. Studies on various large-scale real-world repositories
486 # found 54 bytes a reasonable upper limit for the average path names.
483 # found 54 bytes a reasonable upper limit for the average path names.
487 # Copy entries are ignored for the sake of this estimate.
484 # Copy entries are ignored for the sake of this estimate.
488 self._map = parsers.dict_new_presized(len(st) // 71)
485 self._map = parsers.dict_new_presized(len(st) // 71)
489
486
490 # Python's garbage collector triggers a GC each time a certain number
487 # Python's garbage collector triggers a GC each time a certain number
491 # of container objects (the number being defined by
488 # of container objects (the number being defined by
492 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
489 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
493 # for each file in the dirstate. The C version then immediately marks
490 # for each file in the dirstate. The C version then immediately marks
494 # them as not to be tracked by the collector. However, this has no
491 # them as not to be tracked by the collector. However, this has no
495 # effect on when GCs are triggered, only on what objects the GC looks
492 # effect on when GCs are triggered, only on what objects the GC looks
496 # into. This means that O(number of files) GCs are unavoidable.
493 # into. This means that O(number of files) GCs are unavoidable.
497 # Depending on when in the process's lifetime the dirstate is parsed,
494 # Depending on when in the process's lifetime the dirstate is parsed,
498 # this can get very expensive. As a workaround, disable GC while
495 # this can get very expensive. As a workaround, disable GC while
499 # parsing the dirstate.
496 # parsing the dirstate.
500 #
497 #
501 # (we cannot decorate the function directly since it is in a C module)
498 # (we cannot decorate the function directly since it is in a C module)
502 parse_dirstate = util.nogc(parsers.parse_dirstate)
499 parse_dirstate = util.nogc(parsers.parse_dirstate)
503 p = parse_dirstate(self._map, self.copymap, st)
500 p = parse_dirstate(self._map, self.copymap, st)
504 if not self._dirtyparents:
501 if not self._dirtyparents:
505 self.setparents(*p)
502 self.setparents(*p)
506
503
507 # Avoid excess attribute lookups by fast pathing certain checks
504 # Avoid excess attribute lookups by fast pathing certain checks
508 self.__contains__ = self._map.__contains__
505 self.__contains__ = self._map.__contains__
509 self.__getitem__ = self._map.__getitem__
506 self.__getitem__ = self._map.__getitem__
510 self.get = self._map.get
507 self.get = self._map.get
511
508
512 def write(self, _tr, st, now):
509 def write(self, _tr, st, now):
513 st.write(
510 st.write(
514 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
511 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
515 )
512 )
516 st.close()
513 st.close()
517 self._dirtyparents = False
514 self._dirtyparents = False
518 self.nonnormalset, self.otherparentset = self.nonnormalentries()
515 self.nonnormalset, self.otherparentset = self.nonnormalentries()
519
516
520 @propertycache
517 @propertycache
521 def nonnormalset(self):
518 def nonnormalset(self):
522 nonnorm, otherparents = self.nonnormalentries()
519 nonnorm, otherparents = self.nonnormalentries()
523 self.otherparentset = otherparents
520 self.otherparentset = otherparents
524 return nonnorm
521 return nonnorm
525
522
526 @propertycache
523 @propertycache
527 def otherparentset(self):
524 def otherparentset(self):
528 nonnorm, otherparents = self.nonnormalentries()
525 nonnorm, otherparents = self.nonnormalentries()
529 self.nonnormalset = nonnorm
526 self.nonnormalset = nonnorm
530 return otherparents
527 return otherparents
531
528
532 def non_normal_or_other_parent_paths(self):
529 def non_normal_or_other_parent_paths(self):
533 return self.nonnormalset.union(self.otherparentset)
530 return self.nonnormalset.union(self.otherparentset)
534
531
535 @propertycache
532 @propertycache
536 def identity(self):
533 def identity(self):
537 self._map
534 self._map
538 return self.identity
535 return self.identity
539
536
540 @propertycache
537 @propertycache
541 def dirfoldmap(self):
538 def dirfoldmap(self):
542 f = {}
539 f = {}
543 normcase = util.normcase
540 normcase = util.normcase
544 for name in self._dirs:
541 for name in self._dirs:
545 f[normcase(name)] = name
542 f[normcase(name)] = name
546 return f
543 return f
547
544
548
545
549 if rustmod is not None:
546 if rustmod is not None:
550
547
551 class dirstatemap(object):
548 class dirstatemap(object):
552 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
549 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
553 self._use_dirstate_v2 = use_dirstate_v2
550 self._use_dirstate_v2 = use_dirstate_v2
554 self._nodeconstants = nodeconstants
551 self._nodeconstants = nodeconstants
555 self._ui = ui
552 self._ui = ui
556 self._opener = opener
553 self._opener = opener
557 self._root = root
554 self._root = root
558 self._filename = b'dirstate'
555 self._filename = b'dirstate'
559 self._nodelen = 20 # Also update Rust code when changing this!
556 self._nodelen = 20 # Also update Rust code when changing this!
560 self._parents = None
557 self._parents = None
561 self._dirtyparents = False
558 self._dirtyparents = False
562 self._docket = None
559 self._docket = None
563
560
564 # for consistent view between _pl() and _read() invocations
561 # for consistent view between _pl() and _read() invocations
565 self._pendingmode = None
562 self._pendingmode = None
566
563
567 self._use_dirstate_tree = self._ui.configbool(
564 self._use_dirstate_tree = self._ui.configbool(
568 b"experimental",
565 b"experimental",
569 b"dirstate-tree.in-memory",
566 b"dirstate-tree.in-memory",
570 False,
567 False,
571 )
568 )
572
569
573 def addfile(
570 def addfile(
574 self,
571 self,
575 f,
572 f,
576 mode=0,
573 mode=0,
577 size=None,
574 size=None,
578 mtime=None,
575 mtime=None,
579 added=False,
576 added=False,
580 merged=False,
577 merged=False,
581 from_p2=False,
578 from_p2=False,
582 possibly_dirty=False,
579 possibly_dirty=False,
583 ):
580 ):
584 return self._rustmap.addfile(
581 return self._rustmap.addfile(
585 f,
582 f,
586 mode,
583 mode,
587 size,
584 size,
588 mtime,
585 mtime,
589 added,
586 added,
590 merged,
587 merged,
591 from_p2,
588 from_p2,
592 possibly_dirty,
589 possibly_dirty,
593 )
590 )
594
591
595 def reset_state(
592 def reset_state(
596 self,
593 self,
597 filename,
594 filename,
598 wc_tracked,
595 wc_tracked,
599 p1_tracked,
596 p1_tracked,
600 p2_tracked=False,
597 p2_tracked=False,
601 merged=False,
598 merged=False,
602 clean_p1=False,
599 clean_p1=False,
603 clean_p2=False,
600 clean_p2=False,
604 possibly_dirty=False,
601 possibly_dirty=False,
605 parentfiledata=None,
602 parentfiledata=None,
606 ):
603 ):
607 """Set a entry to a given state, disregarding all previous state
604 """Set a entry to a given state, disregarding all previous state
608
605
609 This is to be used by the part of the dirstate API dedicated to
606 This is to be used by the part of the dirstate API dedicated to
610 adjusting the dirstate after a update/merge.
607 adjusting the dirstate after a update/merge.
611
608
612 note: calling this might result to no entry existing at all if the
609 note: calling this might result to no entry existing at all if the
613 dirstate map does not see any point at having one for this file
610 dirstate map does not see any point at having one for this file
614 anymore.
611 anymore.
615 """
612 """
616 if merged and (clean_p1 or clean_p2):
613 if merged and (clean_p1 or clean_p2):
617 msg = (
614 msg = (
618 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
615 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
619 )
616 )
620 raise error.ProgrammingError(msg)
617 raise error.ProgrammingError(msg)
621 # copy information are now outdated
618 # copy information are now outdated
622 # (maybe new information should be in directly passed to this function)
619 # (maybe new information should be in directly passed to this function)
623 self.copymap.pop(filename, None)
620 self.copymap.pop(filename, None)
624
621
625 if not (p1_tracked or p2_tracked or wc_tracked):
622 if not (p1_tracked or p2_tracked or wc_tracked):
626 self.dropfile(filename)
623 self.dropfile(filename)
627 elif merged:
624 elif merged:
628 # XXX might be merged and removed ?
625 # XXX might be merged and removed ?
629 entry = self.get(filename)
626 entry = self.get(filename)
630 if entry is not None and entry.tracked:
627 if entry is not None and entry.tracked:
631 # XXX mostly replicate dirstate.other parent. We should get
628 # XXX mostly replicate dirstate.other parent. We should get
632 # the higher layer to pass us more reliable data where `merged`
629 # the higher layer to pass us more reliable data where `merged`
633 # actually mean merged. Dropping the else clause will show
630 # actually mean merged. Dropping the else clause will show
634 # failure in `test-graft.t`
631 # failure in `test-graft.t`
635 self.addfile(filename, merged=True)
632 self.addfile(filename, merged=True)
636 else:
633 else:
637 self.addfile(filename, from_p2=True)
634 self.addfile(filename, from_p2=True)
638 elif not (p1_tracked or p2_tracked) and wc_tracked:
635 elif not (p1_tracked or p2_tracked) and wc_tracked:
639 self.addfile(
636 self.addfile(
640 filename, added=True, possibly_dirty=possibly_dirty
637 filename, added=True, possibly_dirty=possibly_dirty
641 )
638 )
642 elif (p1_tracked or p2_tracked) and not wc_tracked:
639 elif (p1_tracked or p2_tracked) and not wc_tracked:
643 # XXX might be merged and removed ?
640 # XXX might be merged and removed ?
644 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
641 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
645 self.nonnormalset.add(filename)
642 self.nonnormalset.add(filename)
646 elif clean_p2 and wc_tracked:
643 elif clean_p2 and wc_tracked:
647 if p1_tracked or self.get(filename) is not None:
644 if p1_tracked or self.get(filename) is not None:
648 # XXX the `self.get` call is catching some case in
645 # XXX the `self.get` call is catching some case in
649 # `test-merge-remove.t` where the file is tracked in p1, the
646 # `test-merge-remove.t` where the file is tracked in p1, the
650 # p1_tracked argument is False.
647 # p1_tracked argument is False.
651 #
648 #
652 # In addition, this seems to be a case where the file is marked
649 # In addition, this seems to be a case where the file is marked
653 # as merged without actually being the result of a merge
650 # as merged without actually being the result of a merge
654 # action. So thing are not ideal here.
651 # action. So thing are not ideal here.
655 self.addfile(filename, merged=True)
652 self.addfile(filename, merged=True)
656 else:
653 else:
657 self.addfile(filename, from_p2=True)
654 self.addfile(filename, from_p2=True)
658 elif not p1_tracked and p2_tracked and wc_tracked:
655 elif not p1_tracked and p2_tracked and wc_tracked:
659 self.addfile(
656 self.addfile(
660 filename, from_p2=True, possibly_dirty=possibly_dirty
657 filename, from_p2=True, possibly_dirty=possibly_dirty
661 )
658 )
662 elif possibly_dirty:
659 elif possibly_dirty:
663 self.addfile(filename, possibly_dirty=possibly_dirty)
660 self.addfile(filename, possibly_dirty=possibly_dirty)
664 elif wc_tracked:
661 elif wc_tracked:
665 # this is a "normal" file
662 # this is a "normal" file
666 if parentfiledata is None:
663 if parentfiledata is None:
667 msg = b'failed to pass parentfiledata for a normal file: %s'
664 msg = b'failed to pass parentfiledata for a normal file: %s'
668 msg %= filename
665 msg %= filename
669 raise error.ProgrammingError(msg)
666 raise error.ProgrammingError(msg)
670 mode, size, mtime = parentfiledata
667 mode, size, mtime = parentfiledata
671 self.addfile(filename, mode=mode, size=size, mtime=mtime)
668 self.addfile(filename, mode=mode, size=size, mtime=mtime)
672 self.nonnormalset.discard(filename)
669 self.nonnormalset.discard(filename)
673 else:
670 else:
674 assert False, 'unreachable'
671 assert False, 'unreachable'
675
672
676 def set_untracked(self, f):
673 def set_untracked(self, f):
677 """Mark a file as no longer tracked in the dirstate map"""
674 """Mark a file as no longer tracked in the dirstate map"""
678 # in merge is only trigger more logic, so it "fine" to pass it.
675 # in merge is only trigger more logic, so it "fine" to pass it.
679 #
676 #
680 # the inner rust dirstate map code need to be adjusted once the API
677 # the inner rust dirstate map code need to be adjusted once the API
681 # for dirstate/dirstatemap/DirstateItem is a bit more settled
678 # for dirstate/dirstatemap/DirstateItem is a bit more settled
682 self._rustmap.removefile(f, in_merge=True)
679 self._rustmap.removefile(f, in_merge=True)
683
680
684 def removefile(self, *args, **kwargs):
681 def removefile(self, *args, **kwargs):
685 return self._rustmap.removefile(*args, **kwargs)
682 return self._rustmap.removefile(*args, **kwargs)
686
683
687 def dropfile(self, *args, **kwargs):
684 def dropfile(self, *args, **kwargs):
688 return self._rustmap.dropfile(*args, **kwargs)
685 return self._rustmap.dropfile(*args, **kwargs)
689
686
690 def clearambiguoustimes(self, *args, **kwargs):
687 def clearambiguoustimes(self, *args, **kwargs):
691 return self._rustmap.clearambiguoustimes(*args, **kwargs)
688 return self._rustmap.clearambiguoustimes(*args, **kwargs)
692
689
693 def nonnormalentries(self):
690 def nonnormalentries(self):
694 return self._rustmap.nonnormalentries()
691 return self._rustmap.nonnormalentries()
695
692
696 def get(self, *args, **kwargs):
693 def get(self, *args, **kwargs):
697 return self._rustmap.get(*args, **kwargs)
694 return self._rustmap.get(*args, **kwargs)
698
695
699 @property
696 @property
700 def copymap(self):
697 def copymap(self):
701 return self._rustmap.copymap()
698 return self._rustmap.copymap()
702
699
703 def directories(self):
700 def directories(self):
704 return self._rustmap.directories()
701 return self._rustmap.directories()
705
702
706 def debug_iter(self):
703 def debug_iter(self):
707 return self._rustmap.debug_iter()
704 return self._rustmap.debug_iter()
708
705
709 def preload(self):
706 def preload(self):
710 self._rustmap
707 self._rustmap
711
708
712 def clear(self):
709 def clear(self):
713 self._rustmap.clear()
710 self._rustmap.clear()
714 self.setparents(
711 self.setparents(
715 self._nodeconstants.nullid, self._nodeconstants.nullid
712 self._nodeconstants.nullid, self._nodeconstants.nullid
716 )
713 )
717 util.clearcachedproperty(self, b"_dirs")
714 util.clearcachedproperty(self, b"_dirs")
718 util.clearcachedproperty(self, b"_alldirs")
715 util.clearcachedproperty(self, b"_alldirs")
719 util.clearcachedproperty(self, b"dirfoldmap")
716 util.clearcachedproperty(self, b"dirfoldmap")
720
717
721 def items(self):
718 def items(self):
722 return self._rustmap.items()
719 return self._rustmap.items()
723
720
724 def keys(self):
721 def keys(self):
725 return iter(self._rustmap)
722 return iter(self._rustmap)
726
723
727 def __contains__(self, key):
724 def __contains__(self, key):
728 return key in self._rustmap
725 return key in self._rustmap
729
726
730 def __getitem__(self, item):
727 def __getitem__(self, item):
731 return self._rustmap[item]
728 return self._rustmap[item]
732
729
733 def __len__(self):
730 def __len__(self):
734 return len(self._rustmap)
731 return len(self._rustmap)
735
732
736 def __iter__(self):
733 def __iter__(self):
737 return iter(self._rustmap)
734 return iter(self._rustmap)
738
735
739 # forward for python2,3 compat
736 # forward for python2,3 compat
740 iteritems = items
737 iteritems = items
741
738
742 def _opendirstatefile(self):
739 def _opendirstatefile(self):
743 fp, mode = txnutil.trypending(
740 fp, mode = txnutil.trypending(
744 self._root, self._opener, self._filename
741 self._root, self._opener, self._filename
745 )
742 )
746 if self._pendingmode is not None and self._pendingmode != mode:
743 if self._pendingmode is not None and self._pendingmode != mode:
747 fp.close()
744 fp.close()
748 raise error.Abort(
745 raise error.Abort(
749 _(b'working directory state may be changed parallelly')
746 _(b'working directory state may be changed parallelly')
750 )
747 )
751 self._pendingmode = mode
748 self._pendingmode = mode
752 return fp
749 return fp
753
750
754 def _readdirstatefile(self, size=-1):
751 def _readdirstatefile(self, size=-1):
755 try:
752 try:
756 with self._opendirstatefile() as fp:
753 with self._opendirstatefile() as fp:
757 return fp.read(size)
754 return fp.read(size)
758 except IOError as err:
755 except IOError as err:
759 if err.errno != errno.ENOENT:
756 if err.errno != errno.ENOENT:
760 raise
757 raise
761 # File doesn't exist, so the current state is empty
758 # File doesn't exist, so the current state is empty
762 return b''
759 return b''
763
760
764 def setparents(self, p1, p2):
761 def setparents(self, p1, p2):
765 self._parents = (p1, p2)
762 self._parents = (p1, p2)
766 self._dirtyparents = True
763 self._dirtyparents = True
767
764
768 def parents(self):
765 def parents(self):
769 if not self._parents:
766 if not self._parents:
770 if self._use_dirstate_v2:
767 if self._use_dirstate_v2:
771 self._parents = self.docket.parents
768 self._parents = self.docket.parents
772 else:
769 else:
773 read_len = self._nodelen * 2
770 read_len = self._nodelen * 2
774 st = self._readdirstatefile(read_len)
771 st = self._readdirstatefile(read_len)
775 l = len(st)
772 l = len(st)
776 if l == read_len:
773 if l == read_len:
777 self._parents = (
774 self._parents = (
778 st[: self._nodelen],
775 st[: self._nodelen],
779 st[self._nodelen : 2 * self._nodelen],
776 st[self._nodelen : 2 * self._nodelen],
780 )
777 )
781 elif l == 0:
778 elif l == 0:
782 self._parents = (
779 self._parents = (
783 self._nodeconstants.nullid,
780 self._nodeconstants.nullid,
784 self._nodeconstants.nullid,
781 self._nodeconstants.nullid,
785 )
782 )
786 else:
783 else:
787 raise error.Abort(
784 raise error.Abort(
788 _(b'working directory state appears damaged!')
785 _(b'working directory state appears damaged!')
789 )
786 )
790
787
791 return self._parents
788 return self._parents
792
789
793 @property
790 @property
794 def docket(self):
791 def docket(self):
795 if not self._docket:
792 if not self._docket:
796 if not self._use_dirstate_v2:
793 if not self._use_dirstate_v2:
797 raise error.ProgrammingError(
794 raise error.ProgrammingError(
798 b'dirstate only has a docket in v2 format'
795 b'dirstate only has a docket in v2 format'
799 )
796 )
800 self._docket = docketmod.DirstateDocket.parse(
797 self._docket = docketmod.DirstateDocket.parse(
801 self._readdirstatefile(), self._nodeconstants
798 self._readdirstatefile(), self._nodeconstants
802 )
799 )
803 return self._docket
800 return self._docket
804
801
805 @propertycache
802 @propertycache
806 def _rustmap(self):
803 def _rustmap(self):
807 """
804 """
808 Fills the Dirstatemap when called.
805 Fills the Dirstatemap when called.
809 """
806 """
810 # ignore HG_PENDING because identity is used only for writing
807 # ignore HG_PENDING because identity is used only for writing
811 self.identity = util.filestat.frompath(
808 self.identity = util.filestat.frompath(
812 self._opener.join(self._filename)
809 self._opener.join(self._filename)
813 )
810 )
814
811
815 if self._use_dirstate_v2:
812 if self._use_dirstate_v2:
816 if self.docket.uuid:
813 if self.docket.uuid:
817 # TODO: use mmap when possible
814 # TODO: use mmap when possible
818 data = self._opener.read(self.docket.data_filename())
815 data = self._opener.read(self.docket.data_filename())
819 else:
816 else:
820 data = b''
817 data = b''
821 self._rustmap = rustmod.DirstateMap.new_v2(
818 self._rustmap = rustmod.DirstateMap.new_v2(
822 data, self.docket.data_size, self.docket.tree_metadata
819 data, self.docket.data_size, self.docket.tree_metadata
823 )
820 )
824 parents = self.docket.parents
821 parents = self.docket.parents
825 else:
822 else:
826 self._rustmap, parents = rustmod.DirstateMap.new_v1(
823 self._rustmap, parents = rustmod.DirstateMap.new_v1(
827 self._use_dirstate_tree, self._readdirstatefile()
824 self._use_dirstate_tree, self._readdirstatefile()
828 )
825 )
829
826
830 if parents and not self._dirtyparents:
827 if parents and not self._dirtyparents:
831 self.setparents(*parents)
828 self.setparents(*parents)
832
829
833 self.__contains__ = self._rustmap.__contains__
830 self.__contains__ = self._rustmap.__contains__
834 self.__getitem__ = self._rustmap.__getitem__
831 self.__getitem__ = self._rustmap.__getitem__
835 self.get = self._rustmap.get
832 self.get = self._rustmap.get
836 return self._rustmap
833 return self._rustmap
837
834
838 def write(self, tr, st, now):
835 def write(self, tr, st, now):
839 if not self._use_dirstate_v2:
836 if not self._use_dirstate_v2:
840 p1, p2 = self.parents()
837 p1, p2 = self.parents()
841 packed = self._rustmap.write_v1(p1, p2, now)
838 packed = self._rustmap.write_v1(p1, p2, now)
842 st.write(packed)
839 st.write(packed)
843 st.close()
840 st.close()
844 self._dirtyparents = False
841 self._dirtyparents = False
845 return
842 return
846
843
847 # We can only append to an existing data file if there is one
844 # We can only append to an existing data file if there is one
848 can_append = self.docket.uuid is not None
845 can_append = self.docket.uuid is not None
849 packed, meta, append = self._rustmap.write_v2(now, can_append)
846 packed, meta, append = self._rustmap.write_v2(now, can_append)
850 if append:
847 if append:
851 docket = self.docket
848 docket = self.docket
852 data_filename = docket.data_filename()
849 data_filename = docket.data_filename()
853 if tr:
850 if tr:
854 tr.add(data_filename, docket.data_size)
851 tr.add(data_filename, docket.data_size)
855 with self._opener(data_filename, b'r+b') as fp:
852 with self._opener(data_filename, b'r+b') as fp:
856 fp.seek(docket.data_size)
853 fp.seek(docket.data_size)
857 assert fp.tell() == docket.data_size
854 assert fp.tell() == docket.data_size
858 written = fp.write(packed)
855 written = fp.write(packed)
859 if written is not None: # py2 may return None
856 if written is not None: # py2 may return None
860 assert written == len(packed), (written, len(packed))
857 assert written == len(packed), (written, len(packed))
861 docket.data_size += len(packed)
858 docket.data_size += len(packed)
862 docket.parents = self.parents()
859 docket.parents = self.parents()
863 docket.tree_metadata = meta
860 docket.tree_metadata = meta
864 st.write(docket.serialize())
861 st.write(docket.serialize())
865 st.close()
862 st.close()
866 else:
863 else:
867 old_docket = self.docket
864 old_docket = self.docket
868 new_docket = docketmod.DirstateDocket.with_new_uuid(
865 new_docket = docketmod.DirstateDocket.with_new_uuid(
869 self.parents(), len(packed), meta
866 self.parents(), len(packed), meta
870 )
867 )
871 data_filename = new_docket.data_filename()
868 data_filename = new_docket.data_filename()
872 if tr:
869 if tr:
873 tr.add(data_filename, 0)
870 tr.add(data_filename, 0)
874 self._opener.write(data_filename, packed)
871 self._opener.write(data_filename, packed)
875 # Write the new docket after the new data file has been
872 # Write the new docket after the new data file has been
876 # written. Because `st` was opened with `atomictemp=True`,
873 # written. Because `st` was opened with `atomictemp=True`,
877 # the actual `.hg/dirstate` file is only affected on close.
874 # the actual `.hg/dirstate` file is only affected on close.
878 st.write(new_docket.serialize())
875 st.write(new_docket.serialize())
879 st.close()
876 st.close()
880 # Remove the old data file after the new docket pointing to
877 # Remove the old data file after the new docket pointing to
881 # the new data file was written.
878 # the new data file was written.
882 if old_docket.uuid:
879 if old_docket.uuid:
883 data_filename = old_docket.data_filename()
880 data_filename = old_docket.data_filename()
884 unlink = lambda _tr=None: self._opener.unlink(data_filename)
881 unlink = lambda _tr=None: self._opener.unlink(data_filename)
885 if tr:
882 if tr:
886 category = b"dirstate-v2-clean-" + old_docket.uuid
883 category = b"dirstate-v2-clean-" + old_docket.uuid
887 tr.addpostclose(category, unlink)
884 tr.addpostclose(category, unlink)
888 else:
885 else:
889 unlink()
886 unlink()
890 self._docket = new_docket
887 self._docket = new_docket
891 # Reload from the newly-written file
888 # Reload from the newly-written file
892 util.clearcachedproperty(self, b"_rustmap")
889 util.clearcachedproperty(self, b"_rustmap")
893 self._dirtyparents = False
890 self._dirtyparents = False
894
891
895 @propertycache
892 @propertycache
896 def filefoldmap(self):
893 def filefoldmap(self):
897 """Returns a dictionary mapping normalized case paths to their
894 """Returns a dictionary mapping normalized case paths to their
898 non-normalized versions.
895 non-normalized versions.
899 """
896 """
900 return self._rustmap.filefoldmapasdict()
897 return self._rustmap.filefoldmapasdict()
901
898
902 def hastrackeddir(self, d):
899 def hastrackeddir(self, d):
903 return self._rustmap.hastrackeddir(d)
900 return self._rustmap.hastrackeddir(d)
904
901
905 def hasdir(self, d):
902 def hasdir(self, d):
906 return self._rustmap.hasdir(d)
903 return self._rustmap.hasdir(d)
907
904
908 @propertycache
905 @propertycache
909 def identity(self):
906 def identity(self):
910 self._rustmap
907 self._rustmap
911 return self.identity
908 return self.identity
912
909
913 @property
910 @property
914 def nonnormalset(self):
911 def nonnormalset(self):
915 nonnorm = self._rustmap.non_normal_entries()
912 nonnorm = self._rustmap.non_normal_entries()
916 return nonnorm
913 return nonnorm
917
914
918 @propertycache
915 @propertycache
919 def otherparentset(self):
916 def otherparentset(self):
920 otherparents = self._rustmap.other_parent_entries()
917 otherparents = self._rustmap.other_parent_entries()
921 return otherparents
918 return otherparents
922
919
923 def non_normal_or_other_parent_paths(self):
920 def non_normal_or_other_parent_paths(self):
924 return self._rustmap.non_normal_or_other_parent_paths()
921 return self._rustmap.non_normal_or_other_parent_paths()
925
922
926 @propertycache
923 @propertycache
927 def dirfoldmap(self):
924 def dirfoldmap(self):
928 f = {}
925 f = {}
929 normcase = util.normcase
926 normcase = util.normcase
930 for name in self._rustmap.tracked_dirs():
927 for name in self._rustmap.tracked_dirs():
931 f[normcase(name)] = name
928 f[normcase(name)] = name
932 return f
929 return f
933
930
934 def set_possibly_dirty(self, filename):
931 def set_possibly_dirty(self, filename):
935 """record that the current state of the file on disk is unknown"""
932 """record that the current state of the file on disk is unknown"""
936 entry = self[filename]
933 entry = self[filename]
937 entry.set_possibly_dirty()
934 entry.set_possibly_dirty()
938 self._rustmap.set_v1(filename, entry)
935 self._rustmap.set_v1(filename, entry)
939
936
940 def __setitem__(self, key, value):
937 def __setitem__(self, key, value):
941 assert isinstance(value, DirstateItem)
938 assert isinstance(value, DirstateItem)
942 self._rustmap.set_v1(key, value)
939 self._rustmap.set_v1(key, value)
General Comments 0
You need to be logged in to leave comments. Login now