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