Mercurial > hg
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 |