comparison mercurial/pure/parsers.py @ 48138:38488d488ec1

dirstate-item: change the internal storage and constructor value This should be closer to what we do need and what we can actually reliably record. In practice it means that we abandon the prospect of storing much more refined data for now. We don't have the necessary information nor code using it right now. So it seems safer to just use a clearer version of what we had so far. See the documentation changes for details. Differential Revision: https://phab.mercurial-scm.org/D11557
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Fri, 01 Oct 2021 20:35:30 +0200
parents e2da3ec94169
children fb3b41d583c2
comparison
equal deleted inserted replaced
48137:25836b0029f5 48138:38488d488ec1
51 It hold multiple attributes 51 It hold multiple attributes
52 52
53 # about file tracking 53 # about file tracking
54 - wc_tracked: is the file tracked by the working copy 54 - wc_tracked: is the file tracked by the working copy
55 - p1_tracked: is the file tracked in working copy first parent 55 - p1_tracked: is the file tracked in working copy first parent
56 - p2_tracked: is the file tracked in working copy second parent 56 - p2_info: the file has been involved in some merge operation. Either
57 57 because it was actually merged, or because the p2 version was
58 # about what possible merge action related to this file 58 ahead, or because some renamed moved it there. In either case
59 - clean_p1: merge picked the file content from p1 59 `hg status` will want it displayed as modified.
60 - clean_p2: merge picked the file content from p2
61 - merged: file gather changes from both side.
62 60
63 # about the file state expected from p1 manifest: 61 # about the file state expected from p1 manifest:
64 - mode: the file mode in p1 62 - mode: the file mode in p1
65 - size: the file size in p1 63 - size: the file size in p1
66 64
65 These value can be set to None, which mean we don't have a meaningful value
66 to compare with. Either because we don't really care about them as there
67 `status` is known without having to look at the disk or because we don't
68 know these right now and a full comparison will be needed to find out if
69 the file is clean.
70
67 # about the file state on disk last time we saw it: 71 # about the file state on disk last time we saw it:
68 - mtime: the last known clean mtime for the file. 72 - mtime: the last known clean mtime for the file.
69 73
70 The last three item (mode, size and mtime) can be None if no meaningful (or 74 This value can be set to None if no cachable state exist. Either because we
71 trusted) value exists. 75 do not care (see previous section) or because we could not cache something
72 76 yet.
73 """ 77 """
74 78
75 _wc_tracked = attr.ib() 79 _wc_tracked = attr.ib()
76 _p1_tracked = attr.ib() 80 _p1_tracked = attr.ib()
77 _p2_tracked = attr.ib() 81 _p2_info = attr.ib()
78 # the three item above should probably be combined
79 #
80 # However it is unclear if they properly cover some of the most advanced
81 # merge case. So we should probably wait on this to be settled.
82 _merged = attr.ib()
83 _clean_p1 = attr.ib()
84 _clean_p2 = attr.ib()
85 _possibly_dirty = attr.ib()
86 _mode = attr.ib() 82 _mode = attr.ib()
87 _size = attr.ib() 83 _size = attr.ib()
88 _mtime = attr.ib() 84 _mtime = attr.ib()
89 85
90 def __init__( 86 def __init__(
91 self, 87 self,
92 wc_tracked=False, 88 wc_tracked=False,
93 p1_tracked=False, 89 p1_tracked=False,
94 p2_tracked=False, 90 p2_info=False,
95 merged=False, 91 has_meaningful_data=True,
96 clean_p1=False, 92 has_meaningful_mtime=True,
97 clean_p2=False,
98 possibly_dirty=False,
99 parentfiledata=None, 93 parentfiledata=None,
100 ): 94 ):
101 if merged and (clean_p1 or clean_p2):
102 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
103 raise error.ProgrammingError(msg)
104
105 assert not (merged and not p1_tracked)
106 self._wc_tracked = wc_tracked 95 self._wc_tracked = wc_tracked
107 self._p1_tracked = p1_tracked 96 self._p1_tracked = p1_tracked
108 self._p2_tracked = p2_tracked 97 self._p2_info = p2_info
109 self._merged = merged 98
110 self._clean_p1 = clean_p1 99 self._mode = None
111 self._clean_p2 = clean_p2 100 self._size = None
112 self._possibly_dirty = possibly_dirty 101 self._mtime = None
113 if parentfiledata is None: 102 if parentfiledata is None:
114 self._mode = None 103 has_meaningful_mtime = False
115 self._size = None 104 has_meaningful_data = False
116 self._mtime = None 105 if has_meaningful_data:
117 else:
118 self._mode = parentfiledata[0] 106 self._mode = parentfiledata[0]
119 self._size = parentfiledata[1] 107 self._size = parentfiledata[1]
108 if has_meaningful_mtime:
120 self._mtime = parentfiledata[2] 109 self._mtime = parentfiledata[2]
121 110
122 @classmethod 111 @classmethod
123 def new_added(cls): 112 def new_added(cls):
124 """constructor to help legacy API to build a new "added" item 113 """constructor to help legacy API to build a new "added" item
125 114
126 Should eventually be removed 115 Should eventually be removed
127 """ 116 """
128 instance = cls() 117 return cls(wc_tracked=True)
129 instance._wc_tracked = True
130 instance._p1_tracked = False
131 instance._p2_tracked = False
132 return instance
133 118
134 @classmethod 119 @classmethod
135 def new_merged(cls): 120 def new_merged(cls):
136 """constructor to help legacy API to build a new "merged" item 121 """constructor to help legacy API to build a new "merged" item
137 122
138 Should eventually be removed 123 Should eventually be removed
139 """ 124 """
140 instance = cls() 125 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
141 instance._wc_tracked = True
142 instance._p1_tracked = True # might not be True because of rename ?
143 instance._p2_tracked = True # might not be True because of rename ?
144 instance._merged = True
145 return instance
146 126
147 @classmethod 127 @classmethod
148 def new_from_p2(cls): 128 def new_from_p2(cls):
149 """constructor to help legacy API to build a new "from_p2" item 129 """constructor to help legacy API to build a new "from_p2" item
150 130
151 Should eventually be removed 131 Should eventually be removed
152 """ 132 """
153 instance = cls() 133 return cls(wc_tracked=True, p2_info=True)
154 instance._wc_tracked = True
155 instance._p1_tracked = False # might actually be True
156 instance._p2_tracked = True
157 instance._clean_p2 = True
158 return instance
159 134
160 @classmethod 135 @classmethod
161 def new_possibly_dirty(cls): 136 def new_possibly_dirty(cls):
162 """constructor to help legacy API to build a new "possibly_dirty" item 137 """constructor to help legacy API to build a new "possibly_dirty" item
163 138
164 Should eventually be removed 139 Should eventually be removed
165 """ 140 """
166 instance = cls() 141 return cls(wc_tracked=True, p1_tracked=True)
167 instance._wc_tracked = True
168 instance._p1_tracked = True
169 instance._possibly_dirty = True
170 return instance
171 142
172 @classmethod 143 @classmethod
173 def new_normal(cls, mode, size, mtime): 144 def new_normal(cls, mode, size, mtime):
174 """constructor to help legacy API to build a new "normal" item 145 """constructor to help legacy API to build a new "normal" item
175 146
176 Should eventually be removed 147 Should eventually be removed
177 """ 148 """
178 assert size != FROM_P2 149 assert size != FROM_P2
179 assert size != NONNORMAL 150 assert size != NONNORMAL
180 instance = cls() 151 return cls(
181 instance._wc_tracked = True 152 wc_tracked=True,
182 instance._p1_tracked = True 153 p1_tracked=True,
183 instance._mode = mode 154 parentfiledata=(mode, size, mtime),
184 instance._size = size 155 )
185 instance._mtime = mtime
186 return instance
187 156
188 @classmethod 157 @classmethod
189 def from_v1_data(cls, state, mode, size, mtime): 158 def from_v1_data(cls, state, mode, size, mtime):
190 """Build a new DirstateItem object from V1 data 159 """Build a new DirstateItem object from V1 data
191 160
195 if state == b'm': 164 if state == b'm':
196 return cls.new_merged() 165 return cls.new_merged()
197 elif state == b'a': 166 elif state == b'a':
198 return cls.new_added() 167 return cls.new_added()
199 elif state == b'r': 168 elif state == b'r':
200 instance = cls()
201 instance._wc_tracked = False
202 if size == NONNORMAL: 169 if size == NONNORMAL:
203 instance._merged = True 170 p1_tracked = True
204 instance._p1_tracked = ( 171 p2_info = True
205 True # might not be True because of rename ?
206 )
207 instance._p2_tracked = (
208 True # might not be True because of rename ?
209 )
210 elif size == FROM_P2: 172 elif size == FROM_P2:
211 instance._clean_p2 = True 173 p1_tracked = False
212 instance._p1_tracked = ( 174 p2_info = True
213 False # We actually don't know (file history)
214 )
215 instance._p2_tracked = True
216 else: 175 else:
217 instance._p1_tracked = True 176 p1_tracked = True
218 return instance 177 p2_info = False
178 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
219 elif state == b'n': 179 elif state == b'n':
220 if size == FROM_P2: 180 if size == FROM_P2:
221 return cls.new_from_p2() 181 return cls.new_from_p2()
222 elif size == NONNORMAL: 182 elif size == NONNORMAL:
223 return cls.new_possibly_dirty() 183 return cls.new_possibly_dirty()
224 elif mtime == AMBIGUOUS_TIME: 184 elif mtime == AMBIGUOUS_TIME:
225 instance = cls.new_normal(mode, size, 42) 185 instance = cls.new_normal(mode, size, 42)
226 instance._mtime = None 186 instance._mtime = None
227 instance._possibly_dirty = True
228 return instance 187 return instance
229 else: 188 else:
230 return cls.new_normal(mode, size, mtime) 189 return cls.new_normal(mode, size, mtime)
231 else: 190 else:
232 raise RuntimeError(b'unknown state: %s' % state) 191 raise RuntimeError(b'unknown state: %s' % state)
235 """Mark a file as "possibly dirty" 194 """Mark a file as "possibly dirty"
236 195
237 This means the next status call will have to actually check its content 196 This means the next status call will have to actually check its content
238 to make sure it is correct. 197 to make sure it is correct.
239 """ 198 """
240 self._possibly_dirty = True 199 self._mtime = None
241 200
242 def set_clean(self, mode, size, mtime): 201 def set_clean(self, mode, size, mtime):
243 """mark a file as "clean" cancelling potential "possibly dirty call" 202 """mark a file as "clean" cancelling potential "possibly dirty call"
244 203
245 Note: this function is a descendant of `dirstate.normal` and is 204 Note: this function is a descendant of `dirstate.normal` and is
247 reason for this to not change in the future as long as the ccode is 206 reason for this to not change in the future as long as the ccode is
248 updated to preserve the proper state of the non-normal files. 207 updated to preserve the proper state of the non-normal files.
249 """ 208 """
250 self._wc_tracked = True 209 self._wc_tracked = True
251 self._p1_tracked = True 210 self._p1_tracked = True
252 self._p2_tracked = False # this might be wrong
253 self._merged = False
254 self._clean_p2 = False
255 self._possibly_dirty = False
256 self._mode = mode 211 self._mode = mode
257 self._size = size 212 self._size = size
258 self._mtime = mtime 213 self._mtime = mtime
259 214
260 def set_tracked(self): 215 def set_tracked(self):
261 """mark a file as tracked in the working copy 216 """mark a file as tracked in the working copy
262 217
263 This will ultimately be called by command like `hg add`. 218 This will ultimately be called by command like `hg add`.
264 """ 219 """
265 self._wc_tracked = True 220 self._wc_tracked = True
266 # `set_tracked` is replacing various `normallookup` call. So we set 221 # `set_tracked` is replacing various `normallookup` call. So we mark
267 # "possibly dirty" to stay on the safe side. 222 # the files as needing lookup
268 # 223 #
269 # Consider dropping this in the future in favor of something less broad. 224 # Consider dropping this in the future in favor of something less broad.
270 self._possibly_dirty = True 225 self._mtime = None
271 226
272 def set_untracked(self): 227 def set_untracked(self):
273 """mark a file as untracked in the working copy 228 """mark a file as untracked in the working copy
274 229
275 This will ultimately be called by command like `hg remove`. 230 This will ultimately be called by command like `hg remove`.
282 def drop_merge_data(self): 237 def drop_merge_data(self):
283 """remove all "merge-only" from a DirstateItem 238 """remove all "merge-only" from a DirstateItem
284 239
285 This is to be call by the dirstatemap code when the second parent is dropped 240 This is to be call by the dirstatemap code when the second parent is dropped
286 """ 241 """
287 if not (self.merged or self.from_p2): 242 if self._p2_info:
288 return 243 self._p2_info = False
289 self._p1_tracked = self.merged # why is this not already properly set ? 244 self._mode = None
290 245 self._size = None
291 self._merged = False 246 self._mtime = None
292 self._clean_p1 = False
293 self._clean_p2 = False
294 self._p2_tracked = False
295 self._possibly_dirty = True
296 self._mode = None
297 self._size = None
298 self._mtime = None
299 247
300 @property 248 @property
301 def mode(self): 249 def mode(self):
302 return self.v1_mode() 250 return self.v1_mode()
303 251
332 return self._wc_tracked 280 return self._wc_tracked
333 281
334 @property 282 @property
335 def any_tracked(self): 283 def any_tracked(self):
336 """True is the file is tracked anywhere (wc or parents)""" 284 """True is the file is tracked anywhere (wc or parents)"""
337 return self._wc_tracked or self._p1_tracked or self._p2_tracked 285 return self._wc_tracked or self._p1_tracked or self._p2_info
338 286
339 @property 287 @property
340 def added(self): 288 def added(self):
341 """True if the file has been added""" 289 """True if the file has been added"""
342 return self._wc_tracked and not (self._p1_tracked or self._p2_tracked) 290 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
343 291
344 @property 292 @property
345 def maybe_clean(self): 293 def maybe_clean(self):
346 """True if the file has a chance to be in the "clean" state""" 294 """True if the file has a chance to be in the "clean" state"""
347 if not self._wc_tracked: 295 if not self._wc_tracked:
348 return False 296 return False
349 elif self.added: 297 elif not self._p1_tracked:
350 return False 298 return False
351 elif self._merged: 299 elif self._p2_info:
352 return False
353 elif self._clean_p2:
354 return False 300 return False
355 return True 301 return True
356 302
357 @property 303 @property
358 def merged(self): 304 def merged(self):
359 """True if the file has been merged 305 """True if the file has been merged
360 306
361 Should only be set if a merge is in progress in the dirstate 307 Should only be set if a merge is in progress in the dirstate
362 """ 308 """
363 return self._wc_tracked and self._merged 309 return self._wc_tracked and self._p1_tracked and self._p2_info
364 310
365 @property 311 @property
366 def from_p2(self): 312 def from_p2(self):
367 """True if the file have been fetched from p2 during the current merge 313 """True if the file have been fetched from p2 during the current merge
368 314
369 This is only True is the file is currently tracked. 315 This is only True is the file is currently tracked.
370 316
371 Should only be set if a merge is in progress in the dirstate 317 Should only be set if a merge is in progress in the dirstate
372 """ 318 """
373 if not self._wc_tracked: 319 return self._wc_tracked and (not self._p1_tracked) and self._p2_info
374 return False
375 return self._clean_p2
376 320
377 @property 321 @property
378 def removed(self): 322 def removed(self):
379 """True if the file has been removed""" 323 """True if the file has been removed"""
380 return not self._wc_tracked and (self._p1_tracked or self._p2_tracked) 324 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
381 325
382 def v1_state(self): 326 def v1_state(self):
383 """return a "state" suitable for v1 serialization""" 327 """return a "state" suitable for v1 serialization"""
384 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked): 328 if not self.any_tracked:
385 # the object has no state to record, this is -currently- 329 # the object has no state to record, this is -currently-
386 # unsupported 330 # unsupported
387 raise RuntimeError('untracked item') 331 raise RuntimeError('untracked item')
388 elif self.removed: 332 elif self.removed:
389 return b'r' 333 return b'r'
402 """return a "size" suitable for v1 serialization""" 346 """return a "size" suitable for v1 serialization"""
403 if not self.any_tracked: 347 if not self.any_tracked:
404 # the object has no state to record, this is -currently- 348 # the object has no state to record, this is -currently-
405 # unsupported 349 # unsupported
406 raise RuntimeError('untracked item') 350 raise RuntimeError('untracked item')
407 elif self.removed and self._merged: 351 elif self.removed and self._p1_tracked and self._p2_info:
408 return NONNORMAL 352 return NONNORMAL
409 elif self.removed and self._clean_p2: 353 elif self.removed and self._p2_info:
410 return FROM_P2 354 return FROM_P2
411 elif self.removed: 355 elif self.removed:
412 return 0 356 return 0
413 elif self.merged: 357 elif self.merged:
414 return FROM_P2 358 return FROM_P2
415 elif self.added: 359 elif self.added:
416 return NONNORMAL 360 return NONNORMAL
417 elif self.from_p2: 361 elif self.from_p2:
418 return FROM_P2 362 return FROM_P2
419 elif self._possibly_dirty: 363 elif self._size is None:
420 return self._size if self._size is not None else NONNORMAL 364 return NONNORMAL
421 else: 365 else:
422 return self._size 366 return self._size
423 367
424 def v1_mtime(self): 368 def v1_mtime(self):
425 """return a "mtime" suitable for v1 serialization""" 369 """return a "mtime" suitable for v1 serialization"""
427 # the object has no state to record, this is -currently- 371 # the object has no state to record, this is -currently-
428 # unsupported 372 # unsupported
429 raise RuntimeError('untracked item') 373 raise RuntimeError('untracked item')
430 elif self.removed: 374 elif self.removed:
431 return 0 375 return 0
432 elif self._possibly_dirty: 376 elif self._mtime is None:
433 return AMBIGUOUS_TIME 377 return AMBIGUOUS_TIME
434 elif self.merged: 378 elif self._p2_info:
435 return AMBIGUOUS_TIME 379 return AMBIGUOUS_TIME
436 elif self.added: 380 elif not self._p1_tracked:
437 return AMBIGUOUS_TIME
438 elif self.from_p2:
439 return AMBIGUOUS_TIME 381 return AMBIGUOUS_TIME
440 else: 382 else:
441 return self._mtime if self._mtime is not None else 0 383 return self._mtime
442 384
443 def need_delay(self, now): 385 def need_delay(self, now):
444 """True if the stored mtime would be ambiguous with the current time""" 386 """True if the stored mtime would be ambiguous with the current time"""
445 return self.v1_state() == b'n' and self.v1_mtime() == now 387 return self.v1_state() == b'n' and self.v1_mtime() == now
446 388