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