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