Mercurial > public > mercurial-scm > hg
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 |