[PATCH 1 of 2 V2] phases: add set per phase in C phase computation

Pierre-Yves David pierre-yves.david at ens-lyon.org
Fri May 15 15:51:59 CDT 2015



On 05/12/2015 02:13 PM, Laurent Charignon wrote:
> # HG changeset patch
> # User Laurent Charignon <lcharignon at fb.com>
> # Date 1427912237 25200
> #      Wed Apr 01 11:17:17 2015 -0700
> # Node ID 3ac4006b09e1bf149714eb444caa2452720ae4aa
> # Parent  e9edd53770fb77a9787a3e6592a3bf0a29c1bd80
> phases: add set per phase in C phase computation
>
> To speed up the computation of draft(), secret(), divergent(), obsolete() and
> unstable() we need to have a fast way of getting the list of revisions that
> are in draft(), secret() or the union of both: not public().
>
> This patch extends the work on phase computation in C and make the phase
> computation code also return a list of set for each non public phase.
> Using these sets we can quickly obtain all the revisions of a given phase.
> We do not return a set for the public phase as we expect it to be roughly the
> size of the repo. Also, it can be computed easily by substracting the entries in the
> non public phases from all the revs in the repo.
>
> diff --git a/mercurial/parsers.c b/mercurial/parsers.c
> --- a/mercurial/parsers.c
> +++ b/mercurial/parsers.c
> @@ -1071,14 +1071,17 @@
>   		phases[i] = phases[parent_2];
>   }
>
> -static PyObject *compute_phases(indexObject *self, PyObject *args)
> +static PyObject *compute_revs_phases(indexObject *self, PyObject *args)

I'm not sure if the C function name have to change. (but the python name 
must, as pointed by Matt). However I'm not fan of the new name.
'compute_revs_phases' is a bit vague. What about something more 
informative 'compute_phases_map_sets' to convey the fact we return a 
{rev → phase} map (as a list) and sets for each phases?

