diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -56,6 +56,7 @@ self._nodelen = 20 # Also update Rust code when changing this! self._parents = None self._dirtyparents = False + self._docket = None # for consistent view between _pl() and _read() invocations self._pendingmode = None @@ -208,6 +209,93 @@ ) self._insert_entry(filename, entry) + ### disk interaction + + def _opendirstatefile(self): + fp, mode = txnutil.trypending(self._root, self._opener, self._filename) + if self._pendingmode is not None and self._pendingmode != mode: + fp.close() + raise error.Abort( + _(b'working directory state may be changed parallelly') + ) + self._pendingmode = mode + return fp + + def _readdirstatefile(self, size=-1): + try: + with self._opendirstatefile() as fp: + return fp.read(size) + except IOError as err: + if err.errno != errno.ENOENT: + raise + # File doesn't exist, so the current state is empty + return b'' + + @property + def docket(self): + if not self._docket: + if not self._use_dirstate_v2: + raise error.ProgrammingError( + b'dirstate only has a docket in v2 format' + ) + self._docket = docketmod.DirstateDocket.parse( + self._readdirstatefile(), self._nodeconstants + ) + return self._docket + + def write_v2_no_append(self, tr, st, meta, packed): + old_docket = self.docket + new_docket = docketmod.DirstateDocket.with_new_uuid( + self.parents(), len(packed), meta + ) + data_filename = new_docket.data_filename() + if tr: + tr.add(data_filename, 0) + self._opener.write(data_filename, packed) + # Write the new docket after the new data file has been + # written. Because `st` was opened with `atomictemp=True`, + # the actual `.hg/dirstate` file is only affected on close. + st.write(new_docket.serialize()) + st.close() + # Remove the old data file after the new docket pointing to + # the new data file was written. + if old_docket.uuid: + data_filename = old_docket.data_filename() + unlink = lambda _tr=None: self._opener.unlink(data_filename) + if tr: + category = b"dirstate-v2-clean-" + old_docket.uuid + tr.addpostclose(category, unlink) + else: + unlink() + self._docket = new_docket + + ### reading/setting parents + + def parents(self): + if not self._parents: + if self._use_dirstate_v2: + self._parents = self.docket.parents + else: + read_len = self._nodelen * 2 + st = self._readdirstatefile(read_len) + l = len(st) + if l == read_len: + self._parents = ( + st[: self._nodelen], + st[self._nodelen : 2 * self._nodelen], + ) + elif l == 0: + self._parents = ( + self._nodeconstants.nullid, + self._nodeconstants.nullid, + ) + else: + raise error.Abort( + _(b'working directory state appears damaged!') + ) + + return self._parents + class dirstatemap(_dirstatemapcommon): """Map encapsulating the dirstate's contents. @@ -295,36 +383,6 @@ ### reading/setting parents - def parents(self): - if not self._parents: - try: - fp = self._opendirstatefile() - st = fp.read(2 * self._nodelen) - fp.close() - except IOError as err: - if err.errno != errno.ENOENT: - raise - # File doesn't exist, so the current state is empty - st = b'' - - l = len(st) - if l == self._nodelen * 2: - self._parents = ( - st[: self._nodelen], - st[self._nodelen : 2 * self._nodelen], - ) - elif l == 0: - self._parents = ( - self._nodeconstants.nullid, - self._nodeconstants.nullid, - ) - else: - raise error.Abort( - _(b'working directory state appears damaged!') - ) - - return self._parents - def setparents(self, p1, p2, fold_p2=False): self._parents = (p1, p2) self._dirtyparents = True @@ -397,16 +455,6 @@ st.close() self._dirtyparents = False - def _opendirstatefile(self): - fp, mode = txnutil.trypending(self._root, self._opener, self._filename) - if self._pendingmode is not None and self._pendingmode != mode: - fp.close() - raise error.Abort( - _(b'working directory state may be changed parallelly') - ) - self._pendingmode = mode - return fp - @propertycache def identity(self): self._map @@ -506,26 +554,9 @@ if rustmod is not None: class dirstatemap(_dirstatemapcommon): - def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2): - super(dirstatemap, self).__init__( - ui, opener, root, nodeconstants, use_dirstate_v2 - ) - self._docket = None ### Core data storage and access - @property - def docket(self): - if not self._docket: - if not self._use_dirstate_v2: - raise error.ProgrammingError( - b'dirstate only has a docket in v2 format' - ) - self._docket = docketmod.DirstateDocket.parse( - self._readdirstatefile(), self._nodeconstants - ) - return self._docket - @propertycache def _map(self): """ @@ -616,31 +647,6 @@ rust_map.set_dirstate_item(f, e) return copies - def parents(self): - if not self._parents: - if self._use_dirstate_v2: - self._parents = self.docket.parents - else: - read_len = self._nodelen * 2 - st = self._readdirstatefile(read_len) - l = len(st) - if l == read_len: - self._parents = ( - st[: self._nodelen], - st[self._nodelen : 2 * self._nodelen], - ) - elif l == 0: - self._parents = ( - self._nodeconstants.nullid, - self._nodeconstants.nullid, - ) - else: - raise error.Abort( - _(b'working directory state appears damaged!') - ) - - return self._parents - ### disk interaction @propertycache @@ -677,56 +683,11 @@ st.write(docket.serialize()) st.close() else: - old_docket = self.docket - new_docket = docketmod.DirstateDocket.with_new_uuid( - self.parents(), len(packed), meta - ) - data_filename = new_docket.data_filename() - if tr: - tr.add(data_filename, 0) - self._opener.write(data_filename, packed) - # Write the new docket after the new data file has been - # written. Because `st` was opened with `atomictemp=True`, - # the actual `.hg/dirstate` file is only affected on close. - st.write(new_docket.serialize()) - st.close() - # Remove the old data file after the new docket pointing to - # the new data file was written. - if old_docket.uuid: - data_filename = old_docket.data_filename() - unlink = lambda _tr=None: self._opener.unlink(data_filename) - if tr: - category = b"dirstate-v2-clean-" + old_docket.uuid - tr.addpostclose(category, unlink) - else: - unlink() - self._docket = new_docket + self.write_v2_no_append(tr, st, meta, packed) # Reload from the newly-written file util.clearcachedproperty(self, b"_map") self._dirtyparents = False - def _opendirstatefile(self): - fp, mode = txnutil.trypending( - self._root, self._opener, self._filename - ) - if self._pendingmode is not None and self._pendingmode != mode: - fp.close() - raise error.Abort( - _(b'working directory state may be changed parallelly') - ) - self._pendingmode = mode - return fp - - def _readdirstatefile(self, size=-1): - try: - with self._opendirstatefile() as fp: - return fp.read(size) - except IOError as err: - if err.errno != errno.ENOENT: - raise - # File doesn't exist, so the current state is empty - return b'' - ### code related to maintaining and accessing "extra" property # (e.g. "has_dir")