Mercurial > public > mercurial-scm > hg
comparison mercurial/dirstate.py @ 49958:96e526fe5fb0
dirstate: invalidate changes when parent-change fails
When an error occurs during changing parents, we should invalidate all dirstate
modifications and reload the dirstate. This is currently done by a `unlock`
callback on the `wlock`.
To fix this anomaly, we start dealing with the error directly in the context
manager and its potential nesting.
The "hard" part is to make sure that, when the parent-change context are nested,
we and higher level nesting do not continue to use the invalidated dirstate.
We introduce dedicated code to enforce that.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Tue, 13 Dec 2022 11:39:44 +0100 |
parents | d09a57ce6fc4 |
children | c166b212bdee |
comparison
equal
deleted
inserted
replaced
49957:ff4df0954742 | 49958:96e526fe5fb0 |
---|---|
69 def requires_parents_change(func): | 69 def requires_parents_change(func): |
70 def wrap(self, *args, **kwargs): | 70 def wrap(self, *args, **kwargs): |
71 if not self.pendingparentchange(): | 71 if not self.pendingparentchange(): |
72 msg = 'calling `%s` outside of a parentchange context' | 72 msg = 'calling `%s` outside of a parentchange context' |
73 msg %= func.__name__ | 73 msg %= func.__name__ |
74 raise error.ProgrammingError(msg) | |
75 if self._invalidated_context: | |
76 msg = 'calling `%s` after the dirstate was invalidated' | |
74 raise error.ProgrammingError(msg) | 77 raise error.ProgrammingError(msg) |
75 return func(self, *args, **kwargs) | 78 return func(self, *args, **kwargs) |
76 | 79 |
77 return wrap | 80 return wrap |
78 | 81 |
122 self._dirty = False | 125 self._dirty = False |
123 # True if the set of tracked file may be different | 126 # True if the set of tracked file may be different |
124 self._dirty_tracked_set = False | 127 self._dirty_tracked_set = False |
125 self._ui = ui | 128 self._ui = ui |
126 self._filecache = {} | 129 self._filecache = {} |
130 # nesting level of `parentchange` context | |
127 self._parentwriters = 0 | 131 self._parentwriters = 0 |
132 # True if the current dirstate changing operations have been | |
133 # invalidated (used to make sure all nested contexts have been exited) | |
134 self._invalidated_context = False | |
128 self._filename = b'dirstate' | 135 self._filename = b'dirstate' |
129 self._filename_th = b'dirstate-tracked-hint' | 136 self._filename_th = b'dirstate-tracked-hint' |
130 self._pendingfilename = b'%s.pending' % self._filename | 137 self._pendingfilename = b'%s.pending' % self._filename |
131 self._plchangecallbacks = {} | 138 self._plchangecallbacks = {} |
132 self._origpl = None | 139 self._origpl = None |
149 | 156 |
150 If an exception occurs in the scope of the context manager, | 157 If an exception occurs in the scope of the context manager, |
151 the incoherent dirstate won't be written when wlock is | 158 the incoherent dirstate won't be written when wlock is |
152 released. | 159 released. |
153 """ | 160 """ |
161 if self._invalidated_context: | |
162 msg = "trying to use an invalidated dirstate before it has reset" | |
163 raise error.ProgrammingError(msg) | |
154 self._parentwriters += 1 | 164 self._parentwriters += 1 |
155 yield | 165 try: |
156 # Typically we want the "undo" step of a context manager in a | 166 yield |
157 # finally block so it happens even when an exception | 167 except Exception: |
158 # occurs. In this case, however, we only want to decrement | 168 self.invalidate() |
159 # parentwriters if the code in the with statement exits | 169 raise |
160 # normally, so we don't have a try/finally here on purpose. | 170 finally: |
161 self._parentwriters -= 1 | 171 if self._parentwriters > 0: |
172 if self._invalidated_context: | |
173 # make sure we invalidate anything an upper context might | |
174 # have changed. | |
175 self.invalidate() | |
176 self._parentwriters -= 1 | |
177 # The invalidation is complete once we exit the final context | |
178 # manager | |
179 if self._parentwriters <= 0: | |
180 assert self._parentwriters == 0 | |
181 self._invalidated_context = False | |
162 | 182 |
163 def pendingparentchange(self): | 183 def pendingparentchange(self): |
164 """Returns true if the dirstate is in the middle of a set of changes | 184 """Returns true if the dirstate is in the middle of a set of changes |
165 that modify the dirstate parent. | 185 that modify the dirstate parent. |
166 """ | 186 """ |
417 for a in ("_map", "_branch", "_ignore"): | 437 for a in ("_map", "_branch", "_ignore"): |
418 if a in self.__dict__: | 438 if a in self.__dict__: |
419 delattr(self, a) | 439 delattr(self, a) |
420 self._dirty = False | 440 self._dirty = False |
421 self._dirty_tracked_set = False | 441 self._dirty_tracked_set = False |
422 self._parentwriters = 0 | 442 self._invalidated_context = self._parentwriters > 0 |
423 self._origpl = None | 443 self._origpl = None |
424 | 444 |
425 def copy(self, source, dest): | 445 def copy(self, source, dest): |
426 """Mark dest as a copy of source. Unmark dest if source is None.""" | 446 """Mark dest as a copy of source. Unmark dest if source is None.""" |
427 if source == dest: | 447 if source == dest: |