>   {
>   	PyObject *roots = Py_None;
> +	PyObject *ret = NULL;
>   	PyObject *phaseslist = NULL;
>   	PyObject *phaseroots = NULL;
>   	PyObject *rev = NULL;
>   	PyObject *p1 = NULL;
>   	PyObject *p2 = NULL;
> +	PyObject *phaseset = NULL;
> +	PyObject *phasessetlist = NULL;
>   	Py_ssize_t addlen = self->added ? PyList_GET_SIZE(self->added) : 0;
>   	Py_ssize_t len = index_length(self) - 1;
>   	Py_ssize_t numphase = 0;
> @@ -1088,6 +1091,7 @@
>   	int parent_1, parent_2;
>   	char *phases = NULL;
>   	const char *data;
> +	long phase;
>
>   	if (!PyArg_ParseTuple(args, "O", &roots))
>   		goto release_none;
> @@ -1100,13 +1104,24 @@
>   	/* Put the phase information of all the roots in phases */
>   	numphase = PyList_GET_SIZE(roots)+1;
>   	minrevallphases = len + 1;
> +	phasessetlist = PyList_New(numphase);
> +	if (phasessetlist == NULL)
> +		goto release_none;
> +
> +	PyList_SET_ITEM(phasessetlist, 0, Py_None);
> +	Py_INCREF(Py_None);
> +
>   	for (i = 0; i < numphase-1; i++) {
>   		phaseroots = PyList_GET_ITEM(roots, i);
> +		phaseset = PySet_New(NULL);
> +		if (phaseset == NULL)
> +			goto release_phasesetlist;
> +		PyList_SET_ITEM(phasessetlist, i+1, phaseset);
>   		if (!PyList_Check(phaseroots))
> -			goto release_phases;
> +			goto release_phasesetlist;
>   		minrevphase = add_roots_get_min(self, phaseroots, i+1, phases);
>   		if (minrevphase == -2) /* Error from add_roots_get_min */
> -			goto release_phases;
> +			goto release_phasesetlist;
>   		minrevallphases = MIN(minrevallphases, minrevphase);
>   	}
>   	/* Propagate the phase information from the roots to the revs */
> @@ -1121,7 +1136,7 @@
>   			p2 = PyTuple_GET_ITEM(rev, 6);
>   			if (!PyInt_Check(p1) || !PyInt_Check(p2)) {
>   				PyErr_SetString(PyExc_TypeError, "revlog parents are invalid");
> -				goto release_phases;
> +				goto release_phasesetlist;
>   			}
>   			parent_1 = (int)PyInt_AS_LONG(p1);
>   			parent_2 = (int)PyInt_AS_LONG(p2);
> @@ -1131,14 +1146,35 @@
>   	/* Transform phase list to a python list */
>   	phaseslist = PyList_New(len);
>   	if (phaseslist == NULL)
> -		goto release_phases;
> -	for (i = 0; i < len; i++)
> -		PyList_SET_ITEM(phaseslist, i, PyInt_FromLong(phases[i]));
> +		goto release_phasesetlist;
> +	for (i = 0; i < len; i++) {
> +		phase = phases[i];
> +		/* We only store the sets of phase for non public phase, the public phase
> +		 * is computed as a difference */
> +		if (phase != 0) {
> +			phaseset = PyList_GET_ITEM(phasessetlist, phase);
> +			PySet_Add(phaseset, PyInt_FromLong(i));
> +		}
> +		PyList_SET_ITEM(phaseslist, i, PyInt_FromLong(phase));
> +	}
> +	ret = PyList_New(2);
> +	if (ret == NULL)
> +		goto release_phaseslist;
>
> +	PyList_SET_ITEM(ret, 0, phaseslist);
> +	PyList_SET_ITEM(ret, 1, phasessetlist);
> +	/* We don't release phaseslist and phasessetlist as we return them to
> +	 * python */
> +	goto release_phases;
> +
> +release_phaseslist:
> +	Py_XDECREF(phaseslist);
> +release_phasesetlist:
> +	Py_XDECREF(phasessetlist);
>   release_phases:
>   	free(phases);
>   release_none:
> -	return phaseslist;
> +	return ret;
>   }
>
>   static PyObject *index_headrevs(indexObject *self, PyObject *args)
> @@ -2278,7 +2314,7 @@
>   	 "clear the index caches"},
>   	{"get", (PyCFunction)index_m_get, METH_VARARGS,
>   	 "get an index entry"},
> -	{"computephases", (PyCFunction)compute_phases, METH_VARARGS,
> +	{"computephases", (PyCFunction)compute_revs_phases, METH_VARARGS,
>   		"compute phases"},
>   	{"headrevs", (PyCFunction)index_headrevs, METH_VARARGS,
>   	 "get head revisions"}, /* Can do filtering since 3.2 */
> diff --git a/mercurial/phases.py b/mercurial/phases.py
> --- a/mercurial/phases.py
> +++ b/mercurial/phases.py
> @@ -99,7 +99,6 @@
>   - content is pushed as draft
>
>   """
> -

unrelated change

>   import os
>   import errno
>   from node import nullid, nullrev, bin, hex, short
> @@ -155,6 +154,7 @@
>               # Cheap trick to allow shallow-copy without copy module
>               self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
>               self._phaserevs = None
> +            self._phasesets = None
>               self.filterunknown(repo)
>               self.opener = repo.svfs
>
> @@ -199,7 +199,8 @@
>                                         'nativephaseskillswitch'):
>                       self._computephaserevspure(repo)
>                   else:
> -                    self._phaserevs = self._getphaserevsnative(repo)
> +                    res = self._getphaserevsnative(repo)
> +                    self._phaserevs, self._phasesets = res

As pointed by matt, no change in the function python side, So old/new 
code will not recognised new/old C code.

>               except AttributeError:
>                   self._computephaserevspure(repo)

Unrelated to this patch, but explicitly testing for the attribute 
(util.safehasattr) would be safer. the current code can shadow other 
legitimate Attribute Error.

-- 
Pierre-Yves David


More information about the Mercurial-devel mailing list