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