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