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