comparison mercurial/pure/parsers.py @ 47927:b81f52ca8075

dirstate-item: keep the full information in memory (for pure form) This changeset is finally reaching the area where we have been headed this whole time. Since all implementation details are contained inside de DirstateItem logic, we can change the way it is implemented. So we store the information that are passed to the object and use them to dynamically compute the "legacy" value. For now we only do this for the Pure implementation, as this is a good demonstration for the logic is fully insulated. The next step will be the update the C code too, to implement the various method with the new attribute (instead of the legacy one) and then start seriously thinking about the parameters we feed into DirstateItem. Differential Revision: https://phab.mercurial-scm.org/D11364
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Fri, 27 Aug 2021 15:10:21 +0200
parents 05f2be3affe3
children f1033c2d9b66
comparison
equal deleted inserted replaced
47926:05f2be3affe3 47927:b81f52ca8075
54 - mode, 54 - mode,
55 - size, 55 - size,
56 - mtime, 56 - mtime,
57 """ 57 """
58 58
59 _state = attr.ib() 59 _wc_tracked = attr.ib()
60 _p1_tracked = attr.ib()
61 _p2_tracked = attr.ib()
62 # the three item above should probably be combined
63 #
64 # However it is unclear if they properly cover some of the most advanced
65 # merge case. So we should probably wait on this to be settled.
66 _merged = attr.ib()
67 _clean_p1 = attr.ib()
68 _clean_p2 = attr.ib()
69 _possibly_dirty = attr.ib()
60 _mode = attr.ib() 70 _mode = attr.ib()
61 _size = attr.ib() 71 _size = attr.ib()
62 _mtime = attr.ib() 72 _mtime = attr.ib()
63 73
64 def __init__( 74 def __init__(
74 ): 84 ):
75 if merged and (clean_p1 or clean_p2): 85 if merged and (clean_p1 or clean_p2):
76 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`' 86 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
77 raise error.ProgrammingError(msg) 87 raise error.ProgrammingError(msg)
78 88
79 self._state = None 89 self._wc_tracked = wc_tracked
80 self._mode = 0 90 self._p1_tracked = p1_tracked
81 self._size = NONNORMAL 91 self._p2_tracked = p2_tracked
82 self._mtime = AMBIGUOUS_TIME 92 self._merged = merged
83 if not (p1_tracked or p2_tracked or wc_tracked): 93 self._clean_p1 = clean_p1
84 pass # the object has no state to record 94 self._clean_p2 = clean_p2
85 elif merged: 95 self._possibly_dirty = possibly_dirty
86 self._state = b'm' 96 if parentfiledata is None:
87 self._size = FROM_P2 97 self._mode = None
88 self._mtime = AMBIGUOUS_TIME 98 self._size = None
89 elif not (p1_tracked or p2_tracked) and wc_tracked: 99 self._mtime = None
90 self._state = b'a' 100 else:
91 self._size = NONNORMAL
92 self._mtime = AMBIGUOUS_TIME
93 elif (p1_tracked or p2_tracked) and not wc_tracked:
94 self._state = b'r'
95 self._size = 0
96 self._mtime = 0
97 elif clean_p2 and wc_tracked:
98 self._state = b'n'
99 self._size = FROM_P2
100 self._mtime = AMBIGUOUS_TIME
101 elif not p1_tracked and p2_tracked and wc_tracked:
102 self._state = b'n'
103 self._size = FROM_P2
104 self._mtime = AMBIGUOUS_TIME
105 elif possibly_dirty:
106 self._state = b'n'
107 self._size = NONNORMAL
108 self._mtime = AMBIGUOUS_TIME
109 elif wc_tracked:
110 # this is a "normal" file
111 if parentfiledata is None:
112 msg = b'failed to pass parentfiledata for a normal file'
113 raise error.ProgrammingError(msg)
114 self._state = b'n'
115 self._mode = parentfiledata[0] 101 self._mode = parentfiledata[0]
116 self._size = parentfiledata[1] 102 self._size = parentfiledata[1]
117 self._mtime = parentfiledata[2] 103 self._mtime = parentfiledata[2]
118 else:
119 assert False, 'unreachable'
120 104
121 @classmethod 105 @classmethod
122 def new_added(cls): 106 def new_added(cls):
123 """constructor to help legacy API to build a new "added" item 107 """constructor to help legacy API to build a new "added" item
124 108
125 Should eventually be removed 109 Should eventually be removed
126 """ 110 """
127 instance = cls() 111 instance = cls()
128 instance._state = b'a' 112 instance._wc_tracked = True
129 instance._mode = 0 113 instance._p1_tracked = False
130 instance._size = NONNORMAL 114 instance._p2_tracked = False
131 instance._mtime = AMBIGUOUS_TIME
132 return instance 115 return instance
133 116
134 @classmethod 117 @classmethod
135 def new_merged(cls): 118 def new_merged(cls):
136 """constructor to help legacy API to build a new "merged" item 119 """constructor to help legacy API to build a new "merged" item
137 120
138 Should eventually be removed 121 Should eventually be removed
139 """ 122 """
140 instance = cls() 123 instance = cls()
141 instance._state = b'm' 124 instance._wc_tracked = True
142 instance._mode = 0 125 instance._p1_tracked = True # might not be True because of rename ?
143 instance._size = FROM_P2 126 instance._p2_tracked = True # might not be True because of rename ?
144 instance._mtime = AMBIGUOUS_TIME 127 instance._merged = True
145 return instance 128 return instance
146 129
147 @classmethod 130 @classmethod
148 def new_from_p2(cls): 131 def new_from_p2(cls):
149 """constructor to help legacy API to build a new "from_p2" item 132 """constructor to help legacy API to build a new "from_p2" item
150 133
151 Should eventually be removed 134 Should eventually be removed
152 """ 135 """
153 instance = cls() 136 instance = cls()
154 instance._state = b'n' 137 instance._wc_tracked = True
155 instance._mode = 0 138 instance._p1_tracked = False # might actually be True
156 instance._size = FROM_P2 139 instance._p2_tracked = True
157 instance._mtime = AMBIGUOUS_TIME 140 instance._clean_p2 = True
158 return instance 141 return instance
159 142
160 @classmethod 143 @classmethod
161 def new_possibly_dirty(cls): 144 def new_possibly_dirty(cls):
162 """constructor to help legacy API to build a new "possibly_dirty" item 145 """constructor to help legacy API to build a new "possibly_dirty" item
163 146
164 Should eventually be removed 147 Should eventually be removed
165 """ 148 """
166 instance = cls() 149 instance = cls()
167 instance._state = b'n' 150 instance._wc_tracked = True
168 instance._mode = 0 151 instance._p1_tracked = True
169 instance._size = NONNORMAL 152 instance._possibly_dirty = True
170 instance._mtime = AMBIGUOUS_TIME
171 return instance 153 return instance
172 154
173 @classmethod 155 @classmethod
174 def new_normal(cls, mode, size, mtime): 156 def new_normal(cls, mode, size, mtime):
175 """constructor to help legacy API to build a new "normal" item 157 """constructor to help legacy API to build a new "normal" item
177 Should eventually be removed 159 Should eventually be removed
178 """ 160 """
179 assert size != FROM_P2 161 assert size != FROM_P2
180 assert size != NONNORMAL 162 assert size != NONNORMAL
181 instance = cls() 163 instance = cls()
182 instance._state = b'n' 164 instance._wc_tracked = True
165 instance._p1_tracked = True
183 instance._mode = mode 166 instance._mode = mode
184 instance._size = size 167 instance._size = size
185 instance._mtime = mtime 168 instance._mtime = mtime
186 return instance 169 return instance
187 170
190 """Build a new DirstateItem object from V1 data 173 """Build a new DirstateItem object from V1 data
191 174
192 Since the dirstate-v1 format is frozen, the signature of this function 175 Since the dirstate-v1 format is frozen, the signature of this function
193 is not expected to change, unlike the __init__ one. 176 is not expected to change, unlike the __init__ one.
194 """ 177 """
195 instance = cls() 178 if state == b'm':
196 instance._state = state 179 return cls.new_merged()
197 instance._mode = mode 180 elif state == b'a':
198 instance._size = size 181 return cls.new_added()
199 instance._mtime = mtime 182 elif state == b'r':
200 return instance 183 instance = cls()
184 instance._wc_tracked = False
185 if size == NONNORMAL:
186 instance._merged = True
187 instance._p1_tracked = (
188 True # might not be True because of rename ?
189 )
190 instance._p2_tracked = (
191 True # might not be True because of rename ?
192 )
193 elif size == FROM_P2:
194 instance._clean_p2 = True
195 instance._p1_tracked = (
196 False # We actually don't know (file history)
197 )
198 instance._p2_tracked = True
199 else:
200 instance._p1_tracked = True
201 return instance
202 elif state == b'n':
203 if size == FROM_P2:
204 return cls.new_from_p2()
205 elif size == NONNORMAL:
206 return cls.new_possibly_dirty()
207 elif mtime == AMBIGUOUS_TIME:
208 instance = cls.new_normal(mode, size, 42)
209 instance._mtime = None
210 instance._possibly_dirty = True
211 return instance
212 else:
213 return cls.new_normal(mode, size, mtime)
214 else:
215 raise RuntimeError(b'unknown state: %s' % state)
201 216
202 def set_possibly_dirty(self): 217 def set_possibly_dirty(self):
203 """Mark a file as "possibly dirty" 218 """Mark a file as "possibly dirty"
204 219
205 This means the next status call will have to actually check its content 220 This means the next status call will have to actually check its content
206 to make sure it is correct. 221 to make sure it is correct.
207 """ 222 """
208 self._mtime = AMBIGUOUS_TIME 223 self._possibly_dirty = True
209 224
210 def set_untracked(self): 225 def set_untracked(self):
211 """mark a file as untracked in the working copy 226 """mark a file as untracked in the working copy
212 227
213 This will ultimately be called by command like `hg remove`. 228 This will ultimately be called by command like `hg remove`.
214 """ 229 """
215 # backup the previous state (useful for merge) 230 # backup the previous state (useful for merge)
216 size = 0 231 self._wc_tracked = False
217 if self.merged: # merge 232 self._mode = None
218 size = NONNORMAL 233 self._size = None
219 elif self.from_p2: 234 self._mtime = None
220 size = FROM_P2
221 self._state = b'r'
222 self._mode = 0
223 self._size = size
224 self._mtime = 0
225 235
226 @property 236 @property
227 def mode(self): 237 def mode(self):
228 return self.v1_mode() 238 return self.v1_mode()
229 239
317 """ 327 """
318 return self.v1_size() == FROM_P2 328 return self.v1_size() == FROM_P2
319 329
320 def v1_state(self): 330 def v1_state(self):
321 """return a "state" suitable for v1 serialization""" 331 """return a "state" suitable for v1 serialization"""
322 return self._state 332 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
333 # the object has no state to record, this is -currently-
334 # unsupported
335 raise RuntimeError('untracked item')
336 elif not self._wc_tracked:
337 return b'r'
338 elif self._merged:
339 return b'm'
340 elif not (self._p1_tracked or self._p2_tracked) and self._wc_tracked:
341 return b'a'
342 elif self._clean_p2 and self._wc_tracked:
343 return b'n'
344 elif not self._p1_tracked and self._p2_tracked and self._wc_tracked:
345 return b'n'
346 elif self._possibly_dirty:
347 return b'n'
348 elif self._wc_tracked:
349 return b'n'
350 else:
351 raise RuntimeError('unreachable')
323 352
324 def v1_mode(self): 353 def v1_mode(self):
325 """return a "mode" suitable for v1 serialization""" 354 """return a "mode" suitable for v1 serialization"""
326 return self._mode 355 return self._mode if self._mode is not None else 0
327 356
328 def v1_size(self): 357 def v1_size(self):
329 """return a "size" suitable for v1 serialization""" 358 """return a "size" suitable for v1 serialization"""
330 return self._size 359 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
360 # the object has no state to record, this is -currently-
361 # unsupported
362 raise RuntimeError('untracked item')
363 elif not self._wc_tracked:
364 # File was deleted
365 if self._merged:
366 return NONNORMAL
367 elif self._clean_p2:
368 return FROM_P2
369 else:
370 return 0
371 elif self._merged:
372 return FROM_P2
373 elif not (self._p1_tracked or self._p2_tracked) and self._wc_tracked:
374 # Added
375 return NONNORMAL
376 elif self._clean_p2 and self._wc_tracked:
377 return FROM_P2
378 elif not self._p1_tracked and self._p2_tracked and self._wc_tracked:
379 return FROM_P2
380 elif self._possibly_dirty:
381 if self._size is None:
382 return NONNORMAL
383 else:
384 return self._size
385 elif self._wc_tracked:
386 return self._size
387 else:
388 raise RuntimeError('unreachable')
331 389
332 def v1_mtime(self): 390 def v1_mtime(self):
333 """return a "mtime" suitable for v1 serialization""" 391 """return a "mtime" suitable for v1 serialization"""
334 return self._mtime 392 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
393 # the object has no state to record, this is -currently-
394 # unsupported
395 raise RuntimeError('untracked item')
396 elif not self._wc_tracked:
397 return 0
398 elif self._possibly_dirty:
399 return AMBIGUOUS_TIME
400 elif self._merged:
401 return AMBIGUOUS_TIME
402 elif not (self._p1_tracked or self._p2_tracked) and self._wc_tracked:
403 return AMBIGUOUS_TIME
404 elif self._clean_p2 and self._wc_tracked:
405 return AMBIGUOUS_TIME
406 elif not self._p1_tracked and self._p2_tracked and self._wc_tracked:
407 return AMBIGUOUS_TIME
408 elif self._wc_tracked:
409 if self._mtime is None:
410 return 0
411 else:
412 return self._mtime
413 else:
414 raise RuntimeError('unreachable')
335 415
336 def need_delay(self, now): 416 def need_delay(self, now):
337 """True if the stored mtime would be ambiguous with the current time""" 417 """True if the stored mtime would be ambiguous with the current time"""
338 return self.v1_state() == b'n' and self.v1_mtime() == now 418 return self.v1_state() == b'n' and self.v1_mtime() == now
339 419