##// END OF EJS Templates
dirstate-v2: no longer register the data-file during transaction...
marmoute -
r50364:b5c85248 6.2.2 stable
parent child Browse files
Show More
@@ -1,690 +1,688 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 if tr:
119 tr.add(data_filename, 0)
120 self._opener.write(data_filename, packed)
118 self._opener.write(data_filename, packed)
121 # Write the new docket after the new data file has been
119 # Write the new docket after the new data file has been
122 # written. Because `st` was opened with `atomictemp=True`,
120 # written. Because `st` was opened with `atomictemp=True`,
123 # the actual `.hg/dirstate` file is only affected on close.
121 # the actual `.hg/dirstate` file is only affected on close.
124 st.write(new_docket.serialize())
122 st.write(new_docket.serialize())
125 st.close()
123 st.close()
126 # Remove the old data file after the new docket pointing to
124 # Remove the old data file after the new docket pointing to
127 # the new data file was written.
125 # the new data file was written.
128 if old_docket.uuid:
126 if old_docket.uuid:
129 data_filename = old_docket.data_filename()
127 data_filename = old_docket.data_filename()
130 unlink = lambda _tr=None: self._opener.unlink(data_filename)
128 unlink = lambda _tr=None: self._opener.unlink(data_filename)
131 if tr:
129 if tr:
132 category = b"dirstate-v2-clean-" + old_docket.uuid
130 category = b"dirstate-v2-clean-" + old_docket.uuid
133 tr.addpostclose(category, unlink)
131 tr.addpostclose(category, unlink)
134 else:
132 else:
135 unlink()
133 unlink()
136 self._docket = new_docket
134 self._docket = new_docket
137
135
138 ### reading/setting parents
136 ### reading/setting parents
139
137
140 def parents(self):
138 def parents(self):
141 if not self._parents:
139 if not self._parents:
142 if self._use_dirstate_v2:
140 if self._use_dirstate_v2:
143 self._parents = self.docket.parents
141 self._parents = self.docket.parents
144 else:
142 else:
145 read_len = self._nodelen * 2
143 read_len = self._nodelen * 2
146 st = self._readdirstatefile(read_len)
144 st = self._readdirstatefile(read_len)
147 l = len(st)
145 l = len(st)
148 if l == read_len:
146 if l == read_len:
149 self._parents = (
147 self._parents = (
150 st[: self._nodelen],
148 st[: self._nodelen],
151 st[self._nodelen : 2 * self._nodelen],
149 st[self._nodelen : 2 * self._nodelen],
152 )
150 )
153 elif l == 0:
151 elif l == 0:
154 self._parents = (
152 self._parents = (
155 self._nodeconstants.nullid,
153 self._nodeconstants.nullid,
156 self._nodeconstants.nullid,
154 self._nodeconstants.nullid,
157 )
155 )
158 else:
156 else:
159 raise error.Abort(
157 raise error.Abort(
160 _(b'working directory state appears damaged!')
158 _(b'working directory state appears damaged!')
161 )
159 )
162
160
163 return self._parents
161 return self._parents
164
162
165
163
166 class dirstatemap(_dirstatemapcommon):
164 class dirstatemap(_dirstatemapcommon):
167 """Map encapsulating the dirstate's contents.
165 """Map encapsulating the dirstate's contents.
168
166
169 The dirstate contains the following state:
167 The dirstate contains the following state:
170
168
171 - `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
172 detect when changes have occurred to the dirstate file.
170 detect when changes have occurred to the dirstate file.
173
171
174 - `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
175 parents are updated by calling `setparents`.
173 parents are updated by calling `setparents`.
176
174
177 - 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),
178 where state is a single character representing 'normal', 'added',
176 where state is a single character representing 'normal', 'added',
179 '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
180 dict. File state is updated by calling various methods (see each
178 dict. File state is updated by calling various methods (see each
181 documentation for details):
179 documentation for details):
182
180
183 - `reset_state`,
181 - `reset_state`,
184 - `set_tracked`
182 - `set_tracked`
185 - `set_untracked`
183 - `set_untracked`
186 - `set_clean`
184 - `set_clean`
187 - `set_possibly_dirty`
185 - `set_possibly_dirty`
188
186
189 - `copymap` maps destination filenames to their source filename.
187 - `copymap` maps destination filenames to their source filename.
190
188
191 The dirstate also provides the following views onto the state:
189 The dirstate also provides the following views onto the state:
192
190
193 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
191 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
194 form that they appear as in the dirstate.
192 form that they appear as in the dirstate.
195
193
196 - `dirfoldmap` is a dict mapping normalized directory names to the
194 - `dirfoldmap` is a dict mapping normalized directory names to the
197 denormalized form that they appear as in the dirstate.
195 denormalized form that they appear as in the dirstate.
198 """
196 """
199
197
200 ### Core data storage and access
198 ### Core data storage and access
201
199
202 @propertycache
200 @propertycache
203 def _map(self):
201 def _map(self):
204 self._map = {}
202 self._map = {}
205 self.read()
203 self.read()
206 return self._map
204 return self._map
207
205
208 @propertycache
206 @propertycache
209 def copymap(self):
207 def copymap(self):
210 self.copymap = {}
208 self.copymap = {}
211 self._map
209 self._map
212 return self.copymap
210 return self.copymap
213
211
214 def clear(self):
212 def clear(self):
215 self._map.clear()
213 self._map.clear()
216 self.copymap.clear()
214 self.copymap.clear()
217 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
215 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
218 util.clearcachedproperty(self, b"_dirs")
216 util.clearcachedproperty(self, b"_dirs")
219 util.clearcachedproperty(self, b"_alldirs")
217 util.clearcachedproperty(self, b"_alldirs")
220 util.clearcachedproperty(self, b"filefoldmap")
218 util.clearcachedproperty(self, b"filefoldmap")
221 util.clearcachedproperty(self, b"dirfoldmap")
219 util.clearcachedproperty(self, b"dirfoldmap")
222
220
223 def items(self):
221 def items(self):
224 return self._map.items()
222 return self._map.items()
225
223
226 # forward for python2,3 compat
224 # forward for python2,3 compat
227 iteritems = items
225 iteritems = items
228
226
229 def debug_iter(self, all):
227 def debug_iter(self, all):
230 """
228 """
231 Return an iterator of (filename, state, mode, size, mtime) tuples
229 Return an iterator of (filename, state, mode, size, mtime) tuples
232
230
233 `all` is unused when Rust is not enabled
231 `all` is unused when Rust is not enabled
234 """
232 """
235 for (filename, item) in self.items():
233 for (filename, item) in self.items():
236 yield (filename, item.state, item.mode, item.size, item.mtime)
234 yield (filename, item.state, item.mode, item.size, item.mtime)
237
235
238 def keys(self):
236 def keys(self):
239 return self._map.keys()
237 return self._map.keys()
240
238
241 ### reading/setting parents
239 ### reading/setting parents
242
240
243 def setparents(self, p1, p2, fold_p2=False):
241 def setparents(self, p1, p2, fold_p2=False):
244 self._parents = (p1, p2)
242 self._parents = (p1, p2)
245 self._dirtyparents = True
243 self._dirtyparents = True
246 copies = {}
244 copies = {}
247 if fold_p2:
245 if fold_p2:
248 for f, s in self._map.items():
246 for f, s in self._map.items():
249 # Discard "merged" markers when moving away from a merge state
247 # Discard "merged" markers when moving away from a merge state
250 if s.p2_info:
248 if s.p2_info:
251 source = self.copymap.pop(f, None)
249 source = self.copymap.pop(f, None)
252 if source:
250 if source:
253 copies[f] = source
251 copies[f] = source
254 s.drop_merge_data()
252 s.drop_merge_data()
255 return copies
253 return copies
256
254
257 ### disk interaction
255 ### disk interaction
258
256
259 def read(self):
257 def read(self):
260 # ignore HG_PENDING because identity is used only for writing
258 # ignore HG_PENDING because identity is used only for writing
261 self.identity = util.filestat.frompath(
259 self.identity = util.filestat.frompath(
262 self._opener.join(self._filename)
260 self._opener.join(self._filename)
263 )
261 )
264
262
265 if self._use_dirstate_v2:
263 if self._use_dirstate_v2:
266 if not self.docket.uuid:
264 if not self.docket.uuid:
267 return
265 return
268 st = self._opener.read(self.docket.data_filename())
266 st = self._opener.read(self.docket.data_filename())
269 else:
267 else:
270 st = self._readdirstatefile()
268 st = self._readdirstatefile()
271
269
272 if not st:
270 if not st:
273 return
271 return
274
272
275 # TODO: adjust this estimate for dirstate-v2
273 # TODO: adjust this estimate for dirstate-v2
276 if util.safehasattr(parsers, b'dict_new_presized'):
274 if util.safehasattr(parsers, b'dict_new_presized'):
277 # 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
278 # its size. This trades wasting some memory for avoiding costly
276 # its size. This trades wasting some memory for avoiding costly
279 # 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
280 # two path names. Studies on various large-scale real-world repositories
278 # two path names. Studies on various large-scale real-world repositories
281 # 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.
282 # Copy entries are ignored for the sake of this estimate.
280 # Copy entries are ignored for the sake of this estimate.
283 self._map = parsers.dict_new_presized(len(st) // 71)
281 self._map = parsers.dict_new_presized(len(st) // 71)
284
282
285 # 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
286 # of container objects (the number being defined by
284 # of container objects (the number being defined by
287 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
285 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
288 # 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
289 # 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
290 # 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
291 # into. This means that O(number of files) GCs are unavoidable.
289 # into. This means that O(number of files) GCs are unavoidable.
292 # 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,
293 # this can get very expensive. As a workaround, disable GC while
291 # this can get very expensive. As a workaround, disable GC while
294 # parsing the dirstate.
292 # parsing the dirstate.
295 #
293 #
296 # (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)
297 if self._use_dirstate_v2:
295 if self._use_dirstate_v2:
298 p = self.docket.parents
296 p = self.docket.parents
299 meta = self.docket.tree_metadata
297 meta = self.docket.tree_metadata
300 parse_dirstate = util.nogc(v2.parse_dirstate)
298 parse_dirstate = util.nogc(v2.parse_dirstate)
301 parse_dirstate(self._map, self.copymap, st, meta)
299 parse_dirstate(self._map, self.copymap, st, meta)
302 else:
300 else:
303 parse_dirstate = util.nogc(parsers.parse_dirstate)
301 parse_dirstate = util.nogc(parsers.parse_dirstate)
304 p = parse_dirstate(self._map, self.copymap, st)
302 p = parse_dirstate(self._map, self.copymap, st)
305 if not self._dirtyparents:
303 if not self._dirtyparents:
306 self.setparents(*p)
304 self.setparents(*p)
307
305
308 # Avoid excess attribute lookups by fast pathing certain checks
306 # Avoid excess attribute lookups by fast pathing certain checks
309 self.__contains__ = self._map.__contains__
307 self.__contains__ = self._map.__contains__
310 self.__getitem__ = self._map.__getitem__
308 self.__getitem__ = self._map.__getitem__
311 self.get = self._map.get
309 self.get = self._map.get
312
310
313 def write(self, tr, st):
311 def write(self, tr, st):
314 if self._use_dirstate_v2:
312 if self._use_dirstate_v2:
315 packed, meta = v2.pack_dirstate(self._map, self.copymap)
313 packed, meta = v2.pack_dirstate(self._map, self.copymap)
316 self.write_v2_no_append(tr, st, meta, packed)
314 self.write_v2_no_append(tr, st, meta, packed)
317 else:
315 else:
318 packed = parsers.pack_dirstate(
316 packed = parsers.pack_dirstate(
319 self._map, self.copymap, self.parents()
317 self._map, self.copymap, self.parents()
320 )
318 )
321 st.write(packed)
319 st.write(packed)
322 st.close()
320 st.close()
323 self._dirtyparents = False
321 self._dirtyparents = False
324
322
325 @propertycache
323 @propertycache
326 def identity(self):
324 def identity(self):
327 self._map
325 self._map
328 return self.identity
326 return self.identity
329
327
330 ### code related to maintaining and accessing "extra" property
328 ### code related to maintaining and accessing "extra" property
331 # (e.g. "has_dir")
329 # (e.g. "has_dir")
332
330
333 def _dirs_incr(self, filename, old_entry=None):
331 def _dirs_incr(self, filename, old_entry=None):
334 """increment the dirstate counter if applicable"""
332 """increment the dirstate counter if applicable"""
335 if (
333 if (
336 old_entry is None or old_entry.removed
334 old_entry is None or old_entry.removed
337 ) and "_dirs" in self.__dict__:
335 ) and "_dirs" in self.__dict__:
338 self._dirs.addpath(filename)
336 self._dirs.addpath(filename)
339 if old_entry is None and "_alldirs" in self.__dict__:
337 if old_entry is None and "_alldirs" in self.__dict__:
340 self._alldirs.addpath(filename)
338 self._alldirs.addpath(filename)
341
339
342 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
340 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
343 """decrement the dirstate counter if applicable"""
341 """decrement the dirstate counter if applicable"""
344 if old_entry is not None:
342 if old_entry is not None:
345 if "_dirs" in self.__dict__ and not old_entry.removed:
343 if "_dirs" in self.__dict__ and not old_entry.removed:
346 self._dirs.delpath(filename)
344 self._dirs.delpath(filename)
347 if "_alldirs" in self.__dict__ and not remove_variant:
345 if "_alldirs" in self.__dict__ and not remove_variant:
348 self._alldirs.delpath(filename)
346 self._alldirs.delpath(filename)
349 elif remove_variant and "_alldirs" in self.__dict__:
347 elif remove_variant and "_alldirs" in self.__dict__:
350 self._alldirs.addpath(filename)
348 self._alldirs.addpath(filename)
351 if "filefoldmap" in self.__dict__:
349 if "filefoldmap" in self.__dict__:
352 normed = util.normcase(filename)
350 normed = util.normcase(filename)
353 self.filefoldmap.pop(normed, None)
351 self.filefoldmap.pop(normed, None)
354
352
355 @propertycache
353 @propertycache
356 def filefoldmap(self):
354 def filefoldmap(self):
357 """Returns a dictionary mapping normalized case paths to their
355 """Returns a dictionary mapping normalized case paths to their
358 non-normalized versions.
356 non-normalized versions.
359 """
357 """
360 try:
358 try:
361 makefilefoldmap = parsers.make_file_foldmap
359 makefilefoldmap = parsers.make_file_foldmap
362 except AttributeError:
360 except AttributeError:
363 pass
361 pass
364 else:
362 else:
365 return makefilefoldmap(
363 return makefilefoldmap(
366 self._map, util.normcasespec, util.normcasefallback
364 self._map, util.normcasespec, util.normcasefallback
367 )
365 )
368
366
369 f = {}
367 f = {}
370 normcase = util.normcase
368 normcase = util.normcase
371 for name, s in self._map.items():
369 for name, s in self._map.items():
372 if not s.removed:
370 if not s.removed:
373 f[normcase(name)] = name
371 f[normcase(name)] = name
374 f[b'.'] = b'.' # prevents useless util.fspath() invocation
372 f[b'.'] = b'.' # prevents useless util.fspath() invocation
375 return f
373 return f
376
374
377 @propertycache
375 @propertycache
378 def dirfoldmap(self):
376 def dirfoldmap(self):
379 f = {}
377 f = {}
380 normcase = util.normcase
378 normcase = util.normcase
381 for name in self._dirs:
379 for name in self._dirs:
382 f[normcase(name)] = name
380 f[normcase(name)] = name
383 return f
381 return f
384
382
385 def hastrackeddir(self, d):
383 def hastrackeddir(self, d):
386 """
384 """
387 Returns True if the dirstate contains a tracked (not removed) file
385 Returns True if the dirstate contains a tracked (not removed) file
388 in this directory.
386 in this directory.
389 """
387 """
390 return d in self._dirs
388 return d in self._dirs
391
389
392 def hasdir(self, d):
390 def hasdir(self, d):
393 """
391 """
394 Returns True if the dirstate contains a file (tracked or removed)
392 Returns True if the dirstate contains a file (tracked or removed)
395 in this directory.
393 in this directory.
396 """
394 """
397 return d in self._alldirs
395 return d in self._alldirs
398
396
399 @propertycache
397 @propertycache
400 def _dirs(self):
398 def _dirs(self):
401 return pathutil.dirs(self._map, only_tracked=True)
399 return pathutil.dirs(self._map, only_tracked=True)
402
400
403 @propertycache
401 @propertycache
404 def _alldirs(self):
402 def _alldirs(self):
405 return pathutil.dirs(self._map)
403 return pathutil.dirs(self._map)
406
404
407 ### code related to manipulation of entries and copy-sources
405 ### code related to manipulation of entries and copy-sources
408
406
409 def reset_state(
407 def reset_state(
410 self,
408 self,
411 filename,
409 filename,
412 wc_tracked=False,
410 wc_tracked=False,
413 p1_tracked=False,
411 p1_tracked=False,
414 p2_info=False,
412 p2_info=False,
415 has_meaningful_mtime=True,
413 has_meaningful_mtime=True,
416 parentfiledata=None,
414 parentfiledata=None,
417 ):
415 ):
418 """Set a entry to a given state, diregarding all previous state
416 """Set a entry to a given state, diregarding all previous state
419
417
420 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
421 adjusting the dirstate after a update/merge.
419 adjusting the dirstate after a update/merge.
422
420
423 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
424 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
425 anymore.
423 anymore.
426 """
424 """
427 # copy information are now outdated
425 # copy information are now outdated
428 # (maybe new information should be in directly passed to this function)
426 # (maybe new information should be in directly passed to this function)
429 self.copymap.pop(filename, None)
427 self.copymap.pop(filename, None)
430
428
431 if not (p1_tracked or p2_info or wc_tracked):
429 if not (p1_tracked or p2_info or wc_tracked):
432 old_entry = self._map.get(filename)
430 old_entry = self._map.get(filename)
433 self._drop_entry(filename)
431 self._drop_entry(filename)
434 self._dirs_decr(filename, old_entry=old_entry)
432 self._dirs_decr(filename, old_entry=old_entry)
435 return
433 return
436
434
437 old_entry = self._map.get(filename)
435 old_entry = self._map.get(filename)
438 self._dirs_incr(filename, old_entry)
436 self._dirs_incr(filename, old_entry)
439 entry = DirstateItem(
437 entry = DirstateItem(
440 wc_tracked=wc_tracked,
438 wc_tracked=wc_tracked,
441 p1_tracked=p1_tracked,
439 p1_tracked=p1_tracked,
442 p2_info=p2_info,
440 p2_info=p2_info,
443 has_meaningful_mtime=has_meaningful_mtime,
441 has_meaningful_mtime=has_meaningful_mtime,
444 parentfiledata=parentfiledata,
442 parentfiledata=parentfiledata,
445 )
443 )
446 self._map[filename] = entry
444 self._map[filename] = entry
447
445
448 def set_tracked(self, filename):
446 def set_tracked(self, filename):
449 new = False
447 new = False
450 entry = self.get(filename)
448 entry = self.get(filename)
451 if entry is None:
449 if entry is None:
452 self._dirs_incr(filename)
450 self._dirs_incr(filename)
453 entry = DirstateItem(
451 entry = DirstateItem(
454 wc_tracked=True,
452 wc_tracked=True,
455 )
453 )
456
454
457 self._map[filename] = entry
455 self._map[filename] = entry
458 new = True
456 new = True
459 elif not entry.tracked:
457 elif not entry.tracked:
460 self._dirs_incr(filename, entry)
458 self._dirs_incr(filename, entry)
461 entry.set_tracked()
459 entry.set_tracked()
462 self._refresh_entry(filename, entry)
460 self._refresh_entry(filename, entry)
463 new = True
461 new = True
464 else:
462 else:
465 # 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
466 # fully replace the `normallookup` call with `set_tracked` one.
464 # fully replace the `normallookup` call with `set_tracked` one.
467 # Consider smoothing this in the future.
465 # Consider smoothing this in the future.
468 entry.set_possibly_dirty()
466 entry.set_possibly_dirty()
469 self._refresh_entry(filename, entry)
467 self._refresh_entry(filename, entry)
470 return new
468 return new
471
469
472 def set_untracked(self, f):
470 def set_untracked(self, f):
473 """Mark a file as no longer tracked in the dirstate map"""
471 """Mark a file as no longer tracked in the dirstate map"""
474 entry = self.get(f)
472 entry = self.get(f)
475 if entry is None:
473 if entry is None:
476 return False
474 return False
477 else:
475 else:
478 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)
479 if not entry.p2_info:
477 if not entry.p2_info:
480 self.copymap.pop(f, None)
478 self.copymap.pop(f, None)
481 entry.set_untracked()
479 entry.set_untracked()
482 self._refresh_entry(f, entry)
480 self._refresh_entry(f, entry)
483 return True
481 return True
484
482
485 def set_clean(self, filename, mode, size, mtime):
483 def set_clean(self, filename, mode, size, mtime):
486 """mark a file as back to a clean state"""
484 """mark a file as back to a clean state"""
487 entry = self[filename]
485 entry = self[filename]
488 size = size & rangemask
486 size = size & rangemask
489 entry.set_clean(mode, size, mtime)
487 entry.set_clean(mode, size, mtime)
490 self._refresh_entry(filename, entry)
488 self._refresh_entry(filename, entry)
491 self.copymap.pop(filename, None)
489 self.copymap.pop(filename, None)
492
490
493 def set_possibly_dirty(self, filename):
491 def set_possibly_dirty(self, filename):
494 """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"""
495 entry = self[filename]
493 entry = self[filename]
496 entry.set_possibly_dirty()
494 entry.set_possibly_dirty()
497 self._refresh_entry(filename, entry)
495 self._refresh_entry(filename, entry)
498
496
499 def _refresh_entry(self, f, entry):
497 def _refresh_entry(self, f, entry):
500 """record updated state of an entry"""
498 """record updated state of an entry"""
501 if not entry.any_tracked:
499 if not entry.any_tracked:
502 self._map.pop(f, None)
500 self._map.pop(f, None)
503
501
504 def _drop_entry(self, f):
502 def _drop_entry(self, f):
505 """remove any entry for file f
503 """remove any entry for file f
506
504
507 This should also drop associated copy information
505 This should also drop associated copy information
508
506
509 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"""
510 self._map.pop(f, None)
508 self._map.pop(f, None)
511 self.copymap.pop(f, None)
509 self.copymap.pop(f, None)
512
510
513
511
514 if rustmod is not None:
512 if rustmod is not None:
515
513
516 class dirstatemap(_dirstatemapcommon):
514 class dirstatemap(_dirstatemapcommon):
517
515
518 ### Core data storage and access
516 ### Core data storage and access
519
517
520 @propertycache
518 @propertycache
521 def _map(self):
519 def _map(self):
522 """
520 """
523 Fills the Dirstatemap when called.
521 Fills the Dirstatemap when called.
524 """
522 """
525 # ignore HG_PENDING because identity is used only for writing
523 # ignore HG_PENDING because identity is used only for writing
526 self.identity = util.filestat.frompath(
524 self.identity = util.filestat.frompath(
527 self._opener.join(self._filename)
525 self._opener.join(self._filename)
528 )
526 )
529
527
530 if self._use_dirstate_v2:
528 if self._use_dirstate_v2:
531 if self.docket.uuid:
529 if self.docket.uuid:
532 # TODO: use mmap when possible
530 # TODO: use mmap when possible
533 data = self._opener.read(self.docket.data_filename())
531 data = self._opener.read(self.docket.data_filename())
534 else:
532 else:
535 data = b''
533 data = b''
536 self._map = rustmod.DirstateMap.new_v2(
534 self._map = rustmod.DirstateMap.new_v2(
537 data, self.docket.data_size, self.docket.tree_metadata
535 data, self.docket.data_size, self.docket.tree_metadata
538 )
536 )
539 parents = self.docket.parents
537 parents = self.docket.parents
540 else:
538 else:
541 self._map, parents = rustmod.DirstateMap.new_v1(
539 self._map, parents = rustmod.DirstateMap.new_v1(
542 self._readdirstatefile()
540 self._readdirstatefile()
543 )
541 )
544
542
545 if parents and not self._dirtyparents:
543 if parents and not self._dirtyparents:
546 self.setparents(*parents)
544 self.setparents(*parents)
547
545
548 self.__contains__ = self._map.__contains__
546 self.__contains__ = self._map.__contains__
549 self.__getitem__ = self._map.__getitem__
547 self.__getitem__ = self._map.__getitem__
550 self.get = self._map.get
548 self.get = self._map.get
551 return self._map
549 return self._map
552
550
553 @property
551 @property
554 def copymap(self):
552 def copymap(self):
555 return self._map.copymap()
553 return self._map.copymap()
556
554
557 def debug_iter(self, all):
555 def debug_iter(self, all):
558 """
556 """
559 Return an iterator of (filename, state, mode, size, mtime) tuples
557 Return an iterator of (filename, state, mode, size, mtime) tuples
560
558
561 `all`: also include with `state == b' '` dirstate tree nodes that
559 `all`: also include with `state == b' '` dirstate tree nodes that
562 don't have an associated `DirstateItem`.
560 don't have an associated `DirstateItem`.
563
561
564 """
562 """
565 return self._map.debug_iter(all)
563 return self._map.debug_iter(all)
566
564
567 def clear(self):
565 def clear(self):
568 self._map.clear()
566 self._map.clear()
569 self.setparents(
567 self.setparents(
570 self._nodeconstants.nullid, self._nodeconstants.nullid
568 self._nodeconstants.nullid, self._nodeconstants.nullid
571 )
569 )
572 util.clearcachedproperty(self, b"_dirs")
570 util.clearcachedproperty(self, b"_dirs")
573 util.clearcachedproperty(self, b"_alldirs")
571 util.clearcachedproperty(self, b"_alldirs")
574 util.clearcachedproperty(self, b"dirfoldmap")
572 util.clearcachedproperty(self, b"dirfoldmap")
575
573
576 def items(self):
574 def items(self):
577 return self._map.items()
575 return self._map.items()
578
576
579 # forward for python2,3 compat
577 # forward for python2,3 compat
580 iteritems = items
578 iteritems = items
581
579
582 def keys(self):
580 def keys(self):
583 return iter(self._map)
581 return iter(self._map)
584
582
585 ### reading/setting parents
583 ### reading/setting parents
586
584
587 def setparents(self, p1, p2, fold_p2=False):
585 def setparents(self, p1, p2, fold_p2=False):
588 self._parents = (p1, p2)
586 self._parents = (p1, p2)
589 self._dirtyparents = True
587 self._dirtyparents = True
590 copies = {}
588 copies = {}
591 if fold_p2:
589 if fold_p2:
592 copies = self._map.setparents_fixup()
590 copies = self._map.setparents_fixup()
593 return copies
591 return copies
594
592
595 ### disk interaction
593 ### disk interaction
596
594
597 @propertycache
595 @propertycache
598 def identity(self):
596 def identity(self):
599 self._map
597 self._map
600 return self.identity
598 return self.identity
601
599
602 def write(self, tr, st):
600 def write(self, tr, st):
603 if not self._use_dirstate_v2:
601 if not self._use_dirstate_v2:
604 p1, p2 = self.parents()
602 p1, p2 = self.parents()
605 packed = self._map.write_v1(p1, p2)
603 packed = self._map.write_v1(p1, p2)
606 st.write(packed)
604 st.write(packed)
607 st.close()
605 st.close()
608 self._dirtyparents = False
606 self._dirtyparents = False
609 return
607 return
610
608
611 # 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
612 can_append = self.docket.uuid is not None
610 can_append = self.docket.uuid is not None
613 packed, meta, append = self._map.write_v2(can_append)
611 packed, meta, append = self._map.write_v2(can_append)
614 if append:
612 if append:
615 docket = self.docket
613 docket = self.docket
616 data_filename = docket.data_filename()
614 data_filename = docket.data_filename()
617 if tr:
615 if tr:
618 tr.add(data_filename, docket.data_size)
616 tr.add(data_filename, docket.data_size)
619 with self._opener(data_filename, b'r+b') as fp:
617 with self._opener(data_filename, b'r+b') as fp:
620 fp.seek(docket.data_size)
618 fp.seek(docket.data_size)
621 assert fp.tell() == docket.data_size
619 assert fp.tell() == docket.data_size
622 written = fp.write(packed)
620 written = fp.write(packed)
623 if written is not None: # py2 may return None
621 if written is not None: # py2 may return None
624 assert written == len(packed), (written, len(packed))
622 assert written == len(packed), (written, len(packed))
625 docket.data_size += len(packed)
623 docket.data_size += len(packed)
626 docket.parents = self.parents()
624 docket.parents = self.parents()
627 docket.tree_metadata = meta
625 docket.tree_metadata = meta
628 st.write(docket.serialize())
626 st.write(docket.serialize())
629 st.close()
627 st.close()
630 else:
628 else:
631 self.write_v2_no_append(tr, st, meta, packed)
629 self.write_v2_no_append(tr, st, meta, packed)
632 # Reload from the newly-written file
630 # Reload from the newly-written file
633 util.clearcachedproperty(self, b"_map")
631 util.clearcachedproperty(self, b"_map")
634 self._dirtyparents = False
632 self._dirtyparents = False
635
633
636 ### code related to maintaining and accessing "extra" property
634 ### code related to maintaining and accessing "extra" property
637 # (e.g. "has_dir")
635 # (e.g. "has_dir")
638
636
639 @propertycache
637 @propertycache
640 def filefoldmap(self):
638 def filefoldmap(self):
641 """Returns a dictionary mapping normalized case paths to their
639 """Returns a dictionary mapping normalized case paths to their
642 non-normalized versions.
640 non-normalized versions.
643 """
641 """
644 return self._map.filefoldmapasdict()
642 return self._map.filefoldmapasdict()
645
643
646 def hastrackeddir(self, d):
644 def hastrackeddir(self, d):
647 return self._map.hastrackeddir(d)
645 return self._map.hastrackeddir(d)
648
646
649 def hasdir(self, d):
647 def hasdir(self, d):
650 return self._map.hasdir(d)
648 return self._map.hasdir(d)
651
649
652 @propertycache
650 @propertycache
653 def dirfoldmap(self):
651 def dirfoldmap(self):
654 f = {}
652 f = {}
655 normcase = util.normcase
653 normcase = util.normcase
656 for name in self._map.tracked_dirs():
654 for name in self._map.tracked_dirs():
657 f[normcase(name)] = name
655 f[normcase(name)] = name
658 return f
656 return f
659
657
660 ### code related to manipulation of entries and copy-sources
658 ### code related to manipulation of entries and copy-sources
661
659
662 def set_tracked(self, f):
660 def set_tracked(self, f):
663 return self._map.set_tracked(f)
661 return self._map.set_tracked(f)
664
662
665 def set_untracked(self, f):
663 def set_untracked(self, f):
666 return self._map.set_untracked(f)
664 return self._map.set_untracked(f)
667
665
668 def set_clean(self, filename, mode, size, mtime):
666 def set_clean(self, filename, mode, size, mtime):
669 self._map.set_clean(filename, mode, size, mtime)
667 self._map.set_clean(filename, mode, size, mtime)
670
668
671 def set_possibly_dirty(self, f):
669 def set_possibly_dirty(self, f):
672 self._map.set_possibly_dirty(f)
670 self._map.set_possibly_dirty(f)
673
671
674 def reset_state(
672 def reset_state(
675 self,
673 self,
676 filename,
674 filename,
677 wc_tracked=False,
675 wc_tracked=False,
678 p1_tracked=False,
676 p1_tracked=False,
679 p2_info=False,
677 p2_info=False,
680 has_meaningful_mtime=True,
678 has_meaningful_mtime=True,
681 parentfiledata=None,
679 parentfiledata=None,
682 ):
680 ):
683 return self._map.reset_state(
681 return self._map.reset_state(
684 filename,
682 filename,
685 wc_tracked,
683 wc_tracked,
686 p1_tracked,
684 p1_tracked,
687 p2_info,
685 p2_info,
688 has_meaningful_mtime,
686 has_meaningful_mtime,
689 parentfiledata,
687 parentfiledata,
690 )
688 )
General Comments 0
You need to be logged in to leave comments. Login now