revlog-index: add `replace_sidedata_info` method
During a `pull` operation where the server does not provide sidedata, the client
that requires it should generate them on-the-fly. In the generic case, we need
to wait for the changelog + manifests + filelogs to be added, since we don't
know what the sidedata computers might need: this means rewriting the sidedata
of index entries from within the pull transaction (and no further back) right
after we've added them.
Both Python and C implementations only allow for rewriting the sidedata offset
and length for revs within the transaction where they were created.
Differential Revision: https://phab.mercurial-scm.org/D10031
--- a/mercurial/cext/revlog.c Fri Feb 19 11:04:17 2021 +0100
+++ b/mercurial/cext/revlog.c Mon Feb 15 11:08:28 2021 +0100
@@ -458,6 +458,56 @@
Py_RETURN_NONE;
}
+/* Replace an existing index entry's sidedata offset and length with new ones.
+ This cannot be used outside of the context of sidedata rewriting,
+ inside the transaction that creates the given revision. */
+static PyObject *index_replace_sidedata_info(indexObject *self, PyObject *args)
+{
+ uint64_t sidedata_offset;
+ int rev;
+ Py_ssize_t sidedata_comp_len;
+ char *data;
+ #if LONG_MAX == 0x7fffffffL
+ const char *const sidedata_format = PY23("nKi", "nKi");
+ #else
+ const char *const sidedata_format = PY23("nki", "nki");
+ #endif
+
+ if (self->hdrsize == v1_hdrsize || self->inlined) {
+ /*
+ There is a bug in the transaction handling when going from an
+ inline revlog to a separate index and data file. Turn it off until
+ it's fixed, since v2 revlogs sometimes get rewritten on exchange.
+ See issue6485.
+ */
+ raise_revlog_error();
+ return NULL;
+ }
+
+ if (!PyArg_ParseTuple(args, sidedata_format, &rev, &sidedata_offset,
+ &sidedata_comp_len))
+ return NULL;
+
+ if (rev < 0 || rev >= index_length(self)) {
+ PyErr_SetString(PyExc_IndexError, "revision outside index");
+ return NULL;
+ }
+ if (rev < self->length) {
+ PyErr_SetString(
+ PyExc_IndexError,
+ "cannot rewrite entries outside of this transaction");
+ return NULL;
+ }
+
+ /* Find the newly added node, offset from the "already on-disk" length */
+ data = self->added + self->hdrsize * (rev - self->length);
+ putbe64(sidedata_offset, data + 64);
+ putbe32(sidedata_comp_len, data + 72);
+
+
+ Py_RETURN_NONE;
+}
+
static PyObject *index_stats(indexObject *self)
{
PyObject *obj = PyDict_New();
@@ -2789,6 +2839,8 @@
"compute phases"},
{"reachableroots2", (PyCFunction)reachableroots2, METH_VARARGS,
"reachableroots"},
+ {"replace_sidedata_info", (PyCFunction)index_replace_sidedata_info,
+ METH_VARARGS, "replace an existing index entry with a new value"},
{"headrevs", (PyCFunction)index_headrevs, METH_VARARGS,
"get head revisions"}, /* Can do filtering since 3.2 */
{"headrevsfiltered", (PyCFunction)index_headrevs, METH_VARARGS,
--- a/mercurial/pure/parsers.py Fri Feb 19 11:04:17 2021 +0100
+++ b/mercurial/pure/parsers.py Mon Feb 15 11:08:28 2021 +0100
@@ -259,6 +259,27 @@
assert index_size == 96, index_size
null_item = (0, 0, 0, -1, -1, -1, -1, nullid, 0, 0)
+ def replace_sidedata_info(self, i, sidedata_offset, sidedata_length):
+ """
+ Replace an existing index entry's sidedata offset and length with new
+ ones.
+ This cannot be used outside of the context of sidedata rewriting,
+ inside the transaction that creates the revision `i`.
+ """
+ if i < 0:
+ raise KeyError
+ self._check_index(i)
+ sidedata_format = b">Qi"
+ packed_size = struct.calcsize(sidedata_format)
+ if i >= self._lgt:
+ packed = _pack(sidedata_format, sidedata_offset, sidedata_length)
+ old = self._extra[i - self._lgt]
+ new = old[:64] + packed + old[64 + packed_size :]
+ self._extra[i - self._lgt] = new
+ else:
+ msg = b"cannot rewrite entries outside of this transaction"
+ raise KeyError(msg)
+
class IndexObject2(Index2Mixin, IndexObject):
pass