##// END OF EJS Templates
dirstatemap: add a common `_drop_entry` method for dirstatemap...
marmoute -
r48944:c057d7c9 default
parent child Browse files
Show More
@@ -1,925 +1,939 b''
1 # dirstatemap.py
1 # dirstatemap.py
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 import errno
8 import errno
9
9
10 from .i18n import _
10 from .i18n import _
11
11
12 from . import (
12 from . import (
13 error,
13 error,
14 pathutil,
14 pathutil,
15 policy,
15 policy,
16 pycompat,
16 pycompat,
17 txnutil,
17 txnutil,
18 util,
18 util,
19 )
19 )
20
20
21 from .dirstateutils import (
21 from .dirstateutils import (
22 docket as docketmod,
22 docket as docketmod,
23 )
23 )
24
24
25 parsers = policy.importmod('parsers')
25 parsers = policy.importmod('parsers')
26 rustmod = policy.importrust('dirstate')
26 rustmod = policy.importrust('dirstate')
27
27
28 propertycache = util.propertycache
28 propertycache = util.propertycache
29
29
30 if rustmod is None:
30 if rustmod is None:
31 DirstateItem = parsers.DirstateItem
31 DirstateItem = parsers.DirstateItem
32 else:
32 else:
33 DirstateItem = rustmod.DirstateItem
33 DirstateItem = rustmod.DirstateItem
34
34
35 rangemask = 0x7FFFFFFF
35 rangemask = 0x7FFFFFFF
36
36
37
37
38 class _dirstatemapcommon(object):
38 class _dirstatemapcommon(object):
39 """
39 """
40 Methods that are identical for both implementations of the dirstatemap
40 Methods that are identical for both implementations of the dirstatemap
41 class, with and without Rust extensions enabled.
41 class, with and without Rust extensions enabled.
42 """
42 """
43
43
44 # please pytype
44 # please pytype
45
45
46 _map = None
46 _map = None
47 copymap = None
47 copymap = None
48
48
49 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
49 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
50 self._use_dirstate_v2 = use_dirstate_v2
50 self._use_dirstate_v2 = use_dirstate_v2
51 self._nodeconstants = nodeconstants
51 self._nodeconstants = nodeconstants
52 self._ui = ui
52 self._ui = ui
53 self._opener = opener
53 self._opener = opener
54 self._root = root
54 self._root = root
55 self._filename = b'dirstate'
55 self._filename = b'dirstate'
56 self._nodelen = 20 # Also update Rust code when changing this!
56 self._nodelen = 20 # Also update Rust code when changing this!
57 self._parents = None
57 self._parents = None
58 self._dirtyparents = False
58 self._dirtyparents = False
59
59
60 # for consistent view between _pl() and _read() invocations
60 # for consistent view between _pl() and _read() invocations
61 self._pendingmode = None
61 self._pendingmode = None
62
62
63 def preload(self):
63 def preload(self):
64 """Loads the underlying data, if it's not already loaded"""
64 """Loads the underlying data, if it's not already loaded"""
65 self._map
65 self._map
66
66
67 def get(self, key, default=None):
67 def get(self, key, default=None):
68 return self._map.get(key, default)
68 return self._map.get(key, default)
69
69
70 def __len__(self):
70 def __len__(self):
71 return len(self._map)
71 return len(self._map)
72
72
73 def __iter__(self):
73 def __iter__(self):
74 return iter(self._map)
74 return iter(self._map)
75
75
76 def __contains__(self, key):
76 def __contains__(self, key):
77 return key in self._map
77 return key in self._map
78
78
79 def __getitem__(self, item):
79 def __getitem__(self, item):
80 return self._map[item]
80 return self._map[item]
81
81
82 ### sub-class utility method
82 ### sub-class utility method
83 #
83 #
84 # Use to allow for generic implementation of some method while still coping
84 # Use to allow for generic implementation of some method while still coping
85 # with minor difference between implementation.
85 # with minor difference between implementation.
86
86
87 def _dirs_incr(self, filename, old_entry=None):
87 def _dirs_incr(self, filename, old_entry=None):
88 """incremente the dirstate counter if applicable
88 """incremente the dirstate counter if applicable
89
89
90 This might be a no-op for some subclass who deal with directory
90 This might be a no-op for some subclass who deal with directory
91 tracking in a different way.
91 tracking in a different way.
92 """
92 """
93
93
94 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
94 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
95 """decremente the dirstate counter if applicable
95 """decremente the dirstate counter if applicable
96
96
97 This might be a no-op for some subclass who deal with directory
97 This might be a no-op for some subclass who deal with directory
98 tracking in a different way.
98 tracking in a different way.
99 """
99 """
100
100
101 def _refresh_entry(self, f, entry):
101 def _refresh_entry(self, f, entry):
102 """record updated state of an entry"""
102 """record updated state of an entry"""
103
103
104 def _insert_entry(self, f, entry):
104 def _insert_entry(self, f, entry):
105 """add a new dirstate entry (or replace an unrelated one)
105 """add a new dirstate entry (or replace an unrelated one)
106
106
107 The fact it is actually new is the responsability of the caller
107 The fact it is actually new is the responsability of the caller
108 """
108 """
109
109
110 def _drop_entry(self, f):
111 """remove any entry for file f
112
113 This should also drop associated copy information
114
115 The fact we actually need to drop it is the responsability of the caller"""
116
110 ### method to manipulate the entries
117 ### method to manipulate the entries
111
118
112 def set_possibly_dirty(self, filename):
119 def set_possibly_dirty(self, filename):
113 """record that the current state of the file on disk is unknown"""
120 """record that the current state of the file on disk is unknown"""
114 entry = self[filename]
121 entry = self[filename]
115 entry.set_possibly_dirty()
122 entry.set_possibly_dirty()
116 self._refresh_entry(filename, entry)
123 self._refresh_entry(filename, entry)
117
124
118 def set_clean(self, filename, mode, size, mtime):
125 def set_clean(self, filename, mode, size, mtime):
119 """mark a file as back to a clean state"""
126 """mark a file as back to a clean state"""
120 entry = self[filename]
127 entry = self[filename]
121 mtime = mtime & rangemask
128 mtime = mtime & rangemask
122 size = size & rangemask
129 size = size & rangemask
123 entry.set_clean(mode, size, mtime)
130 entry.set_clean(mode, size, mtime)
124 self._refresh_entry(filename, entry)
131 self._refresh_entry(filename, entry)
125 self.copymap.pop(filename, None)
132 self.copymap.pop(filename, None)
126
133
127 def set_tracked(self, filename):
134 def set_tracked(self, filename):
128 new = False
135 new = False
129 entry = self.get(filename)
136 entry = self.get(filename)
130 if entry is None:
137 if entry is None:
131 self._dirs_incr(filename)
138 self._dirs_incr(filename)
132 entry = DirstateItem(
139 entry = DirstateItem(
133 p1_tracked=False,
140 p1_tracked=False,
134 p2_tracked=False,
141 p2_tracked=False,
135 wc_tracked=True,
142 wc_tracked=True,
136 merged=False,
143 merged=False,
137 clean_p1=False,
144 clean_p1=False,
138 clean_p2=False,
145 clean_p2=False,
139 possibly_dirty=False,
146 possibly_dirty=False,
140 parentfiledata=None,
147 parentfiledata=None,
141 )
148 )
142
149
143 self._insert_entry(filename, entry)
150 self._insert_entry(filename, entry)
144 new = True
151 new = True
145 elif not entry.tracked:
152 elif not entry.tracked:
146 self._dirs_incr(filename, entry)
153 self._dirs_incr(filename, entry)
147 entry.set_tracked()
154 entry.set_tracked()
148 self._refresh_entry(filename, entry)
155 self._refresh_entry(filename, entry)
149 new = True
156 new = True
150 else:
157 else:
151 # XXX This is probably overkill for more case, but we need this to
158 # XXX This is probably overkill for more case, but we need this to
152 # fully replace the `normallookup` call with `set_tracked` one.
159 # fully replace the `normallookup` call with `set_tracked` one.
153 # Consider smoothing this in the future.
160 # Consider smoothing this in the future.
154 entry.set_possibly_dirty()
161 entry.set_possibly_dirty()
155 self._refresh_entry(filename, entry)
162 self._refresh_entry(filename, entry)
156 return new
163 return new
157
164
158 def set_untracked(self, f):
165 def set_untracked(self, f):
159 """Mark a file as no longer tracked in the dirstate map"""
166 """Mark a file as no longer tracked in the dirstate map"""
160 entry = self.get(f)
167 entry = self.get(f)
161 if entry is None:
168 if entry is None:
162 return False
169 return False
163 else:
170 else:
164 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
171 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
165 if not entry.merged:
172 if not entry.merged:
166 self.copymap.pop(f, None)
173 self.copymap.pop(f, None)
167 entry.set_untracked()
174 entry.set_untracked()
168 self._refresh_entry(f, entry)
175 self._refresh_entry(f, entry)
169 return True
176 return True
170
177
171
178
172 class dirstatemap(_dirstatemapcommon):
179 class dirstatemap(_dirstatemapcommon):
173 """Map encapsulating the dirstate's contents.
180 """Map encapsulating the dirstate's contents.
174
181
175 The dirstate contains the following state:
182 The dirstate contains the following state:
176
183
177 - `identity` is the identity of the dirstate file, which can be used to
184 - `identity` is the identity of the dirstate file, which can be used to
178 detect when changes have occurred to the dirstate file.
185 detect when changes have occurred to the dirstate file.
179
186
180 - `parents` is a pair containing the parents of the working copy. The
187 - `parents` is a pair containing the parents of the working copy. The
181 parents are updated by calling `setparents`.
188 parents are updated by calling `setparents`.
182
189
183 - the state map maps filenames to tuples of (state, mode, size, mtime),
190 - the state map maps filenames to tuples of (state, mode, size, mtime),
184 where state is a single character representing 'normal', 'added',
191 where state is a single character representing 'normal', 'added',
185 'removed', or 'merged'. It is read by treating the dirstate as a
192 'removed', or 'merged'. It is read by treating the dirstate as a
186 dict. File state is updated by calling various methods (see each
193 dict. File state is updated by calling various methods (see each
187 documentation for details):
194 documentation for details):
188
195
189 - `reset_state`,
196 - `reset_state`,
190 - `set_tracked`
197 - `set_tracked`
191 - `set_untracked`
198 - `set_untracked`
192 - `set_clean`
199 - `set_clean`
193 - `set_possibly_dirty`
200 - `set_possibly_dirty`
194
201
195 - `copymap` maps destination filenames to their source filename.
202 - `copymap` maps destination filenames to their source filename.
196
203
197 The dirstate also provides the following views onto the state:
204 The dirstate also provides the following views onto the state:
198
205
199 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
206 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
200 form that they appear as in the dirstate.
207 form that they appear as in the dirstate.
201
208
202 - `dirfoldmap` is a dict mapping normalized directory names to the
209 - `dirfoldmap` is a dict mapping normalized directory names to the
203 denormalized form that they appear as in the dirstate.
210 denormalized form that they appear as in the dirstate.
204 """
211 """
205
212
206 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
213 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
207 super(dirstatemap, self).__init__(
214 super(dirstatemap, self).__init__(
208 ui, opener, root, nodeconstants, use_dirstate_v2
215 ui, opener, root, nodeconstants, use_dirstate_v2
209 )
216 )
210 if self._use_dirstate_v2:
217 if self._use_dirstate_v2:
211 msg = "Dirstate V2 not supportedi"
218 msg = "Dirstate V2 not supportedi"
212 msg += "(should have detected unsupported requirement)"
219 msg += "(should have detected unsupported requirement)"
213 raise error.ProgrammingError(msg)
220 raise error.ProgrammingError(msg)
214
221
215 ### Core data storage and access
222 ### Core data storage and access
216
223
217 @propertycache
224 @propertycache
218 def _map(self):
225 def _map(self):
219 self._map = {}
226 self._map = {}
220 self.read()
227 self.read()
221 return self._map
228 return self._map
222
229
223 @propertycache
230 @propertycache
224 def copymap(self):
231 def copymap(self):
225 self.copymap = {}
232 self.copymap = {}
226 self._map
233 self._map
227 return self.copymap
234 return self.copymap
228
235
229 def clear(self):
236 def clear(self):
230 self._map.clear()
237 self._map.clear()
231 self.copymap.clear()
238 self.copymap.clear()
232 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
239 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
233 util.clearcachedproperty(self, b"_dirs")
240 util.clearcachedproperty(self, b"_dirs")
234 util.clearcachedproperty(self, b"_alldirs")
241 util.clearcachedproperty(self, b"_alldirs")
235 util.clearcachedproperty(self, b"filefoldmap")
242 util.clearcachedproperty(self, b"filefoldmap")
236 util.clearcachedproperty(self, b"dirfoldmap")
243 util.clearcachedproperty(self, b"dirfoldmap")
237
244
238 def items(self):
245 def items(self):
239 return pycompat.iteritems(self._map)
246 return pycompat.iteritems(self._map)
240
247
241 # forward for python2,3 compat
248 # forward for python2,3 compat
242 iteritems = items
249 iteritems = items
243
250
244 def debug_iter(self, all):
251 def debug_iter(self, all):
245 """
252 """
246 Return an iterator of (filename, state, mode, size, mtime) tuples
253 Return an iterator of (filename, state, mode, size, mtime) tuples
247
254
248 `all` is unused when Rust is not enabled
255 `all` is unused when Rust is not enabled
249 """
256 """
250 for (filename, item) in self.items():
257 for (filename, item) in self.items():
251 yield (filename, item.state, item.mode, item.size, item.mtime)
258 yield (filename, item.state, item.mode, item.size, item.mtime)
252
259
253 def keys(self):
260 def keys(self):
254 return self._map.keys()
261 return self._map.keys()
255
262
256 ### reading/setting parents
263 ### reading/setting parents
257
264
258 def parents(self):
265 def parents(self):
259 if not self._parents:
266 if not self._parents:
260 try:
267 try:
261 fp = self._opendirstatefile()
268 fp = self._opendirstatefile()
262 st = fp.read(2 * self._nodelen)
269 st = fp.read(2 * self._nodelen)
263 fp.close()
270 fp.close()
264 except IOError as err:
271 except IOError as err:
265 if err.errno != errno.ENOENT:
272 if err.errno != errno.ENOENT:
266 raise
273 raise
267 # File doesn't exist, so the current state is empty
274 # File doesn't exist, so the current state is empty
268 st = b''
275 st = b''
269
276
270 l = len(st)
277 l = len(st)
271 if l == self._nodelen * 2:
278 if l == self._nodelen * 2:
272 self._parents = (
279 self._parents = (
273 st[: self._nodelen],
280 st[: self._nodelen],
274 st[self._nodelen : 2 * self._nodelen],
281 st[self._nodelen : 2 * self._nodelen],
275 )
282 )
276 elif l == 0:
283 elif l == 0:
277 self._parents = (
284 self._parents = (
278 self._nodeconstants.nullid,
285 self._nodeconstants.nullid,
279 self._nodeconstants.nullid,
286 self._nodeconstants.nullid,
280 )
287 )
281 else:
288 else:
282 raise error.Abort(
289 raise error.Abort(
283 _(b'working directory state appears damaged!')
290 _(b'working directory state appears damaged!')
284 )
291 )
285
292
286 return self._parents
293 return self._parents
287
294
288 def setparents(self, p1, p2, fold_p2=False):
295 def setparents(self, p1, p2, fold_p2=False):
289 self._parents = (p1, p2)
296 self._parents = (p1, p2)
290 self._dirtyparents = True
297 self._dirtyparents = True
291 copies = {}
298 copies = {}
292 if fold_p2:
299 if fold_p2:
293 for f, s in pycompat.iteritems(self._map):
300 for f, s in pycompat.iteritems(self._map):
294 # Discard "merged" markers when moving away from a merge state
301 # Discard "merged" markers when moving away from a merge state
295 if s.merged or s.from_p2:
302 if s.merged or s.from_p2:
296 source = self.copymap.pop(f, None)
303 source = self.copymap.pop(f, None)
297 if source:
304 if source:
298 copies[f] = source
305 copies[f] = source
299 s.drop_merge_data()
306 s.drop_merge_data()
300 return copies
307 return copies
301
308
302 ### disk interaction
309 ### disk interaction
303
310
304 def read(self):
311 def read(self):
305 # ignore HG_PENDING because identity is used only for writing
312 # ignore HG_PENDING because identity is used only for writing
306 self.identity = util.filestat.frompath(
313 self.identity = util.filestat.frompath(
307 self._opener.join(self._filename)
314 self._opener.join(self._filename)
308 )
315 )
309
316
310 try:
317 try:
311 fp = self._opendirstatefile()
318 fp = self._opendirstatefile()
312 try:
319 try:
313 st = fp.read()
320 st = fp.read()
314 finally:
321 finally:
315 fp.close()
322 fp.close()
316 except IOError as err:
323 except IOError as err:
317 if err.errno != errno.ENOENT:
324 if err.errno != errno.ENOENT:
318 raise
325 raise
319 return
326 return
320 if not st:
327 if not st:
321 return
328 return
322
329
323 if util.safehasattr(parsers, b'dict_new_presized'):
330 if util.safehasattr(parsers, b'dict_new_presized'):
324 # Make an estimate of the number of files in the dirstate based on
331 # Make an estimate of the number of files in the dirstate based on
325 # its size. This trades wasting some memory for avoiding costly
332 # its size. This trades wasting some memory for avoiding costly
326 # resizes. Each entry have a prefix of 17 bytes followed by one or
333 # resizes. Each entry have a prefix of 17 bytes followed by one or
327 # two path names. Studies on various large-scale real-world repositories
334 # two path names. Studies on various large-scale real-world repositories
328 # found 54 bytes a reasonable upper limit for the average path names.
335 # found 54 bytes a reasonable upper limit for the average path names.
329 # Copy entries are ignored for the sake of this estimate.
336 # Copy entries are ignored for the sake of this estimate.
330 self._map = parsers.dict_new_presized(len(st) // 71)
337 self._map = parsers.dict_new_presized(len(st) // 71)
331
338
332 # Python's garbage collector triggers a GC each time a certain number
339 # Python's garbage collector triggers a GC each time a certain number
333 # of container objects (the number being defined by
340 # of container objects (the number being defined by
334 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
341 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
335 # for each file in the dirstate. The C version then immediately marks
342 # for each file in the dirstate. The C version then immediately marks
336 # them as not to be tracked by the collector. However, this has no
343 # them as not to be tracked by the collector. However, this has no
337 # effect on when GCs are triggered, only on what objects the GC looks
344 # effect on when GCs are triggered, only on what objects the GC looks
338 # into. This means that O(number of files) GCs are unavoidable.
345 # into. This means that O(number of files) GCs are unavoidable.
339 # Depending on when in the process's lifetime the dirstate is parsed,
346 # Depending on when in the process's lifetime the dirstate is parsed,
340 # this can get very expensive. As a workaround, disable GC while
347 # this can get very expensive. As a workaround, disable GC while
341 # parsing the dirstate.
348 # parsing the dirstate.
342 #
349 #
343 # (we cannot decorate the function directly since it is in a C module)
350 # (we cannot decorate the function directly since it is in a C module)
344 parse_dirstate = util.nogc(parsers.parse_dirstate)
351 parse_dirstate = util.nogc(parsers.parse_dirstate)
345 p = parse_dirstate(self._map, self.copymap, st)
352 p = parse_dirstate(self._map, self.copymap, st)
346 if not self._dirtyparents:
353 if not self._dirtyparents:
347 self.setparents(*p)
354 self.setparents(*p)
348
355
349 # Avoid excess attribute lookups by fast pathing certain checks
356 # Avoid excess attribute lookups by fast pathing certain checks
350 self.__contains__ = self._map.__contains__
357 self.__contains__ = self._map.__contains__
351 self.__getitem__ = self._map.__getitem__
358 self.__getitem__ = self._map.__getitem__
352 self.get = self._map.get
359 self.get = self._map.get
353
360
354 def write(self, _tr, st, now):
361 def write(self, _tr, st, now):
355 d = parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
362 d = parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
356 st.write(d)
363 st.write(d)
357 st.close()
364 st.close()
358 self._dirtyparents = False
365 self._dirtyparents = False
359
366
360 def _opendirstatefile(self):
367 def _opendirstatefile(self):
361 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
368 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
362 if self._pendingmode is not None and self._pendingmode != mode:
369 if self._pendingmode is not None and self._pendingmode != mode:
363 fp.close()
370 fp.close()
364 raise error.Abort(
371 raise error.Abort(
365 _(b'working directory state may be changed parallelly')
372 _(b'working directory state may be changed parallelly')
366 )
373 )
367 self._pendingmode = mode
374 self._pendingmode = mode
368 return fp
375 return fp
369
376
370 @propertycache
377 @propertycache
371 def identity(self):
378 def identity(self):
372 self._map
379 self._map
373 return self.identity
380 return self.identity
374
381
375 ### code related to maintaining and accessing "extra" property
382 ### code related to maintaining and accessing "extra" property
376 # (e.g. "has_dir")
383 # (e.g. "has_dir")
377
384
378 def _dirs_incr(self, filename, old_entry=None):
385 def _dirs_incr(self, filename, old_entry=None):
379 """incremente the dirstate counter if applicable"""
386 """incremente the dirstate counter if applicable"""
380 if (
387 if (
381 old_entry is None or old_entry.removed
388 old_entry is None or old_entry.removed
382 ) and "_dirs" in self.__dict__:
389 ) and "_dirs" in self.__dict__:
383 self._dirs.addpath(filename)
390 self._dirs.addpath(filename)
384 if old_entry is None and "_alldirs" in self.__dict__:
391 if old_entry is None and "_alldirs" in self.__dict__:
385 self._alldirs.addpath(filename)
392 self._alldirs.addpath(filename)
386
393
387 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
394 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
388 """decremente the dirstate counter if applicable"""
395 """decremente the dirstate counter if applicable"""
389 if old_entry is not None:
396 if old_entry is not None:
390 if "_dirs" in self.__dict__ and not old_entry.removed:
397 if "_dirs" in self.__dict__ and not old_entry.removed:
391 self._dirs.delpath(filename)
398 self._dirs.delpath(filename)
392 if "_alldirs" in self.__dict__ and not remove_variant:
399 if "_alldirs" in self.__dict__ and not remove_variant:
393 self._alldirs.delpath(filename)
400 self._alldirs.delpath(filename)
394 elif remove_variant and "_alldirs" in self.__dict__:
401 elif remove_variant and "_alldirs" in self.__dict__:
395 self._alldirs.addpath(filename)
402 self._alldirs.addpath(filename)
396 if "filefoldmap" in self.__dict__:
403 if "filefoldmap" in self.__dict__:
397 normed = util.normcase(filename)
404 normed = util.normcase(filename)
398 self.filefoldmap.pop(normed, None)
405 self.filefoldmap.pop(normed, None)
399
406
400 @propertycache
407 @propertycache
401 def filefoldmap(self):
408 def filefoldmap(self):
402 """Returns a dictionary mapping normalized case paths to their
409 """Returns a dictionary mapping normalized case paths to their
403 non-normalized versions.
410 non-normalized versions.
404 """
411 """
405 try:
412 try:
406 makefilefoldmap = parsers.make_file_foldmap
413 makefilefoldmap = parsers.make_file_foldmap
407 except AttributeError:
414 except AttributeError:
408 pass
415 pass
409 else:
416 else:
410 return makefilefoldmap(
417 return makefilefoldmap(
411 self._map, util.normcasespec, util.normcasefallback
418 self._map, util.normcasespec, util.normcasefallback
412 )
419 )
413
420
414 f = {}
421 f = {}
415 normcase = util.normcase
422 normcase = util.normcase
416 for name, s in pycompat.iteritems(self._map):
423 for name, s in pycompat.iteritems(self._map):
417 if not s.removed:
424 if not s.removed:
418 f[normcase(name)] = name
425 f[normcase(name)] = name
419 f[b'.'] = b'.' # prevents useless util.fspath() invocation
426 f[b'.'] = b'.' # prevents useless util.fspath() invocation
420 return f
427 return f
421
428
422 @propertycache
429 @propertycache
423 def dirfoldmap(self):
430 def dirfoldmap(self):
424 f = {}
431 f = {}
425 normcase = util.normcase
432 normcase = util.normcase
426 for name in self._dirs:
433 for name in self._dirs:
427 f[normcase(name)] = name
434 f[normcase(name)] = name
428 return f
435 return f
429
436
430 def hastrackeddir(self, d):
437 def hastrackeddir(self, d):
431 """
438 """
432 Returns True if the dirstate contains a tracked (not removed) file
439 Returns True if the dirstate contains a tracked (not removed) file
433 in this directory.
440 in this directory.
434 """
441 """
435 return d in self._dirs
442 return d in self._dirs
436
443
437 def hasdir(self, d):
444 def hasdir(self, d):
438 """
445 """
439 Returns True if the dirstate contains a file (tracked or removed)
446 Returns True if the dirstate contains a file (tracked or removed)
440 in this directory.
447 in this directory.
441 """
448 """
442 return d in self._alldirs
449 return d in self._alldirs
443
450
444 @propertycache
451 @propertycache
445 def _dirs(self):
452 def _dirs(self):
446 return pathutil.dirs(self._map, only_tracked=True)
453 return pathutil.dirs(self._map, only_tracked=True)
447
454
448 @propertycache
455 @propertycache
449 def _alldirs(self):
456 def _alldirs(self):
450 return pathutil.dirs(self._map)
457 return pathutil.dirs(self._map)
451
458
452 ### code related to manipulation of entries and copy-sources
459 ### code related to manipulation of entries and copy-sources
453
460
454 def _refresh_entry(self, f, entry):
461 def _refresh_entry(self, f, entry):
455 if not entry.any_tracked:
462 if not entry.any_tracked:
456 self._map.pop(f, None)
463 self._map.pop(f, None)
457
464
458 def _insert_entry(self, f, entry):
465 def _insert_entry(self, f, entry):
459 self._map[f] = entry
466 self._map[f] = entry
460
467
461 def reset_state(
468 def reset_state(
462 self,
469 self,
463 filename,
470 filename,
464 wc_tracked=False,
471 wc_tracked=False,
465 p1_tracked=False,
472 p1_tracked=False,
466 p2_tracked=False,
473 p2_tracked=False,
467 merged=False,
474 merged=False,
468 clean_p1=False,
475 clean_p1=False,
469 clean_p2=False,
476 clean_p2=False,
470 possibly_dirty=False,
477 possibly_dirty=False,
471 parentfiledata=None,
478 parentfiledata=None,
472 ):
479 ):
473 """Set a entry to a given state, diregarding all previous state
480 """Set a entry to a given state, diregarding all previous state
474
481
475 This is to be used by the part of the dirstate API dedicated to
482 This is to be used by the part of the dirstate API dedicated to
476 adjusting the dirstate after a update/merge.
483 adjusting the dirstate after a update/merge.
477
484
478 note: calling this might result to no entry existing at all if the
485 note: calling this might result to no entry existing at all if the
479 dirstate map does not see any point at having one for this file
486 dirstate map does not see any point at having one for this file
480 anymore.
487 anymore.
481 """
488 """
482 if merged and (clean_p1 or clean_p2):
489 if merged and (clean_p1 or clean_p2):
483 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
490 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
484 raise error.ProgrammingError(msg)
491 raise error.ProgrammingError(msg)
485 # copy information are now outdated
492 # copy information are now outdated
486 # (maybe new information should be in directly passed to this function)
493 # (maybe new information should be in directly passed to this function)
487 self.copymap.pop(filename, None)
494 self.copymap.pop(filename, None)
488
495
489 if not (p1_tracked or p2_tracked or wc_tracked):
496 if not (p1_tracked or p2_tracked or wc_tracked):
490 old_entry = self._map.pop(filename, None)
497 old_entry = self._map.pop(filename, None)
491 self._dirs_decr(filename, old_entry=old_entry)
498 self._dirs_decr(filename, old_entry=old_entry)
492 self.copymap.pop(filename, None)
499 self.copymap.pop(filename, None)
493 return
500 return
494 elif merged:
501 elif merged:
495 pass
502 pass
496 elif not (p1_tracked or p2_tracked) and wc_tracked:
503 elif not (p1_tracked or p2_tracked) and wc_tracked:
497 pass # file is added, nothing special to adjust
504 pass # file is added, nothing special to adjust
498 elif (p1_tracked or p2_tracked) and not wc_tracked:
505 elif (p1_tracked or p2_tracked) and not wc_tracked:
499 pass
506 pass
500 elif clean_p2 and wc_tracked:
507 elif clean_p2 and wc_tracked:
501 pass
508 pass
502 elif not p1_tracked and p2_tracked and wc_tracked:
509 elif not p1_tracked and p2_tracked and wc_tracked:
503 clean_p2 = True
510 clean_p2 = True
504 elif possibly_dirty:
511 elif possibly_dirty:
505 pass
512 pass
506 elif wc_tracked:
513 elif wc_tracked:
507 # this is a "normal" file
514 # this is a "normal" file
508 if parentfiledata is None:
515 if parentfiledata is None:
509 msg = b'failed to pass parentfiledata for a normal file: %s'
516 msg = b'failed to pass parentfiledata for a normal file: %s'
510 msg %= filename
517 msg %= filename
511 raise error.ProgrammingError(msg)
518 raise error.ProgrammingError(msg)
512 else:
519 else:
513 assert False, 'unreachable'
520 assert False, 'unreachable'
514
521
515 old_entry = self._map.get(filename)
522 old_entry = self._map.get(filename)
516 self._dirs_incr(filename, old_entry)
523 self._dirs_incr(filename, old_entry)
517 entry = DirstateItem(
524 entry = DirstateItem(
518 wc_tracked=wc_tracked,
525 wc_tracked=wc_tracked,
519 p1_tracked=p1_tracked,
526 p1_tracked=p1_tracked,
520 p2_tracked=p2_tracked,
527 p2_tracked=p2_tracked,
521 merged=merged,
528 merged=merged,
522 clean_p1=clean_p1,
529 clean_p1=clean_p1,
523 clean_p2=clean_p2,
530 clean_p2=clean_p2,
524 possibly_dirty=possibly_dirty,
531 possibly_dirty=possibly_dirty,
525 parentfiledata=parentfiledata,
532 parentfiledata=parentfiledata,
526 )
533 )
527 self._map[filename] = entry
534 self._map[filename] = entry
528
535
536 def _drop_entry(self, f):
537 self._map.pop(f, None)
538 self._copymap.pop(f, None)
539
529
540
530 if rustmod is not None:
541 if rustmod is not None:
531
542
532 class dirstatemap(_dirstatemapcommon):
543 class dirstatemap(_dirstatemapcommon):
533 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
544 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
534 super(dirstatemap, self).__init__(
545 super(dirstatemap, self).__init__(
535 ui, opener, root, nodeconstants, use_dirstate_v2
546 ui, opener, root, nodeconstants, use_dirstate_v2
536 )
547 )
537 self._docket = None
548 self._docket = None
538
549
539 ### Core data storage and access
550 ### Core data storage and access
540
551
541 @property
552 @property
542 def docket(self):
553 def docket(self):
543 if not self._docket:
554 if not self._docket:
544 if not self._use_dirstate_v2:
555 if not self._use_dirstate_v2:
545 raise error.ProgrammingError(
556 raise error.ProgrammingError(
546 b'dirstate only has a docket in v2 format'
557 b'dirstate only has a docket in v2 format'
547 )
558 )
548 self._docket = docketmod.DirstateDocket.parse(
559 self._docket = docketmod.DirstateDocket.parse(
549 self._readdirstatefile(), self._nodeconstants
560 self._readdirstatefile(), self._nodeconstants
550 )
561 )
551 return self._docket
562 return self._docket
552
563
553 @propertycache
564 @propertycache
554 def _map(self):
565 def _map(self):
555 """
566 """
556 Fills the Dirstatemap when called.
567 Fills the Dirstatemap when called.
557 """
568 """
558 # ignore HG_PENDING because identity is used only for writing
569 # ignore HG_PENDING because identity is used only for writing
559 self.identity = util.filestat.frompath(
570 self.identity = util.filestat.frompath(
560 self._opener.join(self._filename)
571 self._opener.join(self._filename)
561 )
572 )
562
573
563 if self._use_dirstate_v2:
574 if self._use_dirstate_v2:
564 if self.docket.uuid:
575 if self.docket.uuid:
565 # TODO: use mmap when possible
576 # TODO: use mmap when possible
566 data = self._opener.read(self.docket.data_filename())
577 data = self._opener.read(self.docket.data_filename())
567 else:
578 else:
568 data = b''
579 data = b''
569 self._map = rustmod.DirstateMap.new_v2(
580 self._map = rustmod.DirstateMap.new_v2(
570 data, self.docket.data_size, self.docket.tree_metadata
581 data, self.docket.data_size, self.docket.tree_metadata
571 )
582 )
572 parents = self.docket.parents
583 parents = self.docket.parents
573 else:
584 else:
574 self._map, parents = rustmod.DirstateMap.new_v1(
585 self._map, parents = rustmod.DirstateMap.new_v1(
575 self._readdirstatefile()
586 self._readdirstatefile()
576 )
587 )
577
588
578 if parents and not self._dirtyparents:
589 if parents and not self._dirtyparents:
579 self.setparents(*parents)
590 self.setparents(*parents)
580
591
581 self.__contains__ = self._map.__contains__
592 self.__contains__ = self._map.__contains__
582 self.__getitem__ = self._map.__getitem__
593 self.__getitem__ = self._map.__getitem__
583 self.get = self._map.get
594 self.get = self._map.get
584 return self._map
595 return self._map
585
596
586 @property
597 @property
587 def copymap(self):
598 def copymap(self):
588 return self._map.copymap()
599 return self._map.copymap()
589
600
590 def debug_iter(self, all):
601 def debug_iter(self, all):
591 """
602 """
592 Return an iterator of (filename, state, mode, size, mtime) tuples
603 Return an iterator of (filename, state, mode, size, mtime) tuples
593
604
594 `all`: also include with `state == b' '` dirstate tree nodes that
605 `all`: also include with `state == b' '` dirstate tree nodes that
595 don't have an associated `DirstateItem`.
606 don't have an associated `DirstateItem`.
596
607
597 """
608 """
598 return self._map.debug_iter(all)
609 return self._map.debug_iter(all)
599
610
600 def clear(self):
611 def clear(self):
601 self._map.clear()
612 self._map.clear()
602 self.setparents(
613 self.setparents(
603 self._nodeconstants.nullid, self._nodeconstants.nullid
614 self._nodeconstants.nullid, self._nodeconstants.nullid
604 )
615 )
605 util.clearcachedproperty(self, b"_dirs")
616 util.clearcachedproperty(self, b"_dirs")
606 util.clearcachedproperty(self, b"_alldirs")
617 util.clearcachedproperty(self, b"_alldirs")
607 util.clearcachedproperty(self, b"dirfoldmap")
618 util.clearcachedproperty(self, b"dirfoldmap")
608
619
609 def items(self):
620 def items(self):
610 return self._map.items()
621 return self._map.items()
611
622
612 # forward for python2,3 compat
623 # forward for python2,3 compat
613 iteritems = items
624 iteritems = items
614
625
615 def keys(self):
626 def keys(self):
616 return iter(self._map)
627 return iter(self._map)
617
628
618 ### reading/setting parents
629 ### reading/setting parents
619
630
620 def setparents(self, p1, p2, fold_p2=False):
631 def setparents(self, p1, p2, fold_p2=False):
621 self._parents = (p1, p2)
632 self._parents = (p1, p2)
622 self._dirtyparents = True
633 self._dirtyparents = True
623 copies = {}
634 copies = {}
624 if fold_p2:
635 if fold_p2:
625 # Collect into an intermediate list to avoid a `RuntimeError`
636 # Collect into an intermediate list to avoid a `RuntimeError`
626 # exception due to mutation during iteration.
637 # exception due to mutation during iteration.
627 # TODO: move this the whole loop to Rust where `iter_mut`
638 # TODO: move this the whole loop to Rust where `iter_mut`
628 # enables in-place mutation of elements of a collection while
639 # enables in-place mutation of elements of a collection while
629 # iterating it, without mutating the collection itself.
640 # iterating it, without mutating the collection itself.
630 candidatefiles = [
641 candidatefiles = [
631 (f, s)
642 (f, s)
632 for f, s in self._map.items()
643 for f, s in self._map.items()
633 if s.merged or s.from_p2
644 if s.merged or s.from_p2
634 ]
645 ]
635 for f, s in candidatefiles:
646 for f, s in candidatefiles:
636 # Discard "merged" markers when moving away from a merge state
647 # Discard "merged" markers when moving away from a merge state
637 if s.merged:
648 if s.merged:
638 source = self.copymap.get(f)
649 source = self.copymap.get(f)
639 if source:
650 if source:
640 copies[f] = source
651 copies[f] = source
641 self.reset_state(
652 self.reset_state(
642 f,
653 f,
643 wc_tracked=True,
654 wc_tracked=True,
644 p1_tracked=True,
655 p1_tracked=True,
645 possibly_dirty=True,
656 possibly_dirty=True,
646 )
657 )
647 # Also fix up otherparent markers
658 # Also fix up otherparent markers
648 elif s.from_p2:
659 elif s.from_p2:
649 source = self.copymap.get(f)
660 source = self.copymap.get(f)
650 if source:
661 if source:
651 copies[f] = source
662 copies[f] = source
652 self.reset_state(
663 self.reset_state(
653 f,
664 f,
654 p1_tracked=False,
665 p1_tracked=False,
655 wc_tracked=True,
666 wc_tracked=True,
656 )
667 )
657 return copies
668 return copies
658
669
659 def parents(self):
670 def parents(self):
660 if not self._parents:
671 if not self._parents:
661 if self._use_dirstate_v2:
672 if self._use_dirstate_v2:
662 self._parents = self.docket.parents
673 self._parents = self.docket.parents
663 else:
674 else:
664 read_len = self._nodelen * 2
675 read_len = self._nodelen * 2
665 st = self._readdirstatefile(read_len)
676 st = self._readdirstatefile(read_len)
666 l = len(st)
677 l = len(st)
667 if l == read_len:
678 if l == read_len:
668 self._parents = (
679 self._parents = (
669 st[: self._nodelen],
680 st[: self._nodelen],
670 st[self._nodelen : 2 * self._nodelen],
681 st[self._nodelen : 2 * self._nodelen],
671 )
682 )
672 elif l == 0:
683 elif l == 0:
673 self._parents = (
684 self._parents = (
674 self._nodeconstants.nullid,
685 self._nodeconstants.nullid,
675 self._nodeconstants.nullid,
686 self._nodeconstants.nullid,
676 )
687 )
677 else:
688 else:
678 raise error.Abort(
689 raise error.Abort(
679 _(b'working directory state appears damaged!')
690 _(b'working directory state appears damaged!')
680 )
691 )
681
692
682 return self._parents
693 return self._parents
683
694
684 ### disk interaction
695 ### disk interaction
685
696
686 @propertycache
697 @propertycache
687 def identity(self):
698 def identity(self):
688 self._map
699 self._map
689 return self.identity
700 return self.identity
690
701
691 def write(self, tr, st, now):
702 def write(self, tr, st, now):
692 if not self._use_dirstate_v2:
703 if not self._use_dirstate_v2:
693 p1, p2 = self.parents()
704 p1, p2 = self.parents()
694 packed = self._map.write_v1(p1, p2, now)
705 packed = self._map.write_v1(p1, p2, now)
695 st.write(packed)
706 st.write(packed)
696 st.close()
707 st.close()
697 self._dirtyparents = False
708 self._dirtyparents = False
698 return
709 return
699
710
700 # We can only append to an existing data file if there is one
711 # We can only append to an existing data file if there is one
701 can_append = self.docket.uuid is not None
712 can_append = self.docket.uuid is not None
702 packed, meta, append = self._map.write_v2(now, can_append)
713 packed, meta, append = self._map.write_v2(now, can_append)
703 if append:
714 if append:
704 docket = self.docket
715 docket = self.docket
705 data_filename = docket.data_filename()
716 data_filename = docket.data_filename()
706 if tr:
717 if tr:
707 tr.add(data_filename, docket.data_size)
718 tr.add(data_filename, docket.data_size)
708 with self._opener(data_filename, b'r+b') as fp:
719 with self._opener(data_filename, b'r+b') as fp:
709 fp.seek(docket.data_size)
720 fp.seek(docket.data_size)
710 assert fp.tell() == docket.data_size
721 assert fp.tell() == docket.data_size
711 written = fp.write(packed)
722 written = fp.write(packed)
712 if written is not None: # py2 may return None
723 if written is not None: # py2 may return None
713 assert written == len(packed), (written, len(packed))
724 assert written == len(packed), (written, len(packed))
714 docket.data_size += len(packed)
725 docket.data_size += len(packed)
715 docket.parents = self.parents()
726 docket.parents = self.parents()
716 docket.tree_metadata = meta
727 docket.tree_metadata = meta
717 st.write(docket.serialize())
728 st.write(docket.serialize())
718 st.close()
729 st.close()
719 else:
730 else:
720 old_docket = self.docket
731 old_docket = self.docket
721 new_docket = docketmod.DirstateDocket.with_new_uuid(
732 new_docket = docketmod.DirstateDocket.with_new_uuid(
722 self.parents(), len(packed), meta
733 self.parents(), len(packed), meta
723 )
734 )
724 data_filename = new_docket.data_filename()
735 data_filename = new_docket.data_filename()
725 if tr:
736 if tr:
726 tr.add(data_filename, 0)
737 tr.add(data_filename, 0)
727 self._opener.write(data_filename, packed)
738 self._opener.write(data_filename, packed)
728 # Write the new docket after the new data file has been
739 # Write the new docket after the new data file has been
729 # written. Because `st` was opened with `atomictemp=True`,
740 # written. Because `st` was opened with `atomictemp=True`,
730 # the actual `.hg/dirstate` file is only affected on close.
741 # the actual `.hg/dirstate` file is only affected on close.
731 st.write(new_docket.serialize())
742 st.write(new_docket.serialize())
732 st.close()
743 st.close()
733 # Remove the old data file after the new docket pointing to
744 # Remove the old data file after the new docket pointing to
734 # the new data file was written.
745 # the new data file was written.
735 if old_docket.uuid:
746 if old_docket.uuid:
736 data_filename = old_docket.data_filename()
747 data_filename = old_docket.data_filename()
737 unlink = lambda _tr=None: self._opener.unlink(data_filename)
748 unlink = lambda _tr=None: self._opener.unlink(data_filename)
738 if tr:
749 if tr:
739 category = b"dirstate-v2-clean-" + old_docket.uuid
750 category = b"dirstate-v2-clean-" + old_docket.uuid
740 tr.addpostclose(category, unlink)
751 tr.addpostclose(category, unlink)
741 else:
752 else:
742 unlink()
753 unlink()
743 self._docket = new_docket
754 self._docket = new_docket
744 # Reload from the newly-written file
755 # Reload from the newly-written file
745 util.clearcachedproperty(self, b"_map")
756 util.clearcachedproperty(self, b"_map")
746 self._dirtyparents = False
757 self._dirtyparents = False
747
758
748 def _opendirstatefile(self):
759 def _opendirstatefile(self):
749 fp, mode = txnutil.trypending(
760 fp, mode = txnutil.trypending(
750 self._root, self._opener, self._filename
761 self._root, self._opener, self._filename
751 )
762 )
752 if self._pendingmode is not None and self._pendingmode != mode:
763 if self._pendingmode is not None and self._pendingmode != mode:
753 fp.close()
764 fp.close()
754 raise error.Abort(
765 raise error.Abort(
755 _(b'working directory state may be changed parallelly')
766 _(b'working directory state may be changed parallelly')
756 )
767 )
757 self._pendingmode = mode
768 self._pendingmode = mode
758 return fp
769 return fp
759
770
760 def _readdirstatefile(self, size=-1):
771 def _readdirstatefile(self, size=-1):
761 try:
772 try:
762 with self._opendirstatefile() as fp:
773 with self._opendirstatefile() as fp:
763 return fp.read(size)
774 return fp.read(size)
764 except IOError as err:
775 except IOError as err:
765 if err.errno != errno.ENOENT:
776 if err.errno != errno.ENOENT:
766 raise
777 raise
767 # File doesn't exist, so the current state is empty
778 # File doesn't exist, so the current state is empty
768 return b''
779 return b''
769
780
770 ### code related to maintaining and accessing "extra" property
781 ### code related to maintaining and accessing "extra" property
771 # (e.g. "has_dir")
782 # (e.g. "has_dir")
772
783
773 @propertycache
784 @propertycache
774 def filefoldmap(self):
785 def filefoldmap(self):
775 """Returns a dictionary mapping normalized case paths to their
786 """Returns a dictionary mapping normalized case paths to their
776 non-normalized versions.
787 non-normalized versions.
777 """
788 """
778 return self._map.filefoldmapasdict()
789 return self._map.filefoldmapasdict()
779
790
780 def hastrackeddir(self, d):
791 def hastrackeddir(self, d):
781 return self._map.hastrackeddir(d)
792 return self._map.hastrackeddir(d)
782
793
783 def hasdir(self, d):
794 def hasdir(self, d):
784 return self._map.hasdir(d)
795 return self._map.hasdir(d)
785
796
786 @propertycache
797 @propertycache
787 def dirfoldmap(self):
798 def dirfoldmap(self):
788 f = {}
799 f = {}
789 normcase = util.normcase
800 normcase = util.normcase
790 for name in self._map.tracked_dirs():
801 for name in self._map.tracked_dirs():
791 f[normcase(name)] = name
802 f[normcase(name)] = name
792 return f
803 return f
793
804
794 ### code related to manipulation of entries and copy-sources
805 ### code related to manipulation of entries and copy-sources
795
806
796 def _refresh_entry(self, f, entry):
807 def _refresh_entry(self, f, entry):
797 if not entry.any_tracked:
808 if not entry.any_tracked:
798 self._map.drop_item_and_copy_source(f)
809 self._map.drop_item_and_copy_source(f)
799 else:
810 else:
800 self._map.addfile(f, entry)
811 self._map.addfile(f, entry)
801
812
802 def _insert_entry(self, f, entry):
813 def _insert_entry(self, f, entry):
803 self._map.addfile(f, entry)
814 self._map.addfile(f, entry)
804
815
816 def _drop_entry(self, f):
817 self._map.drop_item_and_copy_source(f)
818
805 def __setitem__(self, key, value):
819 def __setitem__(self, key, value):
806 assert isinstance(value, DirstateItem)
820 assert isinstance(value, DirstateItem)
807 self._map.set_dirstate_item(key, value)
821 self._map.set_dirstate_item(key, value)
808
822
809 def reset_state(
823 def reset_state(
810 self,
824 self,
811 filename,
825 filename,
812 wc_tracked=False,
826 wc_tracked=False,
813 p1_tracked=False,
827 p1_tracked=False,
814 p2_tracked=False,
828 p2_tracked=False,
815 merged=False,
829 merged=False,
816 clean_p1=False,
830 clean_p1=False,
817 clean_p2=False,
831 clean_p2=False,
818 possibly_dirty=False,
832 possibly_dirty=False,
819 parentfiledata=None,
833 parentfiledata=None,
820 ):
834 ):
821 """Set a entry to a given state, disregarding all previous state
835 """Set a entry to a given state, disregarding all previous state
822
836
823 This is to be used by the part of the dirstate API dedicated to
837 This is to be used by the part of the dirstate API dedicated to
824 adjusting the dirstate after a update/merge.
838 adjusting the dirstate after a update/merge.
825
839
826 note: calling this might result to no entry existing at all if the
840 note: calling this might result to no entry existing at all if the
827 dirstate map does not see any point at having one for this file
841 dirstate map does not see any point at having one for this file
828 anymore.
842 anymore.
829 """
843 """
830 if merged and (clean_p1 or clean_p2):
844 if merged and (clean_p1 or clean_p2):
831 msg = (
845 msg = (
832 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
846 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
833 )
847 )
834 raise error.ProgrammingError(msg)
848 raise error.ProgrammingError(msg)
835 # copy information are now outdated
849 # copy information are now outdated
836 # (maybe new information should be in directly passed to this function)
850 # (maybe new information should be in directly passed to this function)
837 self.copymap.pop(filename, None)
851 self.copymap.pop(filename, None)
838
852
839 if not (p1_tracked or p2_tracked or wc_tracked):
853 if not (p1_tracked or p2_tracked or wc_tracked):
840 self._map.drop_item_and_copy_source(filename)
854 self._map.drop_item_and_copy_source(filename)
841 elif merged:
855 elif merged:
842 # XXX might be merged and removed ?
856 # XXX might be merged and removed ?
843 entry = self.get(filename)
857 entry = self.get(filename)
844 if entry is not None and entry.tracked:
858 if entry is not None and entry.tracked:
845 # XXX mostly replicate dirstate.other parent. We should get
859 # XXX mostly replicate dirstate.other parent. We should get
846 # the higher layer to pass us more reliable data where `merged`
860 # the higher layer to pass us more reliable data where `merged`
847 # actually mean merged. Dropping the else clause will show
861 # actually mean merged. Dropping the else clause will show
848 # failure in `test-graft.t`
862 # failure in `test-graft.t`
849 self.addfile(filename, merged=True)
863 self.addfile(filename, merged=True)
850 else:
864 else:
851 self.addfile(filename, from_p2=True)
865 self.addfile(filename, from_p2=True)
852 elif not (p1_tracked or p2_tracked) and wc_tracked:
866 elif not (p1_tracked or p2_tracked) and wc_tracked:
853 self.addfile(
867 self.addfile(
854 filename, added=True, possibly_dirty=possibly_dirty
868 filename, added=True, possibly_dirty=possibly_dirty
855 )
869 )
856 elif (p1_tracked or p2_tracked) and not wc_tracked:
870 elif (p1_tracked or p2_tracked) and not wc_tracked:
857 # XXX might be merged and removed ?
871 # XXX might be merged and removed ?
858 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
872 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
859 elif clean_p2 and wc_tracked:
873 elif clean_p2 and wc_tracked:
860 if p1_tracked or self.get(filename) is not None:
874 if p1_tracked or self.get(filename) is not None:
861 # XXX the `self.get` call is catching some case in
875 # XXX the `self.get` call is catching some case in
862 # `test-merge-remove.t` where the file is tracked in p1, the
876 # `test-merge-remove.t` where the file is tracked in p1, the
863 # p1_tracked argument is False.
877 # p1_tracked argument is False.
864 #
878 #
865 # In addition, this seems to be a case where the file is marked
879 # In addition, this seems to be a case where the file is marked
866 # as merged without actually being the result of a merge
880 # as merged without actually being the result of a merge
867 # action. So thing are not ideal here.
881 # action. So thing are not ideal here.
868 self.addfile(filename, merged=True)
882 self.addfile(filename, merged=True)
869 else:
883 else:
870 self.addfile(filename, from_p2=True)
884 self.addfile(filename, from_p2=True)
871 elif not p1_tracked and p2_tracked and wc_tracked:
885 elif not p1_tracked and p2_tracked and wc_tracked:
872 self.addfile(
886 self.addfile(
873 filename, from_p2=True, possibly_dirty=possibly_dirty
887 filename, from_p2=True, possibly_dirty=possibly_dirty
874 )
888 )
875 elif possibly_dirty:
889 elif possibly_dirty:
876 self.addfile(filename, possibly_dirty=possibly_dirty)
890 self.addfile(filename, possibly_dirty=possibly_dirty)
877 elif wc_tracked:
891 elif wc_tracked:
878 # this is a "normal" file
892 # this is a "normal" file
879 if parentfiledata is None:
893 if parentfiledata is None:
880 msg = b'failed to pass parentfiledata for a normal file: %s'
894 msg = b'failed to pass parentfiledata for a normal file: %s'
881 msg %= filename
895 msg %= filename
882 raise error.ProgrammingError(msg)
896 raise error.ProgrammingError(msg)
883 mode, size, mtime = parentfiledata
897 mode, size, mtime = parentfiledata
884 self.addfile(filename, mode=mode, size=size, mtime=mtime)
898 self.addfile(filename, mode=mode, size=size, mtime=mtime)
885 else:
899 else:
886 assert False, 'unreachable'
900 assert False, 'unreachable'
887
901
888 ### Legacy method we need to get rid of
902 ### Legacy method we need to get rid of
889
903
890 def addfile(
904 def addfile(
891 self,
905 self,
892 f,
906 f,
893 mode=0,
907 mode=0,
894 size=None,
908 size=None,
895 mtime=None,
909 mtime=None,
896 added=False,
910 added=False,
897 merged=False,
911 merged=False,
898 from_p2=False,
912 from_p2=False,
899 possibly_dirty=False,
913 possibly_dirty=False,
900 ):
914 ):
901 if added:
915 if added:
902 assert not possibly_dirty
916 assert not possibly_dirty
903 assert not from_p2
917 assert not from_p2
904 item = DirstateItem.new_added()
918 item = DirstateItem.new_added()
905 elif merged:
919 elif merged:
906 assert not possibly_dirty
920 assert not possibly_dirty
907 assert not from_p2
921 assert not from_p2
908 item = DirstateItem.new_merged()
922 item = DirstateItem.new_merged()
909 elif from_p2:
923 elif from_p2:
910 assert not possibly_dirty
924 assert not possibly_dirty
911 item = DirstateItem.new_from_p2()
925 item = DirstateItem.new_from_p2()
912 elif possibly_dirty:
926 elif possibly_dirty:
913 item = DirstateItem.new_possibly_dirty()
927 item = DirstateItem.new_possibly_dirty()
914 else:
928 else:
915 assert size is not None
929 assert size is not None
916 assert mtime is not None
930 assert mtime is not None
917 size = size & rangemask
931 size = size & rangemask
918 mtime = mtime & rangemask
932 mtime = mtime & rangemask
919 item = DirstateItem.new_normal(mode, size, mtime)
933 item = DirstateItem.new_normal(mode, size, mtime)
920 self._map.addfile(f, item)
934 self._map.addfile(f, item)
921 if added:
935 if added:
922 self.copymap.pop(f, None)
936 self.copymap.pop(f, None)
923
937
924 def removefile(self, *args, **kwargs):
938 def removefile(self, *args, **kwargs):
925 return self._map.removefile(*args, **kwargs)
939 return self._map.removefile(*args, **kwargs)
General Comments 0
You need to be logged in to leave comments. Login now