##// END OF EJS Templates
phases: sparsify phaseroots and phasesets...
Joerg Sonnenberger -
r45691:61e74644 default
parent child Browse files
Show More
@@ -1,762 +1,762 b''
1 1 /*
2 2 parsers.c - efficient content parsing
3 3
4 4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #define PY_SSIZE_T_CLEAN
11 11 #include <Python.h>
12 12 #include <ctype.h>
13 13 #include <stddef.h>
14 14 #include <string.h>
15 15
16 16 #include "bitmanipulation.h"
17 17 #include "charencode.h"
18 18 #include "util.h"
19 19
20 20 #ifdef IS_PY3K
21 21 /* The mapping of Python types is meant to be temporary to get Python
22 22 * 3 to compile. We should remove this once Python 3 support is fully
23 23 * supported and proper types are used in the extensions themselves. */
24 24 #define PyInt_Check PyLong_Check
25 25 #define PyInt_FromLong PyLong_FromLong
26 26 #define PyInt_FromSsize_t PyLong_FromSsize_t
27 27 #define PyInt_AsLong PyLong_AsLong
28 28 #endif
29 29
30 30 static const char *const versionerrortext = "Python minor version mismatch";
31 31
32 32 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
33 33 {
34 34 Py_ssize_t expected_size;
35 35
36 36 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
37 37 return NULL;
38 38 }
39 39
40 40 return _dict_new_presized(expected_size);
41 41 }
42 42
43 43 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
44 44 int size, int mtime)
45 45 {
46 46 dirstateTupleObject *t =
47 47 PyObject_New(dirstateTupleObject, &dirstateTupleType);
48 48 if (!t) {
49 49 return NULL;
50 50 }
51 51 t->state = state;
52 52 t->mode = mode;
53 53 t->size = size;
54 54 t->mtime = mtime;
55 55 return t;
56 56 }
57 57
58 58 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
59 59 PyObject *kwds)
60 60 {
61 61 /* We do all the initialization here and not a tp_init function because
62 62 * dirstate_tuple is immutable. */
63 63 dirstateTupleObject *t;
64 64 char state;
65 65 int size, mode, mtime;
66 66 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
67 67 return NULL;
68 68 }
69 69
70 70 t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1);
71 71 if (!t) {
72 72 return NULL;
73 73 }
74 74 t->state = state;
75 75 t->mode = mode;
76 76 t->size = size;
77 77 t->mtime = mtime;
78 78
79 79 return (PyObject *)t;
80 80 }
81 81
82 82 static void dirstate_tuple_dealloc(PyObject *o)
83 83 {
84 84 PyObject_Del(o);
85 85 }
86 86
87 87 static Py_ssize_t dirstate_tuple_length(PyObject *o)
88 88 {
89 89 return 4;
90 90 }
91 91
92 92 static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i)
93 93 {
94 94 dirstateTupleObject *t = (dirstateTupleObject *)o;
95 95 switch (i) {
96 96 case 0:
97 97 return PyBytes_FromStringAndSize(&t->state, 1);
98 98 case 1:
99 99 return PyInt_FromLong(t->mode);
100 100 case 2:
101 101 return PyInt_FromLong(t->size);
102 102 case 3:
103 103 return PyInt_FromLong(t->mtime);
104 104 default:
105 105 PyErr_SetString(PyExc_IndexError, "index out of range");
106 106 return NULL;
107 107 }
108 108 }
109 109
110 110 static PySequenceMethods dirstate_tuple_sq = {
111 111 dirstate_tuple_length, /* sq_length */
112 112 0, /* sq_concat */
113 113 0, /* sq_repeat */
114 114 dirstate_tuple_item, /* sq_item */
115 115 0, /* sq_ass_item */
116 116 0, /* sq_contains */
117 117 0, /* sq_inplace_concat */
118 118 0 /* sq_inplace_repeat */
119 119 };
120 120
121 121 PyTypeObject dirstateTupleType = {
122 122 PyVarObject_HEAD_INIT(NULL, 0) /* header */
123 123 "dirstate_tuple", /* tp_name */
124 124 sizeof(dirstateTupleObject), /* tp_basicsize */
125 125 0, /* tp_itemsize */
126 126 (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
127 127 0, /* tp_print */
128 128 0, /* tp_getattr */
129 129 0, /* tp_setattr */
130 130 0, /* tp_compare */
131 131 0, /* tp_repr */
132 132 0, /* tp_as_number */
133 133 &dirstate_tuple_sq, /* tp_as_sequence */
134 134 0, /* tp_as_mapping */
135 135 0, /* tp_hash */
136 136 0, /* tp_call */
137 137 0, /* tp_str */
138 138 0, /* tp_getattro */
139 139 0, /* tp_setattro */
140 140 0, /* tp_as_buffer */
141 141 Py_TPFLAGS_DEFAULT, /* tp_flags */
142 142 "dirstate tuple", /* tp_doc */
143 143 0, /* tp_traverse */
144 144 0, /* tp_clear */
145 145 0, /* tp_richcompare */
146 146 0, /* tp_weaklistoffset */
147 147 0, /* tp_iter */
148 148 0, /* tp_iternext */
149 149 0, /* tp_methods */
150 150 0, /* tp_members */
151 151 0, /* tp_getset */
152 152 0, /* tp_base */
153 153 0, /* tp_dict */
154 154 0, /* tp_descr_get */
155 155 0, /* tp_descr_set */
156 156 0, /* tp_dictoffset */
157 157 0, /* tp_init */
158 158 0, /* tp_alloc */
159 159 dirstate_tuple_new, /* tp_new */
160 160 };
161 161
162 162 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
163 163 {
164 164 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
165 165 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
166 166 char state, *cur, *str, *cpos;
167 167 int mode, size, mtime;
168 168 unsigned int flen, pos = 40;
169 169 Py_ssize_t len = 40;
170 170 Py_ssize_t readlen;
171 171
172 172 if (!PyArg_ParseTuple(
173 173 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
174 174 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
175 175 goto quit;
176 176 }
177 177
178 178 len = readlen;
179 179
180 180 /* read parents */
181 181 if (len < 40) {
182 182 PyErr_SetString(PyExc_ValueError,
183 183 "too little data for parents");
184 184 goto quit;
185 185 }
186 186
187 187 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
188 188 str + 20, (Py_ssize_t)20);
189 189 if (!parents) {
190 190 goto quit;
191 191 }
192 192
193 193 /* read filenames */
194 194 while (pos >= 40 && pos < len) {
195 195 if (pos + 17 > len) {
196 196 PyErr_SetString(PyExc_ValueError,
197 197 "overflow in dirstate");
198 198 goto quit;
199 199 }
200 200 cur = str + pos;
201 201 /* unpack header */
202 202 state = *cur;
203 203 mode = getbe32(cur + 1);
204 204 size = getbe32(cur + 5);
205 205 mtime = getbe32(cur + 9);
206 206 flen = getbe32(cur + 13);
207 207 pos += 17;
208 208 cur += 17;
209 209 if (flen > len - pos) {
210 210 PyErr_SetString(PyExc_ValueError,
211 211 "overflow in dirstate");
212 212 goto quit;
213 213 }
214 214
215 215 entry =
216 216 (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
217 217 cpos = memchr(cur, 0, flen);
218 218 if (cpos) {
219 219 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
220 220 cname = PyBytes_FromStringAndSize(
221 221 cpos + 1, flen - (cpos - cur) - 1);
222 222 if (!fname || !cname ||
223 223 PyDict_SetItem(cmap, fname, cname) == -1 ||
224 224 PyDict_SetItem(dmap, fname, entry) == -1) {
225 225 goto quit;
226 226 }
227 227 Py_DECREF(cname);
228 228 } else {
229 229 fname = PyBytes_FromStringAndSize(cur, flen);
230 230 if (!fname ||
231 231 PyDict_SetItem(dmap, fname, entry) == -1) {
232 232 goto quit;
233 233 }
234 234 }
235 235 Py_DECREF(fname);
236 236 Py_DECREF(entry);
237 237 fname = cname = entry = NULL;
238 238 pos += flen;
239 239 }
240 240
241 241 ret = parents;
242 242 Py_INCREF(ret);
243 243 quit:
244 244 Py_XDECREF(fname);
245 245 Py_XDECREF(cname);
246 246 Py_XDECREF(entry);
247 247 Py_XDECREF(parents);
248 248 return ret;
249 249 }
250 250
251 251 /*
252 252 * Build a set of non-normal and other parent entries from the dirstate dmap
253 253 */
254 254 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
255 255 {
256 256 PyObject *dmap, *fname, *v;
257 257 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
258 258 Py_ssize_t pos;
259 259
260 260 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
261 261 &dmap)) {
262 262 goto bail;
263 263 }
264 264
265 265 nonnset = PySet_New(NULL);
266 266 if (nonnset == NULL) {
267 267 goto bail;
268 268 }
269 269
270 270 otherpset = PySet_New(NULL);
271 271 if (otherpset == NULL) {
272 272 goto bail;
273 273 }
274 274
275 275 pos = 0;
276 276 while (PyDict_Next(dmap, &pos, &fname, &v)) {
277 277 dirstateTupleObject *t;
278 278 if (!dirstate_tuple_check(v)) {
279 279 PyErr_SetString(PyExc_TypeError,
280 280 "expected a dirstate tuple");
281 281 goto bail;
282 282 }
283 283 t = (dirstateTupleObject *)v;
284 284
285 285 if (t->state == 'n' && t->size == -2) {
286 286 if (PySet_Add(otherpset, fname) == -1) {
287 287 goto bail;
288 288 }
289 289 }
290 290
291 291 if (t->state == 'n' && t->mtime != -1) {
292 292 continue;
293 293 }
294 294 if (PySet_Add(nonnset, fname) == -1) {
295 295 goto bail;
296 296 }
297 297 }
298 298
299 299 result = Py_BuildValue("(OO)", nonnset, otherpset);
300 300 if (result == NULL) {
301 301 goto bail;
302 302 }
303 303 Py_DECREF(nonnset);
304 304 Py_DECREF(otherpset);
305 305 return result;
306 306 bail:
307 307 Py_XDECREF(nonnset);
308 308 Py_XDECREF(otherpset);
309 309 Py_XDECREF(result);
310 310 return NULL;
311 311 }
312 312
313 313 /*
314 314 * Efficiently pack a dirstate object into its on-disk format.
315 315 */
316 316 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
317 317 {
318 318 PyObject *packobj = NULL;
319 319 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
320 320 Py_ssize_t nbytes, pos, l;
321 321 PyObject *k, *v = NULL, *pn;
322 322 char *p, *s;
323 323 int now;
324 324
325 325 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
326 326 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
327 327 &now)) {
328 328 return NULL;
329 329 }
330 330
331 331 if (PyTuple_Size(pl) != 2) {
332 332 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
333 333 return NULL;
334 334 }
335 335
336 336 /* Figure out how much we need to allocate. */
337 337 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
338 338 PyObject *c;
339 339 if (!PyBytes_Check(k)) {
340 340 PyErr_SetString(PyExc_TypeError, "expected string key");
341 341 goto bail;
342 342 }
343 343 nbytes += PyBytes_GET_SIZE(k) + 17;
344 344 c = PyDict_GetItem(copymap, k);
345 345 if (c) {
346 346 if (!PyBytes_Check(c)) {
347 347 PyErr_SetString(PyExc_TypeError,
348 348 "expected string key");
349 349 goto bail;
350 350 }
351 351 nbytes += PyBytes_GET_SIZE(c) + 1;
352 352 }
353 353 }
354 354
355 355 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
356 356 if (packobj == NULL) {
357 357 goto bail;
358 358 }
359 359
360 360 p = PyBytes_AS_STRING(packobj);
361 361
362 362 pn = PyTuple_GET_ITEM(pl, 0);
363 363 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
364 364 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
365 365 goto bail;
366 366 }
367 367 memcpy(p, s, l);
368 368 p += 20;
369 369 pn = PyTuple_GET_ITEM(pl, 1);
370 370 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
371 371 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
372 372 goto bail;
373 373 }
374 374 memcpy(p, s, l);
375 375 p += 20;
376 376
377 377 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
378 378 dirstateTupleObject *tuple;
379 379 char state;
380 380 int mode, size, mtime;
381 381 Py_ssize_t len, l;
382 382 PyObject *o;
383 383 char *t;
384 384
385 385 if (!dirstate_tuple_check(v)) {
386 386 PyErr_SetString(PyExc_TypeError,
387 387 "expected a dirstate tuple");
388 388 goto bail;
389 389 }
390 390 tuple = (dirstateTupleObject *)v;
391 391
392 392 state = tuple->state;
393 393 mode = tuple->mode;
394 394 size = tuple->size;
395 395 mtime = tuple->mtime;
396 396 if (state == 'n' && mtime == now) {
397 397 /* See pure/parsers.py:pack_dirstate for why we do
398 398 * this. */
399 399 mtime = -1;
400 400 mtime_unset = (PyObject *)make_dirstate_tuple(
401 401 state, mode, size, mtime);
402 402 if (!mtime_unset) {
403 403 goto bail;
404 404 }
405 405 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
406 406 goto bail;
407 407 }
408 408 Py_DECREF(mtime_unset);
409 409 mtime_unset = NULL;
410 410 }
411 411 *p++ = state;
412 412 putbe32((uint32_t)mode, p);
413 413 putbe32((uint32_t)size, p + 4);
414 414 putbe32((uint32_t)mtime, p + 8);
415 415 t = p + 12;
416 416 p += 16;
417 417 len = PyBytes_GET_SIZE(k);
418 418 memcpy(p, PyBytes_AS_STRING(k), len);
419 419 p += len;
420 420 o = PyDict_GetItem(copymap, k);
421 421 if (o) {
422 422 *p++ = '\0';
423 423 l = PyBytes_GET_SIZE(o);
424 424 memcpy(p, PyBytes_AS_STRING(o), l);
425 425 p += l;
426 426 len += l + 1;
427 427 }
428 428 putbe32((uint32_t)len, t);
429 429 }
430 430
431 431 pos = p - PyBytes_AS_STRING(packobj);
432 432 if (pos != nbytes) {
433 433 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
434 434 (long)pos, (long)nbytes);
435 435 goto bail;
436 436 }
437 437
438 438 return packobj;
439 439 bail:
440 440 Py_XDECREF(mtime_unset);
441 441 Py_XDECREF(packobj);
442 442 Py_XDECREF(v);
443 443 return NULL;
444 444 }
445 445
446 446 #define BUMPED_FIX 1
447 447 #define USING_SHA_256 2
448 448 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
449 449
450 450 static PyObject *readshas(const char *source, unsigned char num,
451 451 Py_ssize_t hashwidth)
452 452 {
453 453 int i;
454 454 PyObject *list = PyTuple_New(num);
455 455 if (list == NULL) {
456 456 return NULL;
457 457 }
458 458 for (i = 0; i < num; i++) {
459 459 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
460 460 if (hash == NULL) {
461 461 Py_DECREF(list);
462 462 return NULL;
463 463 }
464 464 PyTuple_SET_ITEM(list, i, hash);
465 465 source += hashwidth;
466 466 }
467 467 return list;
468 468 }
469 469
470 470 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
471 471 uint32_t *msize)
472 472 {
473 473 const char *data = databegin;
474 474 const char *meta;
475 475
476 476 double mtime;
477 477 int16_t tz;
478 478 uint16_t flags;
479 479 unsigned char nsuccs, nparents, nmetadata;
480 480 Py_ssize_t hashwidth = 20;
481 481
482 482 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
483 483 PyObject *metadata = NULL, *ret = NULL;
484 484 int i;
485 485
486 486 if (data + FM1_HEADER_SIZE > dataend) {
487 487 goto overflow;
488 488 }
489 489
490 490 *msize = getbe32(data);
491 491 data += 4;
492 492 mtime = getbefloat64(data);
493 493 data += 8;
494 494 tz = getbeint16(data);
495 495 data += 2;
496 496 flags = getbeuint16(data);
497 497 data += 2;
498 498
499 499 if (flags & USING_SHA_256) {
500 500 hashwidth = 32;
501 501 }
502 502
503 503 nsuccs = (unsigned char)(*data++);
504 504 nparents = (unsigned char)(*data++);
505 505 nmetadata = (unsigned char)(*data++);
506 506
507 507 if (databegin + *msize > dataend) {
508 508 goto overflow;
509 509 }
510 510 dataend = databegin + *msize; /* narrow down to marker size */
511 511
512 512 if (data + hashwidth > dataend) {
513 513 goto overflow;
514 514 }
515 515 prec = PyBytes_FromStringAndSize(data, hashwidth);
516 516 data += hashwidth;
517 517 if (prec == NULL) {
518 518 goto bail;
519 519 }
520 520
521 521 if (data + nsuccs * hashwidth > dataend) {
522 522 goto overflow;
523 523 }
524 524 succs = readshas(data, nsuccs, hashwidth);
525 525 if (succs == NULL) {
526 526 goto bail;
527 527 }
528 528 data += nsuccs * hashwidth;
529 529
530 530 if (nparents == 1 || nparents == 2) {
531 531 if (data + nparents * hashwidth > dataend) {
532 532 goto overflow;
533 533 }
534 534 parents = readshas(data, nparents, hashwidth);
535 535 if (parents == NULL) {
536 536 goto bail;
537 537 }
538 538 data += nparents * hashwidth;
539 539 } else {
540 540 parents = Py_None;
541 541 Py_INCREF(parents);
542 542 }
543 543
544 544 if (data + 2 * nmetadata > dataend) {
545 545 goto overflow;
546 546 }
547 547 meta = data + (2 * nmetadata);
548 548 metadata = PyTuple_New(nmetadata);
549 549 if (metadata == NULL) {
550 550 goto bail;
551 551 }
552 552 for (i = 0; i < nmetadata; i++) {
553 553 PyObject *tmp, *left = NULL, *right = NULL;
554 554 Py_ssize_t leftsize = (unsigned char)(*data++);
555 555 Py_ssize_t rightsize = (unsigned char)(*data++);
556 556 if (meta + leftsize + rightsize > dataend) {
557 557 goto overflow;
558 558 }
559 559 left = PyBytes_FromStringAndSize(meta, leftsize);
560 560 meta += leftsize;
561 561 right = PyBytes_FromStringAndSize(meta, rightsize);
562 562 meta += rightsize;
563 563 tmp = PyTuple_New(2);
564 564 if (!left || !right || !tmp) {
565 565 Py_XDECREF(left);
566 566 Py_XDECREF(right);
567 567 Py_XDECREF(tmp);
568 568 goto bail;
569 569 }
570 570 PyTuple_SET_ITEM(tmp, 0, left);
571 571 PyTuple_SET_ITEM(tmp, 1, right);
572 572 PyTuple_SET_ITEM(metadata, i, tmp);
573 573 }
574 574 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
575 575 (int)tz * 60, parents);
576 576 goto bail; /* return successfully */
577 577
578 578 overflow:
579 579 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
580 580 bail:
581 581 Py_XDECREF(prec);
582 582 Py_XDECREF(succs);
583 583 Py_XDECREF(metadata);
584 584 Py_XDECREF(parents);
585 585 return ret;
586 586 }
587 587
588 588 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
589 589 {
590 590 const char *data, *dataend;
591 591 Py_ssize_t datalen, offset, stop;
592 592 PyObject *markers = NULL;
593 593
594 594 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
595 595 &offset, &stop)) {
596 596 return NULL;
597 597 }
598 598 if (offset < 0) {
599 599 PyErr_SetString(PyExc_ValueError,
600 600 "invalid negative offset in fm1readmarkers");
601 601 return NULL;
602 602 }
603 603 if (stop > datalen) {
604 604 PyErr_SetString(
605 605 PyExc_ValueError,
606 606 "stop longer than data length in fm1readmarkers");
607 607 return NULL;
608 608 }
609 609 dataend = data + datalen;
610 610 data += offset;
611 611 markers = PyList_New(0);
612 612 if (!markers) {
613 613 return NULL;
614 614 }
615 615 while (offset < stop) {
616 616 uint32_t msize;
617 617 int error;
618 618 PyObject *record = fm1readmarker(data, dataend, &msize);
619 619 if (!record) {
620 620 goto bail;
621 621 }
622 622 error = PyList_Append(markers, record);
623 623 Py_DECREF(record);
624 624 if (error) {
625 625 goto bail;
626 626 }
627 627 data += msize;
628 628 offset += msize;
629 629 }
630 630 return markers;
631 631 bail:
632 632 Py_DECREF(markers);
633 633 return NULL;
634 634 }
635 635
636 636 static char parsers_doc[] = "Efficient content parsing.";
637 637
638 638 PyObject *encodedir(PyObject *self, PyObject *args);
639 639 PyObject *pathencode(PyObject *self, PyObject *args);
640 640 PyObject *lowerencode(PyObject *self, PyObject *args);
641 641 PyObject *parse_index2(PyObject *self, PyObject *args);
642 642
643 643 static PyMethodDef methods[] = {
644 644 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
645 645 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
646 646 "create a set containing non-normal and other parent entries of given "
647 647 "dirstate\n"},
648 648 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
649 649 {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
650 650 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
651 651 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
652 652 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
653 653 {"dict_new_presized", dict_new_presized, METH_VARARGS,
654 654 "construct a dict with an expected size\n"},
655 655 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
656 656 "make file foldmap\n"},
657 657 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
658 658 "escape a UTF-8 byte string to JSON (fast path)\n"},
659 659 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
660 660 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
661 661 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
662 662 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
663 663 "parse v1 obsolete markers\n"},
664 664 {NULL, NULL}};
665 665
666 666 void dirs_module_init(PyObject *mod);
667 667 void manifest_module_init(PyObject *mod);
668 668 void revlog_module_init(PyObject *mod);
669 669
670 static const int version = 16;
670 static const int version = 17;
671 671
672 672 static void module_init(PyObject *mod)
673 673 {
674 674 PyObject *capsule = NULL;
675 675 PyModule_AddIntConstant(mod, "version", version);
676 676
677 677 /* This module constant has two purposes. First, it lets us unit test
678 678 * the ImportError raised without hard-coding any error text. This
679 679 * means we can change the text in the future without breaking tests,
680 680 * even across changesets without a recompile. Second, its presence
681 681 * can be used to determine whether the version-checking logic is
682 682 * present, which also helps in testing across changesets without a
683 683 * recompile. Note that this means the pure-Python version of parsers
684 684 * should not have this module constant. */
685 685 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
686 686
687 687 dirs_module_init(mod);
688 688 manifest_module_init(mod);
689 689 revlog_module_init(mod);
690 690
691 691 capsule = PyCapsule_New(
692 692 make_dirstate_tuple,
693 693 "mercurial.cext.parsers.make_dirstate_tuple_CAPI", NULL);
694 694 if (capsule != NULL)
695 695 PyModule_AddObject(mod, "make_dirstate_tuple_CAPI", capsule);
696 696
697 697 if (PyType_Ready(&dirstateTupleType) < 0) {
698 698 return;
699 699 }
700 700 Py_INCREF(&dirstateTupleType);
701 701 PyModule_AddObject(mod, "dirstatetuple",
702 702 (PyObject *)&dirstateTupleType);
703 703 }
704 704
705 705 static int check_python_version(void)
706 706 {
707 707 PyObject *sys = PyImport_ImportModule("sys"), *ver;
708 708 long hexversion;
709 709 if (!sys) {
710 710 return -1;
711 711 }
712 712 ver = PyObject_GetAttrString(sys, "hexversion");
713 713 Py_DECREF(sys);
714 714 if (!ver) {
715 715 return -1;
716 716 }
717 717 hexversion = PyInt_AsLong(ver);
718 718 Py_DECREF(ver);
719 719 /* sys.hexversion is a 32-bit number by default, so the -1 case
720 720 * should only occur in unusual circumstances (e.g. if sys.hexversion
721 721 * is manually set to an invalid value). */
722 722 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
723 723 PyErr_Format(PyExc_ImportError,
724 724 "%s: The Mercurial extension "
725 725 "modules were compiled with Python " PY_VERSION
726 726 ", but "
727 727 "Mercurial is currently using Python with "
728 728 "sys.hexversion=%ld: "
729 729 "Python %s\n at: %s",
730 730 versionerrortext, hexversion, Py_GetVersion(),
731 731 Py_GetProgramFullPath());
732 732 return -1;
733 733 }
734 734 return 0;
735 735 }
736 736
737 737 #ifdef IS_PY3K
738 738 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
739 739 parsers_doc, -1, methods};
740 740
741 741 PyMODINIT_FUNC PyInit_parsers(void)
742 742 {
743 743 PyObject *mod;
744 744
745 745 if (check_python_version() == -1)
746 746 return NULL;
747 747 mod = PyModule_Create(&parsers_module);
748 748 module_init(mod);
749 749 return mod;
750 750 }
751 751 #else
752 752 PyMODINIT_FUNC initparsers(void)
753 753 {
754 754 PyObject *mod;
755 755
756 756 if (check_python_version() == -1) {
757 757 return;
758 758 }
759 759 mod = Py_InitModule3("parsers", methods, parsers_doc);
760 760 module_init(mod);
761 761 }
762 762 #endif
@@ -1,2918 +1,2959 b''
1 1 /*
2 2 parsers.c - efficient content parsing
3 3
4 4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #define PY_SSIZE_T_CLEAN
11 11 #include <Python.h>
12 12 #include <assert.h>
13 13 #include <ctype.h>
14 14 #include <limits.h>
15 15 #include <stddef.h>
16 16 #include <stdlib.h>
17 17 #include <string.h>
18 18
19 19 #include "bitmanipulation.h"
20 20 #include "charencode.h"
21 21 #include "revlog.h"
22 22 #include "util.h"
23 23
24 24 #ifdef IS_PY3K
25 25 /* The mapping of Python types is meant to be temporary to get Python
26 26 * 3 to compile. We should remove this once Python 3 support is fully
27 27 * supported and proper types are used in the extensions themselves. */
28 28 #define PyInt_Check PyLong_Check
29 29 #define PyInt_FromLong PyLong_FromLong
30 30 #define PyInt_FromSsize_t PyLong_FromSsize_t
31 31 #define PyInt_AsLong PyLong_AsLong
32 32 #endif
33 33
34 34 typedef struct indexObjectStruct indexObject;
35 35
36 36 typedef struct {
37 37 int children[16];
38 38 } nodetreenode;
39 39
40 40 typedef struct {
41 41 int abi_version;
42 42 Py_ssize_t (*index_length)(const indexObject *);
43 43 const char *(*index_node)(indexObject *, Py_ssize_t);
44 44 int (*index_parents)(PyObject *, int, int *);
45 45 } Revlog_CAPI;
46 46
47 47 /*
48 48 * A base-16 trie for fast node->rev mapping.
49 49 *
50 50 * Positive value is index of the next node in the trie
51 51 * Negative value is a leaf: -(rev + 2)
52 52 * Zero is empty
53 53 */
54 54 typedef struct {
55 55 indexObject *index;
56 56 nodetreenode *nodes;
57 57 unsigned length; /* # nodes in use */
58 58 unsigned capacity; /* # nodes allocated */
59 59 int depth; /* maximum depth of tree */
60 60 int splits; /* # splits performed */
61 61 } nodetree;
62 62
63 63 typedef struct {
64 64 PyObject_HEAD /* ; */
65 65 nodetree nt;
66 66 } nodetreeObject;
67 67
68 68 /*
69 69 * This class has two behaviors.
70 70 *
71 71 * When used in a list-like way (with integer keys), we decode an
72 72 * entry in a RevlogNG index file on demand. We have limited support for
73 73 * integer-keyed insert and delete, only at elements right before the
74 74 * end.
75 75 *
76 76 * With string keys, we lazily perform a reverse mapping from node to
77 77 * rev, using a base-16 trie.
78 78 */
79 79 struct indexObjectStruct {
80 80 PyObject_HEAD
81 81 /* Type-specific fields go here. */
82 82 PyObject *data; /* raw bytes of index */
83 83 Py_buffer buf; /* buffer of data */
84 84 PyObject **cache; /* cached tuples */
85 85 const char **offsets; /* populated on demand */
86 86 Py_ssize_t raw_length; /* original number of elements */
87 87 Py_ssize_t length; /* current number of elements */
88 88 PyObject *added; /* populated on demand */
89 89 PyObject *headrevs; /* cache, invalidated on changes */
90 90 PyObject *filteredrevs; /* filtered revs set */
91 91 nodetree nt; /* base-16 trie */
92 92 int ntinitialized; /* 0 or 1 */
93 93 int ntrev; /* last rev scanned */
94 94 int ntlookups; /* # lookups */
95 95 int ntmisses; /* # lookups that miss the cache */
96 96 int inlined;
97 97 };
98 98
99 99 static Py_ssize_t index_length(const indexObject *self)
100 100 {
101 101 if (self->added == NULL)
102 102 return self->length;
103 103 return self->length + PyList_GET_SIZE(self->added);
104 104 }
105 105
106 106 static PyObject *nullentry = NULL;
107 107 static const char nullid[20] = {0};
108 108 static const Py_ssize_t nullrev = -1;
109 109
110 110 static Py_ssize_t inline_scan(indexObject *self, const char **offsets);
111 111
112 static int index_find_node(indexObject *self, const char *node,
113 Py_ssize_t nodelen);
114
112 115 #if LONG_MAX == 0x7fffffffL
113 116 static const char *const tuple_format = PY23("Kiiiiiis#", "Kiiiiiiy#");
114 117 #else
115 118 static const char *const tuple_format = PY23("kiiiiiis#", "kiiiiiiy#");
116 119 #endif
117 120
118 121 /* A RevlogNG v1 index entry is 64 bytes long. */
119 122 static const long v1_hdrsize = 64;
120 123
121 124 static void raise_revlog_error(void)
122 125 {
123 126 PyObject *mod = NULL, *dict = NULL, *errclass = NULL;
124 127
125 128 mod = PyImport_ImportModule("mercurial.error");
126 129 if (mod == NULL) {
127 130 goto cleanup;
128 131 }
129 132
130 133 dict = PyModule_GetDict(mod);
131 134 if (dict == NULL) {
132 135 goto cleanup;
133 136 }
134 137 Py_INCREF(dict);
135 138
136 139 errclass = PyDict_GetItemString(dict, "RevlogError");
137 140 if (errclass == NULL) {
138 141 PyErr_SetString(PyExc_SystemError,
139 142 "could not find RevlogError");
140 143 goto cleanup;
141 144 }
142 145
143 146 /* value of exception is ignored by callers */
144 147 PyErr_SetString(errclass, "RevlogError");
145 148
146 149 cleanup:
147 150 Py_XDECREF(dict);
148 151 Py_XDECREF(mod);
149 152 }
150 153
151 154 /*
152 155 * Return a pointer to the beginning of a RevlogNG record.
153 156 */
154 157 static const char *index_deref(indexObject *self, Py_ssize_t pos)
155 158 {
156 159 if (self->inlined && pos > 0) {
157 160 if (self->offsets == NULL) {
158 161 Py_ssize_t ret;
159 162 self->offsets = PyMem_Malloc(self->raw_length *
160 163 sizeof(*self->offsets));
161 164 if (self->offsets == NULL)
162 165 return (const char *)PyErr_NoMemory();
163 166 ret = inline_scan(self, self->offsets);
164 167 if (ret == -1) {
165 168 return NULL;
166 169 };
167 170 }
168 171 return self->offsets[pos];
169 172 }
170 173
171 174 return (const char *)(self->buf.buf) + pos * v1_hdrsize;
172 175 }
173 176
174 177 /*
175 178 * Get parents of the given rev.
176 179 *
177 180 * The specified rev must be valid and must not be nullrev. A returned
178 181 * parent revision may be nullrev, but is guaranteed to be in valid range.
179 182 */
180 183 static inline int index_get_parents(indexObject *self, Py_ssize_t rev, int *ps,
181 184 int maxrev)
182 185 {
183 186 if (rev >= self->length) {
184 187 long tmp;
185 188 PyObject *tuple =
186 189 PyList_GET_ITEM(self->added, rev - self->length);
187 190 if (!pylong_to_long(PyTuple_GET_ITEM(tuple, 5), &tmp)) {
188 191 return -1;
189 192 }
190 193 ps[0] = (int)tmp;
191 194 if (!pylong_to_long(PyTuple_GET_ITEM(tuple, 6), &tmp)) {
192 195 return -1;
193 196 }
194 197 ps[1] = (int)tmp;
195 198 } else {
196 199 const char *data = index_deref(self, rev);
197 200 ps[0] = getbe32(data + 24);
198 201 ps[1] = getbe32(data + 28);
199 202 }
200 203 /* If index file is corrupted, ps[] may point to invalid revisions. So
201 204 * there is a risk of buffer overflow to trust them unconditionally. */
202 205 if (ps[0] < -1 || ps[0] > maxrev || ps[1] < -1 || ps[1] > maxrev) {
203 206 PyErr_SetString(PyExc_ValueError, "parent out of range");
204 207 return -1;
205 208 }
206 209 return 0;
207 210 }
208 211
209 212 /*
210 213 * Get parents of the given rev.
211 214 *
212 215 * If the specified rev is out of range, IndexError will be raised. If the
213 216 * revlog entry is corrupted, ValueError may be raised.
214 217 *
215 218 * Returns 0 on success or -1 on failure.
216 219 */
217 220 static int HgRevlogIndex_GetParents(PyObject *op, int rev, int *ps)
218 221 {
219 222 int tiprev;
220 223 if (!op || !HgRevlogIndex_Check(op) || !ps) {
221 224 PyErr_BadInternalCall();
222 225 return -1;
223 226 }
224 227 tiprev = (int)index_length((indexObject *)op) - 1;
225 228 if (rev < -1 || rev > tiprev) {
226 229 PyErr_Format(PyExc_IndexError, "rev out of range: %d", rev);
227 230 return -1;
228 231 } else if (rev == -1) {
229 232 ps[0] = ps[1] = -1;
230 233 return 0;
231 234 } else {
232 235 return index_get_parents((indexObject *)op, rev, ps, tiprev);
233 236 }
234 237 }
235 238
236 239 static inline int64_t index_get_start(indexObject *self, Py_ssize_t rev)
237 240 {
238 241 uint64_t offset;
239 242 if (rev == nullrev) {
240 243 return 0;
241 244 }
242 245 if (rev >= self->length) {
243 246 PyObject *tuple;
244 247 PyObject *pylong;
245 248 PY_LONG_LONG tmp;
246 249 tuple = PyList_GET_ITEM(self->added, rev - self->length);
247 250 pylong = PyTuple_GET_ITEM(tuple, 0);
248 251 tmp = PyLong_AsLongLong(pylong);
249 252 if (tmp == -1 && PyErr_Occurred()) {
250 253 return -1;
251 254 }
252 255 if (tmp < 0) {
253 256 PyErr_Format(PyExc_OverflowError,
254 257 "revlog entry size out of bound (%lld)",
255 258 (long long)tmp);
256 259 return -1;
257 260 }
258 261 offset = (uint64_t)tmp;
259 262 } else {
260 263 const char *data = index_deref(self, rev);
261 264 offset = getbe32(data + 4);
262 265 if (rev == 0) {
263 266 /* mask out version number for the first entry */
264 267 offset &= 0xFFFF;
265 268 } else {
266 269 uint32_t offset_high = getbe32(data);
267 270 offset |= ((uint64_t)offset_high) << 32;
268 271 }
269 272 }
270 273 return (int64_t)(offset >> 16);
271 274 }
272 275
273 276 static inline int index_get_length(indexObject *self, Py_ssize_t rev)
274 277 {
275 278 if (rev == nullrev) {
276 279 return 0;
277 280 }
278 281 if (rev >= self->length) {
279 282 PyObject *tuple;
280 283 PyObject *pylong;
281 284 long ret;
282 285 tuple = PyList_GET_ITEM(self->added, rev - self->length);
283 286 pylong = PyTuple_GET_ITEM(tuple, 1);
284 287 ret = PyInt_AsLong(pylong);
285 288 if (ret == -1 && PyErr_Occurred()) {
286 289 return -1;
287 290 }
288 291 if (ret < 0 || ret > (long)INT_MAX) {
289 292 PyErr_Format(PyExc_OverflowError,
290 293 "revlog entry size out of bound (%ld)",
291 294 ret);
292 295 return -1;
293 296 }
294 297 return (int)ret;
295 298 } else {
296 299 const char *data = index_deref(self, rev);
297 300 int tmp = (int)getbe32(data + 8);
298 301 if (tmp < 0) {
299 302 PyErr_Format(PyExc_OverflowError,
300 303 "revlog entry size out of bound (%d)",
301 304 tmp);
302 305 return -1;
303 306 }
304 307 return tmp;
305 308 }
306 309 }
307 310
308 311 /*
309 312 * RevlogNG format (all in big endian, data may be inlined):
310 313 * 6 bytes: offset
311 314 * 2 bytes: flags
312 315 * 4 bytes: compressed length
313 316 * 4 bytes: uncompressed length
314 317 * 4 bytes: base revision
315 318 * 4 bytes: link revision
316 319 * 4 bytes: parent 1 revision
317 320 * 4 bytes: parent 2 revision
318 321 * 32 bytes: nodeid (only 20 bytes used)
319 322 */
320 323 static PyObject *index_get(indexObject *self, Py_ssize_t pos)
321 324 {
322 325 uint64_t offset_flags;
323 326 int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2;
324 327 const char *c_node_id;
325 328 const char *data;
326 329 Py_ssize_t length = index_length(self);
327 330 PyObject *entry;
328 331
329 332 if (pos == nullrev) {
330 333 Py_INCREF(nullentry);
331 334 return nullentry;
332 335 }
333 336
334 337 if (pos < 0 || pos >= length) {
335 338 PyErr_SetString(PyExc_IndexError, "revlog index out of range");
336 339 return NULL;
337 340 }
338 341
339 342 if (pos >= self->length) {
340 343 PyObject *obj;
341 344 obj = PyList_GET_ITEM(self->added, pos - self->length);
342 345 Py_INCREF(obj);
343 346 return obj;
344 347 }
345 348
346 349 if (self->cache) {
347 350 if (self->cache[pos]) {
348 351 Py_INCREF(self->cache[pos]);
349 352 return self->cache[pos];
350 353 }
351 354 } else {
352 355 self->cache = calloc(self->raw_length, sizeof(PyObject *));
353 356 if (self->cache == NULL)
354 357 return PyErr_NoMemory();
355 358 }
356 359
357 360 data = index_deref(self, pos);
358 361 if (data == NULL)
359 362 return NULL;
360 363
361 364 offset_flags = getbe32(data + 4);
362 365 if (pos == 0) /* mask out version number for the first entry */
363 366 offset_flags &= 0xFFFF;
364 367 else {
365 368 uint32_t offset_high = getbe32(data);
366 369 offset_flags |= ((uint64_t)offset_high) << 32;
367 370 }
368 371
369 372 comp_len = getbe32(data + 8);
370 373 uncomp_len = getbe32(data + 12);
371 374 base_rev = getbe32(data + 16);
372 375 link_rev = getbe32(data + 20);
373 376 parent_1 = getbe32(data + 24);
374 377 parent_2 = getbe32(data + 28);
375 378 c_node_id = data + 32;
376 379
377 380 entry = Py_BuildValue(tuple_format, offset_flags, comp_len, uncomp_len,
378 381 base_rev, link_rev, parent_1, parent_2, c_node_id,
379 382 (Py_ssize_t)20);
380 383
381 384 if (entry) {
382 385 PyObject_GC_UnTrack(entry);
383 386 Py_INCREF(entry);
384 387 }
385 388
386 389 self->cache[pos] = entry;
387 390
388 391 return entry;
389 392 }
390 393
391 394 /*
392 395 * Return the 20-byte SHA of the node corresponding to the given rev.
393 396 */
394 397 static const char *index_node(indexObject *self, Py_ssize_t pos)
395 398 {
396 399 Py_ssize_t length = index_length(self);
397 400 const char *data;
398 401
399 402 if (pos == nullrev)
400 403 return nullid;
401 404
402 405 if (pos >= length)
403 406 return NULL;
404 407
405 408 if (pos >= self->length) {
406 409 PyObject *tuple, *str;
407 410 tuple = PyList_GET_ITEM(self->added, pos - self->length);
408 411 str = PyTuple_GetItem(tuple, 7);
409 412 return str ? PyBytes_AS_STRING(str) : NULL;
410 413 }
411 414
412 415 data = index_deref(self, pos);
413 416 return data ? data + 32 : NULL;
414 417 }
415 418
416 419 /*
417 420 * Return the 20-byte SHA of the node corresponding to the given rev. The
418 421 * rev is assumed to be existing. If not, an exception is set.
419 422 */
420 423 static const char *index_node_existing(indexObject *self, Py_ssize_t pos)
421 424 {
422 425 const char *node = index_node(self, pos);
423 426 if (node == NULL) {
424 427 PyErr_Format(PyExc_IndexError, "could not access rev %d",
425 428 (int)pos);
426 429 }
427 430 return node;
428 431 }
429 432
430 433 static int nt_insert(nodetree *self, const char *node, int rev);
431 434
432 435 static int node_check(PyObject *obj, char **node)
433 436 {
434 437 Py_ssize_t nodelen;
435 438 if (PyBytes_AsStringAndSize(obj, node, &nodelen) == -1)
436 439 return -1;
437 440 if (nodelen == 20)
438 441 return 0;
439 442 PyErr_SetString(PyExc_ValueError, "20-byte hash required");
440 443 return -1;
441 444 }
442 445
443 446 static PyObject *index_append(indexObject *self, PyObject *obj)
444 447 {
445 448 char *node;
446 449 Py_ssize_t len;
447 450
448 451 if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 8) {
449 452 PyErr_SetString(PyExc_TypeError, "8-tuple required");
450 453 return NULL;
451 454 }
452 455
453 456 if (node_check(PyTuple_GET_ITEM(obj, 7), &node) == -1)
454 457 return NULL;
455 458
456 459 len = index_length(self);
457 460
458 461 if (self->added == NULL) {
459 462 self->added = PyList_New(0);
460 463 if (self->added == NULL)
461 464 return NULL;
462 465 }
463 466
464 467 if (PyList_Append(self->added, obj) == -1)
465 468 return NULL;
466 469
467 470 if (self->ntinitialized)
468 471 nt_insert(&self->nt, node, (int)len);
469 472
470 473 Py_CLEAR(self->headrevs);
471 474 Py_RETURN_NONE;
472 475 }
473 476
474 477 static PyObject *index_stats(indexObject *self)
475 478 {
476 479 PyObject *obj = PyDict_New();
477 480 PyObject *s = NULL;
478 481 PyObject *t = NULL;
479 482
480 483 if (obj == NULL)
481 484 return NULL;
482 485
483 486 #define istat(__n, __d) \
484 487 do { \
485 488 s = PyBytes_FromString(__d); \
486 489 t = PyInt_FromSsize_t(self->__n); \
487 490 if (!s || !t) \
488 491 goto bail; \
489 492 if (PyDict_SetItem(obj, s, t) == -1) \
490 493 goto bail; \
491 494 Py_CLEAR(s); \
492 495 Py_CLEAR(t); \
493 496 } while (0)
494 497
495 498 if (self->added) {
496 499 Py_ssize_t len = PyList_GET_SIZE(self->added);
497 500 s = PyBytes_FromString("index entries added");
498 501 t = PyInt_FromSsize_t(len);
499 502 if (!s || !t)
500 503 goto bail;
501 504 if (PyDict_SetItem(obj, s, t) == -1)
502 505 goto bail;
503 506 Py_CLEAR(s);
504 507 Py_CLEAR(t);
505 508 }
506 509
507 510 if (self->raw_length != self->length)
508 511 istat(raw_length, "revs on disk");
509 512 istat(length, "revs in memory");
510 513 istat(ntlookups, "node trie lookups");
511 514 istat(ntmisses, "node trie misses");
512 515 istat(ntrev, "node trie last rev scanned");
513 516 if (self->ntinitialized) {
514 517 istat(nt.capacity, "node trie capacity");
515 518 istat(nt.depth, "node trie depth");
516 519 istat(nt.length, "node trie count");
517 520 istat(nt.splits, "node trie splits");
518 521 }
519 522
520 523 #undef istat
521 524
522 525 return obj;
523 526
524 527 bail:
525 528 Py_XDECREF(obj);
526 529 Py_XDECREF(s);
527 530 Py_XDECREF(t);
528 531 return NULL;
529 532 }
530 533
531 534 /*
532 535 * When we cache a list, we want to be sure the caller can't mutate
533 536 * the cached copy.
534 537 */
535 538 static PyObject *list_copy(PyObject *list)
536 539 {
537 540 Py_ssize_t len = PyList_GET_SIZE(list);
538 541 PyObject *newlist = PyList_New(len);
539 542 Py_ssize_t i;
540 543
541 544 if (newlist == NULL)
542 545 return NULL;
543 546
544 547 for (i = 0; i < len; i++) {
545 548 PyObject *obj = PyList_GET_ITEM(list, i);
546 549 Py_INCREF(obj);
547 550 PyList_SET_ITEM(newlist, i, obj);
548 551 }
549 552
550 553 return newlist;
551 554 }
552 555
553 556 static int check_filter(PyObject *filter, Py_ssize_t arg)
554 557 {
555 558 if (filter) {
556 559 PyObject *arglist, *result;
557 560 int isfiltered;
558 561
559 562 arglist = Py_BuildValue("(n)", arg);
560 563 if (!arglist) {
561 564 return -1;
562 565 }
563 566
564 567 result = PyEval_CallObject(filter, arglist);
565 568 Py_DECREF(arglist);
566 569 if (!result) {
567 570 return -1;
568 571 }
569 572
570 573 /* PyObject_IsTrue returns 1 if true, 0 if false, -1 if error,
571 574 * same as this function, so we can just return it directly.*/
572 575 isfiltered = PyObject_IsTrue(result);
573 576 Py_DECREF(result);
574 577 return isfiltered;
575 578 } else {
576 579 return 0;
577 580 }
578 581 }
579 582
580 static Py_ssize_t add_roots_get_min(indexObject *self, PyObject *list,
581 Py_ssize_t marker, char *phases)
582 {
583 PyObject *iter = NULL;
584 PyObject *iter_item = NULL;
585 Py_ssize_t min_idx = index_length(self) + 2;
586 long iter_item_long;
587
588 if (PyList_GET_SIZE(list) != 0) {
589 iter = PyObject_GetIter(list);
590 if (iter == NULL)
591 return -2;
592 while ((iter_item = PyIter_Next(iter))) {
593 if (!pylong_to_long(iter_item, &iter_item_long)) {
594 Py_DECREF(iter_item);
595 return -2;
596 }
597 Py_DECREF(iter_item);
598 if (iter_item_long < min_idx)
599 min_idx = iter_item_long;
600 phases[iter_item_long] = (char)marker;
601 }
602 Py_DECREF(iter);
603 }
604
605 return min_idx;
606 }
607
608 583 static inline void set_phase_from_parents(char *phases, int parent_1,
609 584 int parent_2, Py_ssize_t i)
610 585 {
611 586 if (parent_1 >= 0 && phases[parent_1] > phases[i])
612 587 phases[i] = phases[parent_1];
613 588 if (parent_2 >= 0 && phases[parent_2] > phases[i])
614 589 phases[i] = phases[parent_2];
615 590 }
616 591
617 592 static PyObject *reachableroots2(indexObject *self, PyObject *args)
618 593 {
619 594
620 595 /* Input */
621 596 long minroot;
622 597 PyObject *includepatharg = NULL;
623 598 int includepath = 0;
624 599 /* heads and roots are lists */
625 600 PyObject *heads = NULL;
626 601 PyObject *roots = NULL;
627 602 PyObject *reachable = NULL;
628 603
629 604 PyObject *val;
630 605 Py_ssize_t len = index_length(self);
631 606 long revnum;
632 607 Py_ssize_t k;
633 608 Py_ssize_t i;
634 609 Py_ssize_t l;
635 610 int r;
636 611 int parents[2];
637 612
638 613 /* Internal data structure:
639 614 * tovisit: array of length len+1 (all revs + nullrev), filled upto
640 615 * lentovisit
641 616 *
642 617 * revstates: array of length len+1 (all revs + nullrev) */
643 618 int *tovisit = NULL;
644 619 long lentovisit = 0;
645 620 enum { RS_SEEN = 1, RS_ROOT = 2, RS_REACHABLE = 4 };
646 621 char *revstates = NULL;
647 622
648 623 /* Get arguments */
649 624 if (!PyArg_ParseTuple(args, "lO!O!O!", &minroot, &PyList_Type, &heads,
650 625 &PyList_Type, &roots, &PyBool_Type,
651 626 &includepatharg))
652 627 goto bail;
653 628
654 629 if (includepatharg == Py_True)
655 630 includepath = 1;
656 631
657 632 /* Initialize return set */
658 633 reachable = PyList_New(0);
659 634 if (reachable == NULL)
660 635 goto bail;
661 636
662 637 /* Initialize internal datastructures */
663 638 tovisit = (int *)malloc((len + 1) * sizeof(int));
664 639 if (tovisit == NULL) {
665 640 PyErr_NoMemory();
666 641 goto bail;
667 642 }
668 643
669 644 revstates = (char *)calloc(len + 1, 1);
670 645 if (revstates == NULL) {
671 646 PyErr_NoMemory();
672 647 goto bail;
673 648 }
674 649
675 650 l = PyList_GET_SIZE(roots);
676 651 for (i = 0; i < l; i++) {
677 652 revnum = PyInt_AsLong(PyList_GET_ITEM(roots, i));
678 653 if (revnum == -1 && PyErr_Occurred())
679 654 goto bail;
680 655 /* If root is out of range, e.g. wdir(), it must be unreachable
681 656 * from heads. So we can just ignore it. */
682 657 if (revnum + 1 < 0 || revnum + 1 >= len + 1)
683 658 continue;
684 659 revstates[revnum + 1] |= RS_ROOT;
685 660 }
686 661
687 662 /* Populate tovisit with all the heads */
688 663 l = PyList_GET_SIZE(heads);
689 664 for (i = 0; i < l; i++) {
690 665 revnum = PyInt_AsLong(PyList_GET_ITEM(heads, i));
691 666 if (revnum == -1 && PyErr_Occurred())
692 667 goto bail;
693 668 if (revnum + 1 < 0 || revnum + 1 >= len + 1) {
694 669 PyErr_SetString(PyExc_IndexError, "head out of range");
695 670 goto bail;
696 671 }
697 672 if (!(revstates[revnum + 1] & RS_SEEN)) {
698 673 tovisit[lentovisit++] = (int)revnum;
699 674 revstates[revnum + 1] |= RS_SEEN;
700 675 }
701 676 }
702 677
703 678 /* Visit the tovisit list and find the reachable roots */
704 679 k = 0;
705 680 while (k < lentovisit) {
706 681 /* Add the node to reachable if it is a root*/
707 682 revnum = tovisit[k++];
708 683 if (revstates[revnum + 1] & RS_ROOT) {
709 684 revstates[revnum + 1] |= RS_REACHABLE;
710 685 val = PyInt_FromLong(revnum);
711 686 if (val == NULL)
712 687 goto bail;
713 688 r = PyList_Append(reachable, val);
714 689 Py_DECREF(val);
715 690 if (r < 0)
716 691 goto bail;
717 692 if (includepath == 0)
718 693 continue;
719 694 }
720 695
721 696 /* Add its parents to the list of nodes to visit */
722 697 if (revnum == nullrev)
723 698 continue;
724 699 r = index_get_parents(self, revnum, parents, (int)len - 1);
725 700 if (r < 0)
726 701 goto bail;
727 702 for (i = 0; i < 2; i++) {
728 703 if (!(revstates[parents[i] + 1] & RS_SEEN) &&
729 704 parents[i] >= minroot) {
730 705 tovisit[lentovisit++] = parents[i];
731 706 revstates[parents[i] + 1] |= RS_SEEN;
732 707 }
733 708 }
734 709 }
735 710
736 711 /* Find all the nodes in between the roots we found and the heads
737 712 * and add them to the reachable set */
738 713 if (includepath == 1) {
739 714 long minidx = minroot;
740 715 if (minidx < 0)
741 716 minidx = 0;
742 717 for (i = minidx; i < len; i++) {
743 718 if (!(revstates[i + 1] & RS_SEEN))
744 719 continue;
745 720 r = index_get_parents(self, i, parents, (int)len - 1);
746 721 /* Corrupted index file, error is set from
747 722 * index_get_parents */
748 723 if (r < 0)
749 724 goto bail;
750 725 if (((revstates[parents[0] + 1] |
751 726 revstates[parents[1] + 1]) &
752 727 RS_REACHABLE) &&
753 728 !(revstates[i + 1] & RS_REACHABLE)) {
754 729 revstates[i + 1] |= RS_REACHABLE;
755 730 val = PyInt_FromSsize_t(i);
756 731 if (val == NULL)
757 732 goto bail;
758 733 r = PyList_Append(reachable, val);
759 734 Py_DECREF(val);
760 735 if (r < 0)
761 736 goto bail;
762 737 }
763 738 }
764 739 }
765 740
766 741 free(revstates);
767 742 free(tovisit);
768 743 return reachable;
769 744 bail:
770 745 Py_XDECREF(reachable);
771 746 free(revstates);
772 747 free(tovisit);
773 748 return NULL;
774 749 }
775 750
751 static int add_roots_get_min(indexObject *self, PyObject *roots, char *phases,
752 char phase)
753 {
754 Py_ssize_t len = index_length(self);
755 PyObject *iter;
756 PyObject *item;
757 PyObject *iterator;
758 int rev, minrev = -1;
759 char *node;
760
761 if (!PySet_Check(roots))
762 return -2;
763 iterator = PyObject_GetIter(roots);
764 if (iterator == NULL)
765 return -2;
766 while ((item = PyIter_Next(iterator))) {
767 if (node_check(item, &node) == -1)
768 goto failed;
769 rev = index_find_node(self, node, 20);
770 /* null is implicitly public, so negative is invalid */
771 if (rev < 0 || rev >= len)
772 goto failed;
773 phases[rev] = phase;
774 if (minrev == -1 || minrev > rev)
775 minrev = rev;
776 Py_DECREF(item);
777 }
778 Py_DECREF(iterator);
779 return minrev;
780 failed:
781 Py_DECREF(iterator);
782 Py_DECREF(item);
783 return -2;
784 }
785
776 786 static PyObject *compute_phases_map_sets(indexObject *self, PyObject *args)
777 787 {
778 PyObject *roots = Py_None;
788 /* 0: public (untracked), 1: draft, 2: secret, 32: archive,
789 96: internal */
790 static const char trackedphases[] = {1, 2, 32, 96};
779 791 PyObject *ret = NULL;
780 PyObject *phasessize = NULL;
792 PyObject *roots = Py_None;
793 PyObject *idx = NULL;
794 PyObject *pyphase = NULL;
795 PyObject *pyrev = NULL;
781 796 PyObject *phaseroots = NULL;
782 PyObject *phaseset = NULL;
783 PyObject *phasessetlist = NULL;
784 PyObject *rev = NULL;
797 PyObject *phasessize = NULL;
798 PyObject *phasesets[4] = {NULL, NULL, NULL, NULL};
785 799 Py_ssize_t len = index_length(self);
786 Py_ssize_t numphase = 0;
787 Py_ssize_t minrevallphases = 0;
788 Py_ssize_t minrevphase = 0;
789 Py_ssize_t i = 0;
800 const char *currentphase;
790 801 char *phases = NULL;
791 long phase;
802 int minphaserev = -1, rev, i;
803 const int numphases = (int)(sizeof(phasesets) / sizeof(phasesets[0]));
792 804
793 805 if (!PyArg_ParseTuple(args, "O", &roots))
794 goto done;
795 if (roots == NULL || !PyList_Check(roots)) {
796 PyErr_SetString(PyExc_TypeError, "roots must be a list");
797 goto done;
806 return NULL;
807 if (roots == NULL || !PyDict_Check(roots)) {
808 PyErr_SetString(PyExc_TypeError, "roots must be a dictionary");
809 return NULL;
798 810 }
799 811
800 phases = calloc(
801 len, 1); /* phase per rev: {0: public, 1: draft, 2: secret} */
812 phases = calloc(len, 1);
802 813 if (phases == NULL) {
803 814 PyErr_NoMemory();
804 goto done;
815 return NULL;
805 816 }
806 /* Put the phase information of all the roots in phases */
807 numphase = PyList_GET_SIZE(roots) + 1;
808 minrevallphases = len + 1;
809 phasessetlist = PyList_New(numphase);
810 if (phasessetlist == NULL)
811 goto done;
817
818 for (i = 0; i < numphases; ++i) {
819 pyphase = PyInt_FromLong(trackedphases[i]);
820 if (pyphase == NULL)
821 goto release;
822 phaseroots = PyDict_GetItem(roots, pyphase);
823 Py_DECREF(pyphase);
824 if (phaseroots == NULL)
825 continue;
826 rev = add_roots_get_min(self, phaseroots, phases, trackedphases[i]);
827 phaseroots = NULL;
828 if (rev == -2)
829 goto release;
830 if (rev != -1 && (minphaserev == -1 || rev < minphaserev))
831 minphaserev = rev;
832 }
833
834 for (i = 0; i < numphases; ++i) {
835 phasesets[i] = PySet_New(NULL);
836 if (phasesets[i] == NULL)
837 goto release;
838 }
812 839
813 PyList_SET_ITEM(phasessetlist, 0, Py_None);
814 Py_INCREF(Py_None);
815
816 for (i = 0; i < numphase - 1; i++) {
817 phaseroots = PyList_GET_ITEM(roots, i);
818 phaseset = PySet_New(NULL);
819 if (phaseset == NULL)
840 if (minphaserev == -1)
841 minphaserev = len;
842 for (rev = minphaserev; rev < len; ++rev) {
843 int parents[2];
844 /*
845 * The parent lookup could be skipped for phaseroots, but
846 * phase --force would historically not recompute them
847 * correctly, leaving descendents with a lower phase around.
848 * As such, unconditionally recompute the phase.
849 */
850 if (index_get_parents(self, rev, parents, (int)len - 1) < 0)
820 851 goto release;
821 PyList_SET_ITEM(phasessetlist, i + 1, phaseset);
822 if (!PyList_Check(phaseroots)) {
823 PyErr_SetString(PyExc_TypeError,
824 "roots item must be a list");
852 set_phase_from_parents(phases, parents[0], parents[1], rev);
853 switch (phases[rev]) {
854 case 0:
855 continue;
856 case 1:
857 pyphase = phasesets[0];
858 break;
859 case 2:
860 pyphase = phasesets[1];
861 break;
862 case 32:
863 pyphase = phasesets[2];
864 break;
865 case 96:
866 pyphase = phasesets[3];
867 break;
868 default:
825 869 goto release;
826 870 }
827 minrevphase =
828 add_roots_get_min(self, phaseroots, i + 1, phases);
829 if (minrevphase == -2) /* Error from add_roots_get_min */
871 pyrev = PyInt_FromLong(rev);
872 if (pyrev == NULL)
830 873 goto release;
831 minrevallphases = MIN(minrevallphases, minrevphase);
874 if (PySet_Add(pyphase, pyrev) == -1) {
875 Py_DECREF(pyrev);
876 goto release;
877 }
878 Py_DECREF(pyrev);
832 879 }
833 /* Propagate the phase information from the roots to the revs */
834 if (minrevallphases != -1) {
835 int parents[2];
836 for (i = minrevallphases; i < len; i++) {
837 if (index_get_parents(self, i, parents, (int)len - 1) <
838 0)
839 goto release;
840 set_phase_from_parents(phases, parents[0], parents[1],
841 i);
880 phaseroots = _dict_new_presized(numphases);
881 if (phaseroots == NULL)
882 goto release;
883 for (int i = 0; i < numphases; ++i) {
884 pyphase = PyInt_FromLong(trackedphases[i]);
885 if (pyphase == NULL)
886 goto release;
887 if (PyDict_SetItem(phaseroots, pyphase, phasesets[i]) == -1) {
888 Py_DECREF(pyphase);
889 goto release;
842 890 }
891 Py_DECREF(phasesets[i]);
892 phasesets[i] = NULL;
843 893 }
844 /* Transform phase list to a python list */
845 894 phasessize = PyInt_FromSsize_t(len);
846 895 if (phasessize == NULL)
847 896 goto release;
848 for (i = 0; i < len; i++) {
849 phase = phases[i];
850 /* We only store the sets of phase for non public phase, the
851 * public phase is computed as a difference */
852 if (phase != 0) {
853 phaseset = PyList_GET_ITEM(phasessetlist, phase);
854 rev = PyInt_FromSsize_t(i);
855 if (rev == NULL)
856 goto release;
857 PySet_Add(phaseset, rev);
858 Py_XDECREF(rev);
859 }
860 }
861 ret = PyTuple_Pack(2, phasessize, phasessetlist);
897
898 ret = PyTuple_Pack(2, phasessize, phaseroots);
899 Py_DECREF(phasessize);
900 Py_DECREF(phaseroots);
901 return ret;
862 902
863 903 release:
864 Py_XDECREF(phasessize);
865 Py_XDECREF(phasessetlist);
866 done:
904 for (i = 0; i < numphases; ++i)
905 Py_XDECREF(phasesets[i]);
906 Py_XDECREF(phaseroots);
907
867 908 free(phases);
868 return ret;
909 return NULL;
869 910 }
870 911
871 912 static PyObject *index_headrevs(indexObject *self, PyObject *args)
872 913 {
873 914 Py_ssize_t i, j, len;
874 915 char *nothead = NULL;
875 916 PyObject *heads = NULL;
876 917 PyObject *filter = NULL;
877 918 PyObject *filteredrevs = Py_None;
878 919
879 920 if (!PyArg_ParseTuple(args, "|O", &filteredrevs)) {
880 921 return NULL;
881 922 }
882 923
883 924 if (self->headrevs && filteredrevs == self->filteredrevs)
884 925 return list_copy(self->headrevs);
885 926
886 927 Py_DECREF(self->filteredrevs);
887 928 self->filteredrevs = filteredrevs;
888 929 Py_INCREF(filteredrevs);
889 930
890 931 if (filteredrevs != Py_None) {
891 932 filter = PyObject_GetAttrString(filteredrevs, "__contains__");
892 933 if (!filter) {
893 934 PyErr_SetString(
894 935 PyExc_TypeError,
895 936 "filteredrevs has no attribute __contains__");
896 937 goto bail;
897 938 }
898 939 }
899 940
900 941 len = index_length(self);
901 942 heads = PyList_New(0);
902 943 if (heads == NULL)
903 944 goto bail;
904 945 if (len == 0) {
905 946 PyObject *nullid = PyInt_FromLong(-1);
906 947 if (nullid == NULL || PyList_Append(heads, nullid) == -1) {
907 948 Py_XDECREF(nullid);
908 949 goto bail;
909 950 }
910 951 goto done;
911 952 }
912 953
913 954 nothead = calloc(len, 1);
914 955 if (nothead == NULL) {
915 956 PyErr_NoMemory();
916 957 goto bail;
917 958 }
918 959
919 960 for (i = len - 1; i >= 0; i--) {
920 961 int isfiltered;
921 962 int parents[2];
922 963
923 964 /* If nothead[i] == 1, it means we've seen an unfiltered child
924 965 * of this node already, and therefore this node is not
925 966 * filtered. So we can skip the expensive check_filter step.
926 967 */
927 968 if (nothead[i] != 1) {
928 969 isfiltered = check_filter(filter, i);
929 970 if (isfiltered == -1) {
930 971 PyErr_SetString(PyExc_TypeError,
931 972 "unable to check filter");
932 973 goto bail;
933 974 }
934 975
935 976 if (isfiltered) {
936 977 nothead[i] = 1;
937 978 continue;
938 979 }
939 980 }
940 981
941 982 if (index_get_parents(self, i, parents, (int)len - 1) < 0)
942 983 goto bail;
943 984 for (j = 0; j < 2; j++) {
944 985 if (parents[j] >= 0)
945 986 nothead[parents[j]] = 1;
946 987 }
947 988 }
948 989
949 990 for (i = 0; i < len; i++) {
950 991 PyObject *head;
951 992
952 993 if (nothead[i])
953 994 continue;
954 995 head = PyInt_FromSsize_t(i);
955 996 if (head == NULL || PyList_Append(heads, head) == -1) {
956 997 Py_XDECREF(head);
957 998 goto bail;
958 999 }
959 1000 }
960 1001
961 1002 done:
962 1003 self->headrevs = heads;
963 1004 Py_XDECREF(filter);
964 1005 free(nothead);
965 1006 return list_copy(self->headrevs);
966 1007 bail:
967 1008 Py_XDECREF(filter);
968 1009 Py_XDECREF(heads);
969 1010 free(nothead);
970 1011 return NULL;
971 1012 }
972 1013
973 1014 /**
974 1015 * Obtain the base revision index entry.
975 1016 *
976 1017 * Callers must ensure that rev >= 0 or illegal memory access may occur.
977 1018 */
978 1019 static inline int index_baserev(indexObject *self, int rev)
979 1020 {
980 1021 const char *data;
981 1022 int result;
982 1023
983 1024 if (rev >= self->length) {
984 1025 PyObject *tuple =
985 1026 PyList_GET_ITEM(self->added, rev - self->length);
986 1027 long ret;
987 1028 if (!pylong_to_long(PyTuple_GET_ITEM(tuple, 3), &ret)) {
988 1029 return -2;
989 1030 }
990 1031 result = (int)ret;
991 1032 } else {
992 1033 data = index_deref(self, rev);
993 1034 if (data == NULL) {
994 1035 return -2;
995 1036 }
996 1037
997 1038 result = getbe32(data + 16);
998 1039 }
999 1040 if (result > rev) {
1000 1041 PyErr_Format(
1001 1042 PyExc_ValueError,
1002 1043 "corrupted revlog, revision base above revision: %d, %d",
1003 1044 rev, result);
1004 1045 return -2;
1005 1046 }
1006 1047 if (result < -1) {
1007 1048 PyErr_Format(
1008 1049 PyExc_ValueError,
1009 1050 "corrupted revlog, revision base out of range: %d, %d", rev,
1010 1051 result);
1011 1052 return -2;
1012 1053 }
1013 1054 return result;
1014 1055 }
1015 1056
1016 1057 /**
1017 1058 * Find if a revision is a snapshot or not
1018 1059 *
1019 1060 * Only relevant for sparse-revlog case.
1020 1061 * Callers must ensure that rev is in a valid range.
1021 1062 */
1022 1063 static int index_issnapshotrev(indexObject *self, Py_ssize_t rev)
1023 1064 {
1024 1065 int ps[2];
1025 1066 Py_ssize_t base;
1026 1067 while (rev >= 0) {
1027 1068 base = (Py_ssize_t)index_baserev(self, rev);
1028 1069 if (base == rev) {
1029 1070 base = -1;
1030 1071 }
1031 1072 if (base == -2) {
1032 1073 assert(PyErr_Occurred());
1033 1074 return -1;
1034 1075 }
1035 1076 if (base == -1) {
1036 1077 return 1;
1037 1078 }
1038 1079 if (index_get_parents(self, rev, ps, (int)rev) < 0) {
1039 1080 assert(PyErr_Occurred());
1040 1081 return -1;
1041 1082 };
1042 1083 if (base == ps[0] || base == ps[1]) {
1043 1084 return 0;
1044 1085 }
1045 1086 rev = base;
1046 1087 }
1047 1088 return rev == -1;
1048 1089 }
1049 1090
1050 1091 static PyObject *index_issnapshot(indexObject *self, PyObject *value)
1051 1092 {
1052 1093 long rev;
1053 1094 int issnap;
1054 1095 Py_ssize_t length = index_length(self);
1055 1096
1056 1097 if (!pylong_to_long(value, &rev)) {
1057 1098 return NULL;
1058 1099 }
1059 1100 if (rev < -1 || rev >= length) {
1060 1101 PyErr_Format(PyExc_ValueError, "revlog index out of range: %ld",
1061 1102 rev);
1062 1103 return NULL;
1063 1104 };
1064 1105 issnap = index_issnapshotrev(self, (Py_ssize_t)rev);
1065 1106 if (issnap < 0) {
1066 1107 return NULL;
1067 1108 };
1068 1109 return PyBool_FromLong((long)issnap);
1069 1110 }
1070 1111
1071 1112 static PyObject *index_findsnapshots(indexObject *self, PyObject *args)
1072 1113 {
1073 1114 Py_ssize_t start_rev;
1074 1115 PyObject *cache;
1075 1116 Py_ssize_t base;
1076 1117 Py_ssize_t rev;
1077 1118 PyObject *key = NULL;
1078 1119 PyObject *value = NULL;
1079 1120 const Py_ssize_t length = index_length(self);
1080 1121 if (!PyArg_ParseTuple(args, "O!n", &PyDict_Type, &cache, &start_rev)) {
1081 1122 return NULL;
1082 1123 }
1083 1124 for (rev = start_rev; rev < length; rev++) {
1084 1125 int issnap;
1085 1126 PyObject *allvalues = NULL;
1086 1127 issnap = index_issnapshotrev(self, rev);
1087 1128 if (issnap < 0) {
1088 1129 goto bail;
1089 1130 }
1090 1131 if (issnap == 0) {
1091 1132 continue;
1092 1133 }
1093 1134 base = (Py_ssize_t)index_baserev(self, rev);
1094 1135 if (base == rev) {
1095 1136 base = -1;
1096 1137 }
1097 1138 if (base == -2) {
1098 1139 assert(PyErr_Occurred());
1099 1140 goto bail;
1100 1141 }
1101 1142 key = PyInt_FromSsize_t(base);
1102 1143 allvalues = PyDict_GetItem(cache, key);
1103 1144 if (allvalues == NULL && PyErr_Occurred()) {
1104 1145 goto bail;
1105 1146 }
1106 1147 if (allvalues == NULL) {
1107 1148 int r;
1108 1149 allvalues = PyList_New(0);
1109 1150 if (!allvalues) {
1110 1151 goto bail;
1111 1152 }
1112 1153 r = PyDict_SetItem(cache, key, allvalues);
1113 1154 Py_DECREF(allvalues);
1114 1155 if (r < 0) {
1115 1156 goto bail;
1116 1157 }
1117 1158 }
1118 1159 value = PyInt_FromSsize_t(rev);
1119 1160 if (PyList_Append(allvalues, value)) {
1120 1161 goto bail;
1121 1162 }
1122 1163 Py_CLEAR(key);
1123 1164 Py_CLEAR(value);
1124 1165 }
1125 1166 Py_RETURN_NONE;
1126 1167 bail:
1127 1168 Py_XDECREF(key);
1128 1169 Py_XDECREF(value);
1129 1170 return NULL;
1130 1171 }
1131 1172
1132 1173 static PyObject *index_deltachain(indexObject *self, PyObject *args)
1133 1174 {
1134 1175 int rev, generaldelta;
1135 1176 PyObject *stoparg;
1136 1177 int stoprev, iterrev, baserev = -1;
1137 1178 int stopped;
1138 1179 PyObject *chain = NULL, *result = NULL;
1139 1180 const Py_ssize_t length = index_length(self);
1140 1181
1141 1182 if (!PyArg_ParseTuple(args, "iOi", &rev, &stoparg, &generaldelta)) {
1142 1183 return NULL;
1143 1184 }
1144 1185
1145 1186 if (PyInt_Check(stoparg)) {
1146 1187 stoprev = (int)PyInt_AsLong(stoparg);
1147 1188 if (stoprev == -1 && PyErr_Occurred()) {
1148 1189 return NULL;
1149 1190 }
1150 1191 } else if (stoparg == Py_None) {
1151 1192 stoprev = -2;
1152 1193 } else {
1153 1194 PyErr_SetString(PyExc_ValueError,
1154 1195 "stoprev must be integer or None");
1155 1196 return NULL;
1156 1197 }
1157 1198
1158 1199 if (rev < 0 || rev >= length) {
1159 1200 PyErr_SetString(PyExc_ValueError, "revlog index out of range");
1160 1201 return NULL;
1161 1202 }
1162 1203
1163 1204 chain = PyList_New(0);
1164 1205 if (chain == NULL) {
1165 1206 return NULL;
1166 1207 }
1167 1208
1168 1209 baserev = index_baserev(self, rev);
1169 1210
1170 1211 /* This should never happen. */
1171 1212 if (baserev <= -2) {
1172 1213 /* Error should be set by index_deref() */
1173 1214 assert(PyErr_Occurred());
1174 1215 goto bail;
1175 1216 }
1176 1217
1177 1218 iterrev = rev;
1178 1219
1179 1220 while (iterrev != baserev && iterrev != stoprev) {
1180 1221 PyObject *value = PyInt_FromLong(iterrev);
1181 1222 if (value == NULL) {
1182 1223 goto bail;
1183 1224 }
1184 1225 if (PyList_Append(chain, value)) {
1185 1226 Py_DECREF(value);
1186 1227 goto bail;
1187 1228 }
1188 1229 Py_DECREF(value);
1189 1230
1190 1231 if (generaldelta) {
1191 1232 iterrev = baserev;
1192 1233 } else {
1193 1234 iterrev--;
1194 1235 }
1195 1236
1196 1237 if (iterrev < 0) {
1197 1238 break;
1198 1239 }
1199 1240
1200 1241 if (iterrev >= length) {
1201 1242 PyErr_SetString(PyExc_IndexError,
1202 1243 "revision outside index");
1203 1244 return NULL;
1204 1245 }
1205 1246
1206 1247 baserev = index_baserev(self, iterrev);
1207 1248
1208 1249 /* This should never happen. */
1209 1250 if (baserev <= -2) {
1210 1251 /* Error should be set by index_deref() */
1211 1252 assert(PyErr_Occurred());
1212 1253 goto bail;
1213 1254 }
1214 1255 }
1215 1256
1216 1257 if (iterrev == stoprev) {
1217 1258 stopped = 1;
1218 1259 } else {
1219 1260 PyObject *value = PyInt_FromLong(iterrev);
1220 1261 if (value == NULL) {
1221 1262 goto bail;
1222 1263 }
1223 1264 if (PyList_Append(chain, value)) {
1224 1265 Py_DECREF(value);
1225 1266 goto bail;
1226 1267 }
1227 1268 Py_DECREF(value);
1228 1269
1229 1270 stopped = 0;
1230 1271 }
1231 1272
1232 1273 if (PyList_Reverse(chain)) {
1233 1274 goto bail;
1234 1275 }
1235 1276
1236 1277 result = Py_BuildValue("OO", chain, stopped ? Py_True : Py_False);
1237 1278 Py_DECREF(chain);
1238 1279 return result;
1239 1280
1240 1281 bail:
1241 1282 Py_DECREF(chain);
1242 1283 return NULL;
1243 1284 }
1244 1285
1245 1286 static inline int64_t
1246 1287 index_segment_span(indexObject *self, Py_ssize_t start_rev, Py_ssize_t end_rev)
1247 1288 {
1248 1289 int64_t start_offset;
1249 1290 int64_t end_offset;
1250 1291 int end_size;
1251 1292 start_offset = index_get_start(self, start_rev);
1252 1293 if (start_offset < 0) {
1253 1294 return -1;
1254 1295 }
1255 1296 end_offset = index_get_start(self, end_rev);
1256 1297 if (end_offset < 0) {
1257 1298 return -1;
1258 1299 }
1259 1300 end_size = index_get_length(self, end_rev);
1260 1301 if (end_size < 0) {
1261 1302 return -1;
1262 1303 }
1263 1304 if (end_offset < start_offset) {
1264 1305 PyErr_Format(PyExc_ValueError,
1265 1306 "corrupted revlog index: inconsistent offset "
1266 1307 "between revisions (%zd) and (%zd)",
1267 1308 start_rev, end_rev);
1268 1309 return -1;
1269 1310 }
1270 1311 return (end_offset - start_offset) + (int64_t)end_size;
1271 1312 }
1272 1313
1273 1314 /* returns endidx so that revs[startidx:endidx] has no empty trailing revs */
1274 1315 static Py_ssize_t trim_endidx(indexObject *self, const Py_ssize_t *revs,
1275 1316 Py_ssize_t startidx, Py_ssize_t endidx)
1276 1317 {
1277 1318 int length;
1278 1319 while (endidx > 1 && endidx > startidx) {
1279 1320 length = index_get_length(self, revs[endidx - 1]);
1280 1321 if (length < 0) {
1281 1322 return -1;
1282 1323 }
1283 1324 if (length != 0) {
1284 1325 break;
1285 1326 }
1286 1327 endidx -= 1;
1287 1328 }
1288 1329 return endidx;
1289 1330 }
1290 1331
1291 1332 struct Gap {
1292 1333 int64_t size;
1293 1334 Py_ssize_t idx;
1294 1335 };
1295 1336
1296 1337 static int gap_compare(const void *left, const void *right)
1297 1338 {
1298 1339 const struct Gap *l_left = ((const struct Gap *)left);
1299 1340 const struct Gap *l_right = ((const struct Gap *)right);
1300 1341 if (l_left->size < l_right->size) {
1301 1342 return -1;
1302 1343 } else if (l_left->size > l_right->size) {
1303 1344 return 1;
1304 1345 }
1305 1346 return 0;
1306 1347 }
1307 1348 static int Py_ssize_t_compare(const void *left, const void *right)
1308 1349 {
1309 1350 const Py_ssize_t l_left = *(const Py_ssize_t *)left;
1310 1351 const Py_ssize_t l_right = *(const Py_ssize_t *)right;
1311 1352 if (l_left < l_right) {
1312 1353 return -1;
1313 1354 } else if (l_left > l_right) {
1314 1355 return 1;
1315 1356 }
1316 1357 return 0;
1317 1358 }
1318 1359
1319 1360 static PyObject *index_slicechunktodensity(indexObject *self, PyObject *args)
1320 1361 {
1321 1362 /* method arguments */
1322 1363 PyObject *list_revs = NULL; /* revisions in the chain */
1323 1364 double targetdensity = 0; /* min density to achieve */
1324 1365 Py_ssize_t mingapsize = 0; /* threshold to ignore gaps */
1325 1366
1326 1367 /* other core variables */
1327 1368 Py_ssize_t idxlen = index_length(self);
1328 1369 Py_ssize_t i; /* used for various iteration */
1329 1370 PyObject *result = NULL; /* the final return of the function */
1330 1371
1331 1372 /* generic information about the delta chain being slice */
1332 1373 Py_ssize_t num_revs = 0; /* size of the full delta chain */
1333 1374 Py_ssize_t *revs = NULL; /* native array of revision in the chain */
1334 1375 int64_t chainpayload = 0; /* sum of all delta in the chain */
1335 1376 int64_t deltachainspan = 0; /* distance from first byte to last byte */
1336 1377
1337 1378 /* variable used for slicing the delta chain */
1338 1379 int64_t readdata = 0; /* amount of data currently planned to be read */
1339 1380 double density = 0; /* ration of payload data compared to read ones */
1340 1381 int64_t previous_end;
1341 1382 struct Gap *gaps = NULL; /* array of notable gap in the chain */
1342 1383 Py_ssize_t num_gaps =
1343 1384 0; /* total number of notable gap recorded so far */
1344 1385 Py_ssize_t *selected_indices = NULL; /* indices of gap skipped over */
1345 1386 Py_ssize_t num_selected = 0; /* number of gaps skipped */
1346 1387 PyObject *chunk = NULL; /* individual slice */
1347 1388 PyObject *allchunks = NULL; /* all slices */
1348 1389 Py_ssize_t previdx;
1349 1390
1350 1391 /* parsing argument */
1351 1392 if (!PyArg_ParseTuple(args, "O!dn", &PyList_Type, &list_revs,
1352 1393 &targetdensity, &mingapsize)) {
1353 1394 goto bail;
1354 1395 }
1355 1396
1356 1397 /* If the delta chain contains a single element, we do not need slicing
1357 1398 */
1358 1399 num_revs = PyList_GET_SIZE(list_revs);
1359 1400 if (num_revs <= 1) {
1360 1401 result = PyTuple_Pack(1, list_revs);
1361 1402 goto done;
1362 1403 }
1363 1404
1364 1405 /* Turn the python list into a native integer array (for efficiency) */
1365 1406 revs = (Py_ssize_t *)calloc(num_revs, sizeof(Py_ssize_t));
1366 1407 if (revs == NULL) {
1367 1408 PyErr_NoMemory();
1368 1409 goto bail;
1369 1410 }
1370 1411 for (i = 0; i < num_revs; i++) {
1371 1412 Py_ssize_t revnum = PyInt_AsLong(PyList_GET_ITEM(list_revs, i));
1372 1413 if (revnum == -1 && PyErr_Occurred()) {
1373 1414 goto bail;
1374 1415 }
1375 1416 if (revnum < nullrev || revnum >= idxlen) {
1376 1417 PyErr_Format(PyExc_IndexError,
1377 1418 "index out of range: %zd", revnum);
1378 1419 goto bail;
1379 1420 }
1380 1421 revs[i] = revnum;
1381 1422 }
1382 1423
1383 1424 /* Compute and check various property of the unsliced delta chain */
1384 1425 deltachainspan = index_segment_span(self, revs[0], revs[num_revs - 1]);
1385 1426 if (deltachainspan < 0) {
1386 1427 goto bail;
1387 1428 }
1388 1429
1389 1430 if (deltachainspan <= mingapsize) {
1390 1431 result = PyTuple_Pack(1, list_revs);
1391 1432 goto done;
1392 1433 }
1393 1434 chainpayload = 0;
1394 1435 for (i = 0; i < num_revs; i++) {
1395 1436 int tmp = index_get_length(self, revs[i]);
1396 1437 if (tmp < 0) {
1397 1438 goto bail;
1398 1439 }
1399 1440 chainpayload += tmp;
1400 1441 }
1401 1442
1402 1443 readdata = deltachainspan;
1403 1444 density = 1.0;
1404 1445
1405 1446 if (0 < deltachainspan) {
1406 1447 density = (double)chainpayload / (double)deltachainspan;
1407 1448 }
1408 1449
1409 1450 if (density >= targetdensity) {
1410 1451 result = PyTuple_Pack(1, list_revs);
1411 1452 goto done;
1412 1453 }
1413 1454
1414 1455 /* if chain is too sparse, look for relevant gaps */
1415 1456 gaps = (struct Gap *)calloc(num_revs, sizeof(struct Gap));
1416 1457 if (gaps == NULL) {
1417 1458 PyErr_NoMemory();
1418 1459 goto bail;
1419 1460 }
1420 1461
1421 1462 previous_end = -1;
1422 1463 for (i = 0; i < num_revs; i++) {
1423 1464 int64_t revstart;
1424 1465 int revsize;
1425 1466 revstart = index_get_start(self, revs[i]);
1426 1467 if (revstart < 0) {
1427 1468 goto bail;
1428 1469 };
1429 1470 revsize = index_get_length(self, revs[i]);
1430 1471 if (revsize < 0) {
1431 1472 goto bail;
1432 1473 };
1433 1474 if (revsize == 0) {
1434 1475 continue;
1435 1476 }
1436 1477 if (previous_end >= 0) {
1437 1478 int64_t gapsize = revstart - previous_end;
1438 1479 if (gapsize > mingapsize) {
1439 1480 gaps[num_gaps].size = gapsize;
1440 1481 gaps[num_gaps].idx = i;
1441 1482 num_gaps += 1;
1442 1483 }
1443 1484 }
1444 1485 previous_end = revstart + revsize;
1445 1486 }
1446 1487 if (num_gaps == 0) {
1447 1488 result = PyTuple_Pack(1, list_revs);
1448 1489 goto done;
1449 1490 }
1450 1491 qsort(gaps, num_gaps, sizeof(struct Gap), &gap_compare);
1451 1492
1452 1493 /* Slice the largest gap first, they improve the density the most */
1453 1494 selected_indices =
1454 1495 (Py_ssize_t *)malloc((num_gaps + 1) * sizeof(Py_ssize_t));
1455 1496 if (selected_indices == NULL) {
1456 1497 PyErr_NoMemory();
1457 1498 goto bail;
1458 1499 }
1459 1500
1460 1501 for (i = num_gaps - 1; i >= 0; i--) {
1461 1502 selected_indices[num_selected] = gaps[i].idx;
1462 1503 readdata -= gaps[i].size;
1463 1504 num_selected += 1;
1464 1505 if (readdata <= 0) {
1465 1506 density = 1.0;
1466 1507 } else {
1467 1508 density = (double)chainpayload / (double)readdata;
1468 1509 }
1469 1510 if (density >= targetdensity) {
1470 1511 break;
1471 1512 }
1472 1513 }
1473 1514 qsort(selected_indices, num_selected, sizeof(Py_ssize_t),
1474 1515 &Py_ssize_t_compare);
1475 1516
1476 1517 /* create the resulting slice */
1477 1518 allchunks = PyList_New(0);
1478 1519 if (allchunks == NULL) {
1479 1520 goto bail;
1480 1521 }
1481 1522 previdx = 0;
1482 1523 selected_indices[num_selected] = num_revs;
1483 1524 for (i = 0; i <= num_selected; i++) {
1484 1525 Py_ssize_t idx = selected_indices[i];
1485 1526 Py_ssize_t endidx = trim_endidx(self, revs, previdx, idx);
1486 1527 if (endidx < 0) {
1487 1528 goto bail;
1488 1529 }
1489 1530 if (previdx < endidx) {
1490 1531 chunk = PyList_GetSlice(list_revs, previdx, endidx);
1491 1532 if (chunk == NULL) {
1492 1533 goto bail;
1493 1534 }
1494 1535 if (PyList_Append(allchunks, chunk) == -1) {
1495 1536 goto bail;
1496 1537 }
1497 1538 Py_DECREF(chunk);
1498 1539 chunk = NULL;
1499 1540 }
1500 1541 previdx = idx;
1501 1542 }
1502 1543 result = allchunks;
1503 1544 goto done;
1504 1545
1505 1546 bail:
1506 1547 Py_XDECREF(allchunks);
1507 1548 Py_XDECREF(chunk);
1508 1549 done:
1509 1550 free(revs);
1510 1551 free(gaps);
1511 1552 free(selected_indices);
1512 1553 return result;
1513 1554 }
1514 1555
1515 1556 static inline int nt_level(const char *node, Py_ssize_t level)
1516 1557 {
1517 1558 int v = node[level >> 1];
1518 1559 if (!(level & 1))
1519 1560 v >>= 4;
1520 1561 return v & 0xf;
1521 1562 }
1522 1563
1523 1564 /*
1524 1565 * Return values:
1525 1566 *
1526 1567 * -4: match is ambiguous (multiple candidates)
1527 1568 * -2: not found
1528 1569 * rest: valid rev
1529 1570 */
1530 1571 static int nt_find(nodetree *self, const char *node, Py_ssize_t nodelen,
1531 1572 int hex)
1532 1573 {
1533 1574 int (*getnybble)(const char *, Py_ssize_t) = hex ? hexdigit : nt_level;
1534 1575 int level, maxlevel, off;
1535 1576
1536 1577 if (nodelen == 20 && node[0] == '\0' && memcmp(node, nullid, 20) == 0)
1537 1578 return -1;
1538 1579
1539 1580 if (hex)
1540 1581 maxlevel = nodelen > 40 ? 40 : (int)nodelen;
1541 1582 else
1542 1583 maxlevel = nodelen > 20 ? 40 : ((int)nodelen * 2);
1543 1584
1544 1585 for (level = off = 0; level < maxlevel; level++) {
1545 1586 int k = getnybble(node, level);
1546 1587 nodetreenode *n = &self->nodes[off];
1547 1588 int v = n->children[k];
1548 1589
1549 1590 if (v < 0) {
1550 1591 const char *n;
1551 1592 Py_ssize_t i;
1552 1593
1553 1594 v = -(v + 2);
1554 1595 n = index_node(self->index, v);
1555 1596 if (n == NULL)
1556 1597 return -2;
1557 1598 for (i = level; i < maxlevel; i++)
1558 1599 if (getnybble(node, i) != nt_level(n, i))
1559 1600 return -2;
1560 1601 return v;
1561 1602 }
1562 1603 if (v == 0)
1563 1604 return -2;
1564 1605 off = v;
1565 1606 }
1566 1607 /* multiple matches against an ambiguous prefix */
1567 1608 return -4;
1568 1609 }
1569 1610
1570 1611 static int nt_new(nodetree *self)
1571 1612 {
1572 1613 if (self->length == self->capacity) {
1573 1614 unsigned newcapacity;
1574 1615 nodetreenode *newnodes;
1575 1616 newcapacity = self->capacity * 2;
1576 1617 if (newcapacity >= INT_MAX / sizeof(nodetreenode)) {
1577 1618 PyErr_SetString(PyExc_MemoryError,
1578 1619 "overflow in nt_new");
1579 1620 return -1;
1580 1621 }
1581 1622 newnodes =
1582 1623 realloc(self->nodes, newcapacity * sizeof(nodetreenode));
1583 1624 if (newnodes == NULL) {
1584 1625 PyErr_SetString(PyExc_MemoryError, "out of memory");
1585 1626 return -1;
1586 1627 }
1587 1628 self->capacity = newcapacity;
1588 1629 self->nodes = newnodes;
1589 1630 memset(&self->nodes[self->length], 0,
1590 1631 sizeof(nodetreenode) * (self->capacity - self->length));
1591 1632 }
1592 1633 return self->length++;
1593 1634 }
1594 1635
1595 1636 static int nt_insert(nodetree *self, const char *node, int rev)
1596 1637 {
1597 1638 int level = 0;
1598 1639 int off = 0;
1599 1640
1600 1641 while (level < 40) {
1601 1642 int k = nt_level(node, level);
1602 1643 nodetreenode *n;
1603 1644 int v;
1604 1645
1605 1646 n = &self->nodes[off];
1606 1647 v = n->children[k];
1607 1648
1608 1649 if (v == 0) {
1609 1650 n->children[k] = -rev - 2;
1610 1651 return 0;
1611 1652 }
1612 1653 if (v < 0) {
1613 1654 const char *oldnode =
1614 1655 index_node_existing(self->index, -(v + 2));
1615 1656 int noff;
1616 1657
1617 1658 if (oldnode == NULL)
1618 1659 return -1;
1619 1660 if (!memcmp(oldnode, node, 20)) {
1620 1661 n->children[k] = -rev - 2;
1621 1662 return 0;
1622 1663 }
1623 1664 noff = nt_new(self);
1624 1665 if (noff == -1)
1625 1666 return -1;
1626 1667 /* self->nodes may have been changed by realloc */
1627 1668 self->nodes[off].children[k] = noff;
1628 1669 off = noff;
1629 1670 n = &self->nodes[off];
1630 1671 n->children[nt_level(oldnode, ++level)] = v;
1631 1672 if (level > self->depth)
1632 1673 self->depth = level;
1633 1674 self->splits += 1;
1634 1675 } else {
1635 1676 level += 1;
1636 1677 off = v;
1637 1678 }
1638 1679 }
1639 1680
1640 1681 return -1;
1641 1682 }
1642 1683
1643 1684 static PyObject *ntobj_insert(nodetreeObject *self, PyObject *args)
1644 1685 {
1645 1686 Py_ssize_t rev;
1646 1687 const char *node;
1647 1688 Py_ssize_t length;
1648 1689 if (!PyArg_ParseTuple(args, "n", &rev))
1649 1690 return NULL;
1650 1691 length = index_length(self->nt.index);
1651 1692 if (rev < 0 || rev >= length) {
1652 1693 PyErr_SetString(PyExc_ValueError, "revlog index out of range");
1653 1694 return NULL;
1654 1695 }
1655 1696 node = index_node_existing(self->nt.index, rev);
1656 1697 if (nt_insert(&self->nt, node, (int)rev) == -1)
1657 1698 return NULL;
1658 1699 Py_RETURN_NONE;
1659 1700 }
1660 1701
1661 1702 static int nt_delete_node(nodetree *self, const char *node)
1662 1703 {
1663 1704 /* rev==-2 happens to get encoded as 0, which is interpreted as not set
1664 1705 */
1665 1706 return nt_insert(self, node, -2);
1666 1707 }
1667 1708
1668 1709 static int nt_init(nodetree *self, indexObject *index, unsigned capacity)
1669 1710 {
1670 1711 /* Initialize before overflow-checking to avoid nt_dealloc() crash. */
1671 1712 self->nodes = NULL;
1672 1713
1673 1714 self->index = index;
1674 1715 /* The input capacity is in terms of revisions, while the field is in
1675 1716 * terms of nodetree nodes. */
1676 1717 self->capacity = (capacity < 4 ? 4 : capacity / 2);
1677 1718 self->depth = 0;
1678 1719 self->splits = 0;
1679 1720 if ((size_t)self->capacity > INT_MAX / sizeof(nodetreenode)) {
1680 1721 PyErr_SetString(PyExc_ValueError, "overflow in init_nt");
1681 1722 return -1;
1682 1723 }
1683 1724 self->nodes = calloc(self->capacity, sizeof(nodetreenode));
1684 1725 if (self->nodes == NULL) {
1685 1726 PyErr_NoMemory();
1686 1727 return -1;
1687 1728 }
1688 1729 self->length = 1;
1689 1730 return 0;
1690 1731 }
1691 1732
1692 1733 static int ntobj_init(nodetreeObject *self, PyObject *args)
1693 1734 {
1694 1735 PyObject *index;
1695 1736 unsigned capacity;
1696 1737 if (!PyArg_ParseTuple(args, "O!I", &HgRevlogIndex_Type, &index,
1697 1738 &capacity))
1698 1739 return -1;
1699 1740 Py_INCREF(index);
1700 1741 return nt_init(&self->nt, (indexObject *)index, capacity);
1701 1742 }
1702 1743
1703 1744 static int nt_partialmatch(nodetree *self, const char *node, Py_ssize_t nodelen)
1704 1745 {
1705 1746 return nt_find(self, node, nodelen, 1);
1706 1747 }
1707 1748
1708 1749 /*
1709 1750 * Find the length of the shortest unique prefix of node.
1710 1751 *
1711 1752 * Return values:
1712 1753 *
1713 1754 * -3: error (exception set)
1714 1755 * -2: not found (no exception set)
1715 1756 * rest: length of shortest prefix
1716 1757 */
1717 1758 static int nt_shortest(nodetree *self, const char *node)
1718 1759 {
1719 1760 int level, off;
1720 1761
1721 1762 for (level = off = 0; level < 40; level++) {
1722 1763 int k, v;
1723 1764 nodetreenode *n = &self->nodes[off];
1724 1765 k = nt_level(node, level);
1725 1766 v = n->children[k];
1726 1767 if (v < 0) {
1727 1768 const char *n;
1728 1769 v = -(v + 2);
1729 1770 n = index_node_existing(self->index, v);
1730 1771 if (n == NULL)
1731 1772 return -3;
1732 1773 if (memcmp(node, n, 20) != 0)
1733 1774 /*
1734 1775 * Found a unique prefix, but it wasn't for the
1735 1776 * requested node (i.e the requested node does
1736 1777 * not exist).
1737 1778 */
1738 1779 return -2;
1739 1780 return level + 1;
1740 1781 }
1741 1782 if (v == 0)
1742 1783 return -2;
1743 1784 off = v;
1744 1785 }
1745 1786 /*
1746 1787 * The node was still not unique after 40 hex digits, so this won't
1747 1788 * happen. Also, if we get here, then there's a programming error in
1748 1789 * this file that made us insert a node longer than 40 hex digits.
1749 1790 */
1750 1791 PyErr_SetString(PyExc_Exception, "broken node tree");
1751 1792 return -3;
1752 1793 }
1753 1794
1754 1795 static PyObject *ntobj_shortest(nodetreeObject *self, PyObject *args)
1755 1796 {
1756 1797 PyObject *val;
1757 1798 char *node;
1758 1799 int length;
1759 1800
1760 1801 if (!PyArg_ParseTuple(args, "O", &val))
1761 1802 return NULL;
1762 1803 if (node_check(val, &node) == -1)
1763 1804 return NULL;
1764 1805
1765 1806 length = nt_shortest(&self->nt, node);
1766 1807 if (length == -3)
1767 1808 return NULL;
1768 1809 if (length == -2) {
1769 1810 raise_revlog_error();
1770 1811 return NULL;
1771 1812 }
1772 1813 return PyInt_FromLong(length);
1773 1814 }
1774 1815
1775 1816 static void nt_dealloc(nodetree *self)
1776 1817 {
1777 1818 free(self->nodes);
1778 1819 self->nodes = NULL;
1779 1820 }
1780 1821
1781 1822 static void ntobj_dealloc(nodetreeObject *self)
1782 1823 {
1783 1824 Py_XDECREF(self->nt.index);
1784 1825 nt_dealloc(&self->nt);
1785 1826 PyObject_Del(self);
1786 1827 }
1787 1828
1788 1829 static PyMethodDef ntobj_methods[] = {
1789 1830 {"insert", (PyCFunction)ntobj_insert, METH_VARARGS,
1790 1831 "insert an index entry"},
1791 1832 {"shortest", (PyCFunction)ntobj_shortest, METH_VARARGS,
1792 1833 "find length of shortest hex nodeid of a binary ID"},
1793 1834 {NULL} /* Sentinel */
1794 1835 };
1795 1836
1796 1837 static PyTypeObject nodetreeType = {
1797 1838 PyVarObject_HEAD_INIT(NULL, 0) /* header */
1798 1839 "parsers.nodetree", /* tp_name */
1799 1840 sizeof(nodetreeObject), /* tp_basicsize */
1800 1841 0, /* tp_itemsize */
1801 1842 (destructor)ntobj_dealloc, /* tp_dealloc */
1802 1843 0, /* tp_print */
1803 1844 0, /* tp_getattr */
1804 1845 0, /* tp_setattr */
1805 1846 0, /* tp_compare */
1806 1847 0, /* tp_repr */
1807 1848 0, /* tp_as_number */
1808 1849 0, /* tp_as_sequence */
1809 1850 0, /* tp_as_mapping */
1810 1851 0, /* tp_hash */
1811 1852 0, /* tp_call */
1812 1853 0, /* tp_str */
1813 1854 0, /* tp_getattro */
1814 1855 0, /* tp_setattro */
1815 1856 0, /* tp_as_buffer */
1816 1857 Py_TPFLAGS_DEFAULT, /* tp_flags */
1817 1858 "nodetree", /* tp_doc */
1818 1859 0, /* tp_traverse */
1819 1860 0, /* tp_clear */
1820 1861 0, /* tp_richcompare */
1821 1862 0, /* tp_weaklistoffset */
1822 1863 0, /* tp_iter */
1823 1864 0, /* tp_iternext */
1824 1865 ntobj_methods, /* tp_methods */
1825 1866 0, /* tp_members */
1826 1867 0, /* tp_getset */
1827 1868 0, /* tp_base */
1828 1869 0, /* tp_dict */
1829 1870 0, /* tp_descr_get */
1830 1871 0, /* tp_descr_set */
1831 1872 0, /* tp_dictoffset */
1832 1873 (initproc)ntobj_init, /* tp_init */
1833 1874 0, /* tp_alloc */
1834 1875 };
1835 1876
1836 1877 static int index_init_nt(indexObject *self)
1837 1878 {
1838 1879 if (!self->ntinitialized) {
1839 1880 if (nt_init(&self->nt, self, (int)self->raw_length) == -1) {
1840 1881 nt_dealloc(&self->nt);
1841 1882 return -1;
1842 1883 }
1843 1884 if (nt_insert(&self->nt, nullid, -1) == -1) {
1844 1885 nt_dealloc(&self->nt);
1845 1886 return -1;
1846 1887 }
1847 1888 self->ntinitialized = 1;
1848 1889 self->ntrev = (int)index_length(self);
1849 1890 self->ntlookups = 1;
1850 1891 self->ntmisses = 0;
1851 1892 }
1852 1893 return 0;
1853 1894 }
1854 1895
1855 1896 /*
1856 1897 * Return values:
1857 1898 *
1858 1899 * -3: error (exception set)
1859 1900 * -2: not found (no exception set)
1860 1901 * rest: valid rev
1861 1902 */
1862 1903 static int index_find_node(indexObject *self, const char *node,
1863 1904 Py_ssize_t nodelen)
1864 1905 {
1865 1906 int rev;
1866 1907
1867 1908 if (index_init_nt(self) == -1)
1868 1909 return -3;
1869 1910
1870 1911 self->ntlookups++;
1871 1912 rev = nt_find(&self->nt, node, nodelen, 0);
1872 1913 if (rev >= -1)
1873 1914 return rev;
1874 1915
1875 1916 /*
1876 1917 * For the first handful of lookups, we scan the entire index,
1877 1918 * and cache only the matching nodes. This optimizes for cases
1878 1919 * like "hg tip", where only a few nodes are accessed.
1879 1920 *
1880 1921 * After that, we cache every node we visit, using a single
1881 1922 * scan amortized over multiple lookups. This gives the best
1882 1923 * bulk performance, e.g. for "hg log".
1883 1924 */
1884 1925 if (self->ntmisses++ < 4) {
1885 1926 for (rev = self->ntrev - 1; rev >= 0; rev--) {
1886 1927 const char *n = index_node_existing(self, rev);
1887 1928 if (n == NULL)
1888 1929 return -3;
1889 1930 if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
1890 1931 if (nt_insert(&self->nt, n, rev) == -1)
1891 1932 return -3;
1892 1933 break;
1893 1934 }
1894 1935 }
1895 1936 } else {
1896 1937 for (rev = self->ntrev - 1; rev >= 0; rev--) {
1897 1938 const char *n = index_node_existing(self, rev);
1898 1939 if (n == NULL)
1899 1940 return -3;
1900 1941 if (nt_insert(&self->nt, n, rev) == -1) {
1901 1942 self->ntrev = rev + 1;
1902 1943 return -3;
1903 1944 }
1904 1945 if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
1905 1946 break;
1906 1947 }
1907 1948 }
1908 1949 self->ntrev = rev;
1909 1950 }
1910 1951
1911 1952 if (rev >= 0)
1912 1953 return rev;
1913 1954 return -2;
1914 1955 }
1915 1956
1916 1957 static PyObject *index_getitem(indexObject *self, PyObject *value)
1917 1958 {
1918 1959 char *node;
1919 1960 int rev;
1920 1961
1921 1962 if (PyInt_Check(value)) {
1922 1963 long idx;
1923 1964 if (!pylong_to_long(value, &idx)) {
1924 1965 return NULL;
1925 1966 }
1926 1967 return index_get(self, idx);
1927 1968 }
1928 1969
1929 1970 if (node_check(value, &node) == -1)
1930 1971 return NULL;
1931 1972 rev = index_find_node(self, node, 20);
1932 1973 if (rev >= -1)
1933 1974 return PyInt_FromLong(rev);
1934 1975 if (rev == -2)
1935 1976 raise_revlog_error();
1936 1977 return NULL;
1937 1978 }
1938 1979
1939 1980 /*
1940 1981 * Fully populate the radix tree.
1941 1982 */
1942 1983 static int index_populate_nt(indexObject *self)
1943 1984 {
1944 1985 int rev;
1945 1986 if (self->ntrev > 0) {
1946 1987 for (rev = self->ntrev - 1; rev >= 0; rev--) {
1947 1988 const char *n = index_node_existing(self, rev);
1948 1989 if (n == NULL)
1949 1990 return -1;
1950 1991 if (nt_insert(&self->nt, n, rev) == -1)
1951 1992 return -1;
1952 1993 }
1953 1994 self->ntrev = -1;
1954 1995 }
1955 1996 return 0;
1956 1997 }
1957 1998
1958 1999 static PyObject *index_partialmatch(indexObject *self, PyObject *args)
1959 2000 {
1960 2001 const char *fullnode;
1961 2002 Py_ssize_t nodelen;
1962 2003 char *node;
1963 2004 int rev, i;
1964 2005
1965 2006 if (!PyArg_ParseTuple(args, PY23("s#", "y#"), &node, &nodelen))
1966 2007 return NULL;
1967 2008
1968 2009 if (nodelen < 1) {
1969 2010 PyErr_SetString(PyExc_ValueError, "key too short");
1970 2011 return NULL;
1971 2012 }
1972 2013
1973 2014 if (nodelen > 40) {
1974 2015 PyErr_SetString(PyExc_ValueError, "key too long");
1975 2016 return NULL;
1976 2017 }
1977 2018
1978 2019 for (i = 0; i < nodelen; i++)
1979 2020 hexdigit(node, i);
1980 2021 if (PyErr_Occurred()) {
1981 2022 /* input contains non-hex characters */
1982 2023 PyErr_Clear();
1983 2024 Py_RETURN_NONE;
1984 2025 }
1985 2026
1986 2027 if (index_init_nt(self) == -1)
1987 2028 return NULL;
1988 2029 if (index_populate_nt(self) == -1)
1989 2030 return NULL;
1990 2031 rev = nt_partialmatch(&self->nt, node, nodelen);
1991 2032
1992 2033 switch (rev) {
1993 2034 case -4:
1994 2035 raise_revlog_error();
1995 2036 return NULL;
1996 2037 case -2:
1997 2038 Py_RETURN_NONE;
1998 2039 case -1:
1999 2040 return PyBytes_FromStringAndSize(nullid, 20);
2000 2041 }
2001 2042
2002 2043 fullnode = index_node_existing(self, rev);
2003 2044 if (fullnode == NULL) {
2004 2045 return NULL;
2005 2046 }
2006 2047 return PyBytes_FromStringAndSize(fullnode, 20);
2007 2048 }
2008 2049
2009 2050 static PyObject *index_shortest(indexObject *self, PyObject *args)
2010 2051 {
2011 2052 PyObject *val;
2012 2053 char *node;
2013 2054 int length;
2014 2055
2015 2056 if (!PyArg_ParseTuple(args, "O", &val))
2016 2057 return NULL;
2017 2058 if (node_check(val, &node) == -1)
2018 2059 return NULL;
2019 2060
2020 2061 self->ntlookups++;
2021 2062 if (index_init_nt(self) == -1)
2022 2063 return NULL;
2023 2064 if (index_populate_nt(self) == -1)
2024 2065 return NULL;
2025 2066 length = nt_shortest(&self->nt, node);
2026 2067 if (length == -3)
2027 2068 return NULL;
2028 2069 if (length == -2) {
2029 2070 raise_revlog_error();
2030 2071 return NULL;
2031 2072 }
2032 2073 return PyInt_FromLong(length);
2033 2074 }
2034 2075
2035 2076 static PyObject *index_m_get(indexObject *self, PyObject *args)
2036 2077 {
2037 2078 PyObject *val;
2038 2079 char *node;
2039 2080 int rev;
2040 2081
2041 2082 if (!PyArg_ParseTuple(args, "O", &val))
2042 2083 return NULL;
2043 2084 if (node_check(val, &node) == -1)
2044 2085 return NULL;
2045 2086 rev = index_find_node(self, node, 20);
2046 2087 if (rev == -3)
2047 2088 return NULL;
2048 2089 if (rev == -2)
2049 2090 Py_RETURN_NONE;
2050 2091 return PyInt_FromLong(rev);
2051 2092 }
2052 2093
2053 2094 static int index_contains(indexObject *self, PyObject *value)
2054 2095 {
2055 2096 char *node;
2056 2097
2057 2098 if (PyInt_Check(value)) {
2058 2099 long rev;
2059 2100 if (!pylong_to_long(value, &rev)) {
2060 2101 return -1;
2061 2102 }
2062 2103 return rev >= -1 && rev < index_length(self);
2063 2104 }
2064 2105
2065 2106 if (node_check(value, &node) == -1)
2066 2107 return -1;
2067 2108
2068 2109 switch (index_find_node(self, node, 20)) {
2069 2110 case -3:
2070 2111 return -1;
2071 2112 case -2:
2072 2113 return 0;
2073 2114 default:
2074 2115 return 1;
2075 2116 }
2076 2117 }
2077 2118
2078 2119 static PyObject *index_m_has_node(indexObject *self, PyObject *args)
2079 2120 {
2080 2121 int ret = index_contains(self, args);
2081 2122 if (ret < 0)
2082 2123 return NULL;
2083 2124 return PyBool_FromLong((long)ret);
2084 2125 }
2085 2126
2086 2127 static PyObject *index_m_rev(indexObject *self, PyObject *val)
2087 2128 {
2088 2129 char *node;
2089 2130 int rev;
2090 2131
2091 2132 if (node_check(val, &node) == -1)
2092 2133 return NULL;
2093 2134 rev = index_find_node(self, node, 20);
2094 2135 if (rev >= -1)
2095 2136 return PyInt_FromLong(rev);
2096 2137 if (rev == -2)
2097 2138 raise_revlog_error();
2098 2139 return NULL;
2099 2140 }
2100 2141
2101 2142 typedef uint64_t bitmask;
2102 2143
2103 2144 /*
2104 2145 * Given a disjoint set of revs, return all candidates for the
2105 2146 * greatest common ancestor. In revset notation, this is the set
2106 2147 * "heads(::a and ::b and ...)"
2107 2148 */
2108 2149 static PyObject *find_gca_candidates(indexObject *self, const int *revs,
2109 2150 int revcount)
2110 2151 {
2111 2152 const bitmask allseen = (1ull << revcount) - 1;
2112 2153 const bitmask poison = 1ull << revcount;
2113 2154 PyObject *gca = PyList_New(0);
2114 2155 int i, v, interesting;
2115 2156 int maxrev = -1;
2116 2157 bitmask sp;
2117 2158 bitmask *seen;
2118 2159
2119 2160 if (gca == NULL)
2120 2161 return PyErr_NoMemory();
2121 2162
2122 2163 for (i = 0; i < revcount; i++) {
2123 2164 if (revs[i] > maxrev)
2124 2165 maxrev = revs[i];
2125 2166 }
2126 2167
2127 2168 seen = calloc(sizeof(*seen), maxrev + 1);
2128 2169 if (seen == NULL) {
2129 2170 Py_DECREF(gca);
2130 2171 return PyErr_NoMemory();
2131 2172 }
2132 2173
2133 2174 for (i = 0; i < revcount; i++)
2134 2175 seen[revs[i]] = 1ull << i;
2135 2176
2136 2177 interesting = revcount;
2137 2178
2138 2179 for (v = maxrev; v >= 0 && interesting; v--) {
2139 2180 bitmask sv = seen[v];
2140 2181 int parents[2];
2141 2182
2142 2183 if (!sv)
2143 2184 continue;
2144 2185
2145 2186 if (sv < poison) {
2146 2187 interesting -= 1;
2147 2188 if (sv == allseen) {
2148 2189 PyObject *obj = PyInt_FromLong(v);
2149 2190 if (obj == NULL)
2150 2191 goto bail;
2151 2192 if (PyList_Append(gca, obj) == -1) {
2152 2193 Py_DECREF(obj);
2153 2194 goto bail;
2154 2195 }
2155 2196 sv |= poison;
2156 2197 for (i = 0; i < revcount; i++) {
2157 2198 if (revs[i] == v)
2158 2199 goto done;
2159 2200 }
2160 2201 }
2161 2202 }
2162 2203 if (index_get_parents(self, v, parents, maxrev) < 0)
2163 2204 goto bail;
2164 2205
2165 2206 for (i = 0; i < 2; i++) {
2166 2207 int p = parents[i];
2167 2208 if (p == -1)
2168 2209 continue;
2169 2210 sp = seen[p];
2170 2211 if (sv < poison) {
2171 2212 if (sp == 0) {
2172 2213 seen[p] = sv;
2173 2214 interesting++;
2174 2215 } else if (sp != sv)
2175 2216 seen[p] |= sv;
2176 2217 } else {
2177 2218 if (sp && sp < poison)
2178 2219 interesting--;
2179 2220 seen[p] = sv;
2180 2221 }
2181 2222 }
2182 2223 }
2183 2224
2184 2225 done:
2185 2226 free(seen);
2186 2227 return gca;
2187 2228 bail:
2188 2229 free(seen);
2189 2230 Py_XDECREF(gca);
2190 2231 return NULL;
2191 2232 }
2192 2233
2193 2234 /*
2194 2235 * Given a disjoint set of revs, return the subset with the longest
2195 2236 * path to the root.
2196 2237 */
2197 2238 static PyObject *find_deepest(indexObject *self, PyObject *revs)
2198 2239 {
2199 2240 const Py_ssize_t revcount = PyList_GET_SIZE(revs);
2200 2241 static const Py_ssize_t capacity = 24;
2201 2242 int *depth, *interesting = NULL;
2202 2243 int i, j, v, ninteresting;
2203 2244 PyObject *dict = NULL, *keys = NULL;
2204 2245 long *seen = NULL;
2205 2246 int maxrev = -1;
2206 2247 long final;
2207 2248
2208 2249 if (revcount > capacity) {
2209 2250 PyErr_Format(PyExc_OverflowError,
2210 2251 "bitset size (%ld) > capacity (%ld)",
2211 2252 (long)revcount, (long)capacity);
2212 2253 return NULL;
2213 2254 }
2214 2255
2215 2256 for (i = 0; i < revcount; i++) {
2216 2257 int n = (int)PyInt_AsLong(PyList_GET_ITEM(revs, i));
2217 2258 if (n > maxrev)
2218 2259 maxrev = n;
2219 2260 }
2220 2261
2221 2262 depth = calloc(sizeof(*depth), maxrev + 1);
2222 2263 if (depth == NULL)
2223 2264 return PyErr_NoMemory();
2224 2265
2225 2266 seen = calloc(sizeof(*seen), maxrev + 1);
2226 2267 if (seen == NULL) {
2227 2268 PyErr_NoMemory();
2228 2269 goto bail;
2229 2270 }
2230 2271
2231 2272 interesting = calloc(sizeof(*interesting), ((size_t)1) << revcount);
2232 2273 if (interesting == NULL) {
2233 2274 PyErr_NoMemory();
2234 2275 goto bail;
2235 2276 }
2236 2277
2237 2278 if (PyList_Sort(revs) == -1)
2238 2279 goto bail;
2239 2280
2240 2281 for (i = 0; i < revcount; i++) {
2241 2282 int n = (int)PyInt_AsLong(PyList_GET_ITEM(revs, i));
2242 2283 long b = 1l << i;
2243 2284 depth[n] = 1;
2244 2285 seen[n] = b;
2245 2286 interesting[b] = 1;
2246 2287 }
2247 2288
2248 2289 /* invariant: ninteresting is the number of non-zero entries in
2249 2290 * interesting. */
2250 2291 ninteresting = (int)revcount;
2251 2292
2252 2293 for (v = maxrev; v >= 0 && ninteresting > 1; v--) {
2253 2294 int dv = depth[v];
2254 2295 int parents[2];
2255 2296 long sv;
2256 2297
2257 2298 if (dv == 0)
2258 2299 continue;
2259 2300
2260 2301 sv = seen[v];
2261 2302 if (index_get_parents(self, v, parents, maxrev) < 0)
2262 2303 goto bail;
2263 2304
2264 2305 for (i = 0; i < 2; i++) {
2265 2306 int p = parents[i];
2266 2307 long sp;
2267 2308 int dp;
2268 2309
2269 2310 if (p == -1)
2270 2311 continue;
2271 2312
2272 2313 dp = depth[p];
2273 2314 sp = seen[p];
2274 2315 if (dp <= dv) {
2275 2316 depth[p] = dv + 1;
2276 2317 if (sp != sv) {
2277 2318 interesting[sv] += 1;
2278 2319 seen[p] = sv;
2279 2320 if (sp) {
2280 2321 interesting[sp] -= 1;
2281 2322 if (interesting[sp] == 0)
2282 2323 ninteresting -= 1;
2283 2324 }
2284 2325 }
2285 2326 } else if (dv == dp - 1) {
2286 2327 long nsp = sp | sv;
2287 2328 if (nsp == sp)
2288 2329 continue;
2289 2330 seen[p] = nsp;
2290 2331 interesting[sp] -= 1;
2291 2332 if (interesting[sp] == 0)
2292 2333 ninteresting -= 1;
2293 2334 if (interesting[nsp] == 0)
2294 2335 ninteresting += 1;
2295 2336 interesting[nsp] += 1;
2296 2337 }
2297 2338 }
2298 2339 interesting[sv] -= 1;
2299 2340 if (interesting[sv] == 0)
2300 2341 ninteresting -= 1;
2301 2342 }
2302 2343
2303 2344 final = 0;
2304 2345 j = ninteresting;
2305 2346 for (i = 0; i < (int)(2 << revcount) && j > 0; i++) {
2306 2347 if (interesting[i] == 0)
2307 2348 continue;
2308 2349 final |= i;
2309 2350 j -= 1;
2310 2351 }
2311 2352 if (final == 0) {
2312 2353 keys = PyList_New(0);
2313 2354 goto bail;
2314 2355 }
2315 2356
2316 2357 dict = PyDict_New();
2317 2358 if (dict == NULL)
2318 2359 goto bail;
2319 2360
2320 2361 for (i = 0; i < revcount; i++) {
2321 2362 PyObject *key;
2322 2363
2323 2364 if ((final & (1 << i)) == 0)
2324 2365 continue;
2325 2366
2326 2367 key = PyList_GET_ITEM(revs, i);
2327 2368 Py_INCREF(key);
2328 2369 Py_INCREF(Py_None);
2329 2370 if (PyDict_SetItem(dict, key, Py_None) == -1) {
2330 2371 Py_DECREF(key);
2331 2372 Py_DECREF(Py_None);
2332 2373 goto bail;
2333 2374 }
2334 2375 }
2335 2376
2336 2377 keys = PyDict_Keys(dict);
2337 2378
2338 2379 bail:
2339 2380 free(depth);
2340 2381 free(seen);
2341 2382 free(interesting);
2342 2383 Py_XDECREF(dict);
2343 2384
2344 2385 return keys;
2345 2386 }
2346 2387
2347 2388 /*
2348 2389 * Given a (possibly overlapping) set of revs, return all the
2349 2390 * common ancestors heads: heads(::args[0] and ::a[1] and ...)
2350 2391 */
2351 2392 static PyObject *index_commonancestorsheads(indexObject *self, PyObject *args)
2352 2393 {
2353 2394 PyObject *ret = NULL;
2354 2395 Py_ssize_t argcount, i, len;
2355 2396 bitmask repeat = 0;
2356 2397 int revcount = 0;
2357 2398 int *revs;
2358 2399
2359 2400 argcount = PySequence_Length(args);
2360 2401 revs = PyMem_Malloc(argcount * sizeof(*revs));
2361 2402 if (argcount > 0 && revs == NULL)
2362 2403 return PyErr_NoMemory();
2363 2404 len = index_length(self);
2364 2405
2365 2406 for (i = 0; i < argcount; i++) {
2366 2407 static const int capacity = 24;
2367 2408 PyObject *obj = PySequence_GetItem(args, i);
2368 2409 bitmask x;
2369 2410 long val;
2370 2411
2371 2412 if (!PyInt_Check(obj)) {
2372 2413 PyErr_SetString(PyExc_TypeError,
2373 2414 "arguments must all be ints");
2374 2415 Py_DECREF(obj);
2375 2416 goto bail;
2376 2417 }
2377 2418 val = PyInt_AsLong(obj);
2378 2419 Py_DECREF(obj);
2379 2420 if (val == -1) {
2380 2421 ret = PyList_New(0);
2381 2422 goto done;
2382 2423 }
2383 2424 if (val < 0 || val >= len) {
2384 2425 PyErr_SetString(PyExc_IndexError, "index out of range");
2385 2426 goto bail;
2386 2427 }
2387 2428 /* this cheesy bloom filter lets us avoid some more
2388 2429 * expensive duplicate checks in the common set-is-disjoint
2389 2430 * case */
2390 2431 x = 1ull << (val & 0x3f);
2391 2432 if (repeat & x) {
2392 2433 int k;
2393 2434 for (k = 0; k < revcount; k++) {
2394 2435 if (val == revs[k])
2395 2436 goto duplicate;
2396 2437 }
2397 2438 } else
2398 2439 repeat |= x;
2399 2440 if (revcount >= capacity) {
2400 2441 PyErr_Format(PyExc_OverflowError,
2401 2442 "bitset size (%d) > capacity (%d)",
2402 2443 revcount, capacity);
2403 2444 goto bail;
2404 2445 }
2405 2446 revs[revcount++] = (int)val;
2406 2447 duplicate:;
2407 2448 }
2408 2449
2409 2450 if (revcount == 0) {
2410 2451 ret = PyList_New(0);
2411 2452 goto done;
2412 2453 }
2413 2454 if (revcount == 1) {
2414 2455 PyObject *obj;
2415 2456 ret = PyList_New(1);
2416 2457 if (ret == NULL)
2417 2458 goto bail;
2418 2459 obj = PyInt_FromLong(revs[0]);
2419 2460 if (obj == NULL)
2420 2461 goto bail;
2421 2462 PyList_SET_ITEM(ret, 0, obj);
2422 2463 goto done;
2423 2464 }
2424 2465
2425 2466 ret = find_gca_candidates(self, revs, revcount);
2426 2467 if (ret == NULL)
2427 2468 goto bail;
2428 2469
2429 2470 done:
2430 2471 PyMem_Free(revs);
2431 2472 return ret;
2432 2473
2433 2474 bail:
2434 2475 PyMem_Free(revs);
2435 2476 Py_XDECREF(ret);
2436 2477 return NULL;
2437 2478 }
2438 2479
2439 2480 /*
2440 2481 * Given a (possibly overlapping) set of revs, return the greatest
2441 2482 * common ancestors: those with the longest path to the root.
2442 2483 */
2443 2484 static PyObject *index_ancestors(indexObject *self, PyObject *args)
2444 2485 {
2445 2486 PyObject *ret;
2446 2487 PyObject *gca = index_commonancestorsheads(self, args);
2447 2488 if (gca == NULL)
2448 2489 return NULL;
2449 2490
2450 2491 if (PyList_GET_SIZE(gca) <= 1) {
2451 2492 return gca;
2452 2493 }
2453 2494
2454 2495 ret = find_deepest(self, gca);
2455 2496 Py_DECREF(gca);
2456 2497 return ret;
2457 2498 }
2458 2499
2459 2500 /*
2460 2501 * Invalidate any trie entries introduced by added revs.
2461 2502 */
2462 2503 static void index_invalidate_added(indexObject *self, Py_ssize_t start)
2463 2504 {
2464 2505 Py_ssize_t i, len = PyList_GET_SIZE(self->added);
2465 2506
2466 2507 for (i = start; i < len; i++) {
2467 2508 PyObject *tuple = PyList_GET_ITEM(self->added, i);
2468 2509 PyObject *node = PyTuple_GET_ITEM(tuple, 7);
2469 2510
2470 2511 nt_delete_node(&self->nt, PyBytes_AS_STRING(node));
2471 2512 }
2472 2513
2473 2514 if (start == 0)
2474 2515 Py_CLEAR(self->added);
2475 2516 }
2476 2517
2477 2518 /*
2478 2519 * Delete a numeric range of revs, which must be at the end of the
2479 2520 * range.
2480 2521 */
2481 2522 static int index_slice_del(indexObject *self, PyObject *item)
2482 2523 {
2483 2524 Py_ssize_t start, stop, step, slicelength;
2484 2525 Py_ssize_t length = index_length(self) + 1;
2485 2526 int ret = 0;
2486 2527
2487 2528 /* Argument changed from PySliceObject* to PyObject* in Python 3. */
2488 2529 #ifdef IS_PY3K
2489 2530 if (PySlice_GetIndicesEx(item, length, &start, &stop, &step,
2490 2531 &slicelength) < 0)
2491 2532 #else
2492 2533 if (PySlice_GetIndicesEx((PySliceObject *)item, length, &start, &stop,
2493 2534 &step, &slicelength) < 0)
2494 2535 #endif
2495 2536 return -1;
2496 2537
2497 2538 if (slicelength <= 0)
2498 2539 return 0;
2499 2540
2500 2541 if ((step < 0 && start < stop) || (step > 0 && start > stop))
2501 2542 stop = start;
2502 2543
2503 2544 if (step < 0) {
2504 2545 stop = start + 1;
2505 2546 start = stop + step * (slicelength - 1) - 1;
2506 2547 step = -step;
2507 2548 }
2508 2549
2509 2550 if (step != 1) {
2510 2551 PyErr_SetString(PyExc_ValueError,
2511 2552 "revlog index delete requires step size of 1");
2512 2553 return -1;
2513 2554 }
2514 2555
2515 2556 if (stop != length - 1) {
2516 2557 PyErr_SetString(PyExc_IndexError,
2517 2558 "revlog index deletion indices are invalid");
2518 2559 return -1;
2519 2560 }
2520 2561
2521 2562 if (start < self->length) {
2522 2563 if (self->ntinitialized) {
2523 2564 Py_ssize_t i;
2524 2565
2525 2566 for (i = start; i < self->length; i++) {
2526 2567 const char *node = index_node_existing(self, i);
2527 2568 if (node == NULL)
2528 2569 return -1;
2529 2570
2530 2571 nt_delete_node(&self->nt, node);
2531 2572 }
2532 2573 if (self->added)
2533 2574 index_invalidate_added(self, 0);
2534 2575 if (self->ntrev > start)
2535 2576 self->ntrev = (int)start;
2536 2577 } else if (self->added) {
2537 2578 Py_CLEAR(self->added);
2538 2579 }
2539 2580
2540 2581 self->length = start;
2541 2582 if (start < self->raw_length) {
2542 2583 if (self->cache) {
2543 2584 Py_ssize_t i;
2544 2585 for (i = start; i < self->raw_length; i++)
2545 2586 Py_CLEAR(self->cache[i]);
2546 2587 }
2547 2588 self->raw_length = start;
2548 2589 }
2549 2590 goto done;
2550 2591 }
2551 2592
2552 2593 if (self->ntinitialized) {
2553 2594 index_invalidate_added(self, start - self->length);
2554 2595 if (self->ntrev > start)
2555 2596 self->ntrev = (int)start;
2556 2597 }
2557 2598 if (self->added)
2558 2599 ret = PyList_SetSlice(self->added, start - self->length,
2559 2600 PyList_GET_SIZE(self->added), NULL);
2560 2601 done:
2561 2602 Py_CLEAR(self->headrevs);
2562 2603 return ret;
2563 2604 }
2564 2605
2565 2606 /*
2566 2607 * Supported ops:
2567 2608 *
2568 2609 * slice deletion
2569 2610 * string assignment (extend node->rev mapping)
2570 2611 * string deletion (shrink node->rev mapping)
2571 2612 */
2572 2613 static int index_assign_subscript(indexObject *self, PyObject *item,
2573 2614 PyObject *value)
2574 2615 {
2575 2616 char *node;
2576 2617 long rev;
2577 2618
2578 2619 if (PySlice_Check(item) && value == NULL)
2579 2620 return index_slice_del(self, item);
2580 2621
2581 2622 if (node_check(item, &node) == -1)
2582 2623 return -1;
2583 2624
2584 2625 if (value == NULL)
2585 2626 return self->ntinitialized ? nt_delete_node(&self->nt, node)
2586 2627 : 0;
2587 2628 rev = PyInt_AsLong(value);
2588 2629 if (rev > INT_MAX || rev < 0) {
2589 2630 if (!PyErr_Occurred())
2590 2631 PyErr_SetString(PyExc_ValueError, "rev out of range");
2591 2632 return -1;
2592 2633 }
2593 2634
2594 2635 if (index_init_nt(self) == -1)
2595 2636 return -1;
2596 2637 return nt_insert(&self->nt, node, (int)rev);
2597 2638 }
2598 2639
2599 2640 /*
2600 2641 * Find all RevlogNG entries in an index that has inline data. Update
2601 2642 * the optional "offsets" table with those entries.
2602 2643 */
2603 2644 static Py_ssize_t inline_scan(indexObject *self, const char **offsets)
2604 2645 {
2605 2646 const char *data = (const char *)self->buf.buf;
2606 2647 Py_ssize_t pos = 0;
2607 2648 Py_ssize_t end = self->buf.len;
2608 2649 long incr = v1_hdrsize;
2609 2650 Py_ssize_t len = 0;
2610 2651
2611 2652 while (pos + v1_hdrsize <= end && pos >= 0) {
2612 2653 uint32_t comp_len;
2613 2654 /* 3rd element of header is length of compressed inline data */
2614 2655 comp_len = getbe32(data + pos + 8);
2615 2656 incr = v1_hdrsize + comp_len;
2616 2657 if (offsets)
2617 2658 offsets[len] = data + pos;
2618 2659 len++;
2619 2660 pos += incr;
2620 2661 }
2621 2662
2622 2663 if (pos != end) {
2623 2664 if (!PyErr_Occurred())
2624 2665 PyErr_SetString(PyExc_ValueError, "corrupt index file");
2625 2666 return -1;
2626 2667 }
2627 2668
2628 2669 return len;
2629 2670 }
2630 2671
2631 2672 static int index_init(indexObject *self, PyObject *args)
2632 2673 {
2633 2674 PyObject *data_obj, *inlined_obj;
2634 2675 Py_ssize_t size;
2635 2676
2636 2677 /* Initialize before argument-checking to avoid index_dealloc() crash.
2637 2678 */
2638 2679 self->raw_length = 0;
2639 2680 self->added = NULL;
2640 2681 self->cache = NULL;
2641 2682 self->data = NULL;
2642 2683 memset(&self->buf, 0, sizeof(self->buf));
2643 2684 self->headrevs = NULL;
2644 2685 self->filteredrevs = Py_None;
2645 2686 Py_INCREF(Py_None);
2646 2687 self->ntinitialized = 0;
2647 2688 self->offsets = NULL;
2648 2689
2649 2690 if (!PyArg_ParseTuple(args, "OO", &data_obj, &inlined_obj))
2650 2691 return -1;
2651 2692 if (!PyObject_CheckBuffer(data_obj)) {
2652 2693 PyErr_SetString(PyExc_TypeError,
2653 2694 "data does not support buffer interface");
2654 2695 return -1;
2655 2696 }
2656 2697
2657 2698 if (PyObject_GetBuffer(data_obj, &self->buf, PyBUF_SIMPLE) == -1)
2658 2699 return -1;
2659 2700 size = self->buf.len;
2660 2701
2661 2702 self->inlined = inlined_obj && PyObject_IsTrue(inlined_obj);
2662 2703 self->data = data_obj;
2663 2704
2664 2705 self->ntlookups = self->ntmisses = 0;
2665 2706 self->ntrev = -1;
2666 2707 Py_INCREF(self->data);
2667 2708
2668 2709 if (self->inlined) {
2669 2710 Py_ssize_t len = inline_scan(self, NULL);
2670 2711 if (len == -1)
2671 2712 goto bail;
2672 2713 self->raw_length = len;
2673 2714 self->length = len;
2674 2715 } else {
2675 2716 if (size % v1_hdrsize) {
2676 2717 PyErr_SetString(PyExc_ValueError, "corrupt index file");
2677 2718 goto bail;
2678 2719 }
2679 2720 self->raw_length = size / v1_hdrsize;
2680 2721 self->length = self->raw_length;
2681 2722 }
2682 2723
2683 2724 return 0;
2684 2725 bail:
2685 2726 return -1;
2686 2727 }
2687 2728
2688 2729 static PyObject *index_nodemap(indexObject *self)
2689 2730 {
2690 2731 Py_INCREF(self);
2691 2732 return (PyObject *)self;
2692 2733 }
2693 2734
2694 2735 static void _index_clearcaches(indexObject *self)
2695 2736 {
2696 2737 if (self->cache) {
2697 2738 Py_ssize_t i;
2698 2739
2699 2740 for (i = 0; i < self->raw_length; i++)
2700 2741 Py_CLEAR(self->cache[i]);
2701 2742 free(self->cache);
2702 2743 self->cache = NULL;
2703 2744 }
2704 2745 if (self->offsets) {
2705 2746 PyMem_Free((void *)self->offsets);
2706 2747 self->offsets = NULL;
2707 2748 }
2708 2749 if (self->ntinitialized) {
2709 2750 nt_dealloc(&self->nt);
2710 2751 }
2711 2752 self->ntinitialized = 0;
2712 2753 Py_CLEAR(self->headrevs);
2713 2754 }
2714 2755
2715 2756 static PyObject *index_clearcaches(indexObject *self)
2716 2757 {
2717 2758 _index_clearcaches(self);
2718 2759 self->ntrev = -1;
2719 2760 self->ntlookups = self->ntmisses = 0;
2720 2761 Py_RETURN_NONE;
2721 2762 }
2722 2763
2723 2764 static void index_dealloc(indexObject *self)
2724 2765 {
2725 2766 _index_clearcaches(self);
2726 2767 Py_XDECREF(self->filteredrevs);
2727 2768 if (self->buf.buf) {
2728 2769 PyBuffer_Release(&self->buf);
2729 2770 memset(&self->buf, 0, sizeof(self->buf));
2730 2771 }
2731 2772 Py_XDECREF(self->data);
2732 2773 Py_XDECREF(self->added);
2733 2774 PyObject_Del(self);
2734 2775 }
2735 2776
2736 2777 static PySequenceMethods index_sequence_methods = {
2737 2778 (lenfunc)index_length, /* sq_length */
2738 2779 0, /* sq_concat */
2739 2780 0, /* sq_repeat */
2740 2781 (ssizeargfunc)index_get, /* sq_item */
2741 2782 0, /* sq_slice */
2742 2783 0, /* sq_ass_item */
2743 2784 0, /* sq_ass_slice */
2744 2785 (objobjproc)index_contains, /* sq_contains */
2745 2786 };
2746 2787
2747 2788 static PyMappingMethods index_mapping_methods = {
2748 2789 (lenfunc)index_length, /* mp_length */
2749 2790 (binaryfunc)index_getitem, /* mp_subscript */
2750 2791 (objobjargproc)index_assign_subscript, /* mp_ass_subscript */
2751 2792 };
2752 2793
2753 2794 static PyMethodDef index_methods[] = {
2754 2795 {"ancestors", (PyCFunction)index_ancestors, METH_VARARGS,
2755 2796 "return the gca set of the given revs"},
2756 2797 {"commonancestorsheads", (PyCFunction)index_commonancestorsheads,
2757 2798 METH_VARARGS,
2758 2799 "return the heads of the common ancestors of the given revs"},
2759 2800 {"clearcaches", (PyCFunction)index_clearcaches, METH_NOARGS,
2760 2801 "clear the index caches"},
2761 2802 {"get", (PyCFunction)index_m_get, METH_VARARGS, "get an index entry"},
2762 2803 {"get_rev", (PyCFunction)index_m_get, METH_VARARGS,
2763 2804 "return `rev` associated with a node or None"},
2764 2805 {"has_node", (PyCFunction)index_m_has_node, METH_O,
2765 2806 "return True if the node exist in the index"},
2766 2807 {"rev", (PyCFunction)index_m_rev, METH_O,
2767 2808 "return `rev` associated with a node or raise RevlogError"},
2768 2809 {"computephasesmapsets", (PyCFunction)compute_phases_map_sets, METH_VARARGS,
2769 2810 "compute phases"},
2770 2811 {"reachableroots2", (PyCFunction)reachableroots2, METH_VARARGS,
2771 2812 "reachableroots"},
2772 2813 {"headrevs", (PyCFunction)index_headrevs, METH_VARARGS,
2773 2814 "get head revisions"}, /* Can do filtering since 3.2 */
2774 2815 {"headrevsfiltered", (PyCFunction)index_headrevs, METH_VARARGS,
2775 2816 "get filtered head revisions"}, /* Can always do filtering */
2776 2817 {"issnapshot", (PyCFunction)index_issnapshot, METH_O,
2777 2818 "True if the object is a snapshot"},
2778 2819 {"findsnapshots", (PyCFunction)index_findsnapshots, METH_VARARGS,
2779 2820 "Gather snapshot data in a cache dict"},
2780 2821 {"deltachain", (PyCFunction)index_deltachain, METH_VARARGS,
2781 2822 "determine revisions with deltas to reconstruct fulltext"},
2782 2823 {"slicechunktodensity", (PyCFunction)index_slicechunktodensity,
2783 2824 METH_VARARGS, "determine revisions with deltas to reconstruct fulltext"},
2784 2825 {"append", (PyCFunction)index_append, METH_O, "append an index entry"},
2785 2826 {"partialmatch", (PyCFunction)index_partialmatch, METH_VARARGS,
2786 2827 "match a potentially ambiguous node ID"},
2787 2828 {"shortest", (PyCFunction)index_shortest, METH_VARARGS,
2788 2829 "find length of shortest hex nodeid of a binary ID"},
2789 2830 {"stats", (PyCFunction)index_stats, METH_NOARGS, "stats for the index"},
2790 2831 {NULL} /* Sentinel */
2791 2832 };
2792 2833
2793 2834 static PyGetSetDef index_getset[] = {
2794 2835 {"nodemap", (getter)index_nodemap, NULL, "nodemap", NULL},
2795 2836 {NULL} /* Sentinel */
2796 2837 };
2797 2838
2798 2839 PyTypeObject HgRevlogIndex_Type = {
2799 2840 PyVarObject_HEAD_INIT(NULL, 0) /* header */
2800 2841 "parsers.index", /* tp_name */
2801 2842 sizeof(indexObject), /* tp_basicsize */
2802 2843 0, /* tp_itemsize */
2803 2844 (destructor)index_dealloc, /* tp_dealloc */
2804 2845 0, /* tp_print */
2805 2846 0, /* tp_getattr */
2806 2847 0, /* tp_setattr */
2807 2848 0, /* tp_compare */
2808 2849 0, /* tp_repr */
2809 2850 0, /* tp_as_number */
2810 2851 &index_sequence_methods, /* tp_as_sequence */
2811 2852 &index_mapping_methods, /* tp_as_mapping */
2812 2853 0, /* tp_hash */
2813 2854 0, /* tp_call */
2814 2855 0, /* tp_str */
2815 2856 0, /* tp_getattro */
2816 2857 0, /* tp_setattro */
2817 2858 0, /* tp_as_buffer */
2818 2859 Py_TPFLAGS_DEFAULT, /* tp_flags */
2819 2860 "revlog index", /* tp_doc */
2820 2861 0, /* tp_traverse */
2821 2862 0, /* tp_clear */
2822 2863 0, /* tp_richcompare */
2823 2864 0, /* tp_weaklistoffset */
2824 2865 0, /* tp_iter */
2825 2866 0, /* tp_iternext */
2826 2867 index_methods, /* tp_methods */
2827 2868 0, /* tp_members */
2828 2869 index_getset, /* tp_getset */
2829 2870 0, /* tp_base */
2830 2871 0, /* tp_dict */
2831 2872 0, /* tp_descr_get */
2832 2873 0, /* tp_descr_set */
2833 2874 0, /* tp_dictoffset */
2834 2875 (initproc)index_init, /* tp_init */
2835 2876 0, /* tp_alloc */
2836 2877 };
2837 2878
2838 2879 /*
2839 2880 * returns a tuple of the form (index, index, cache) with elements as
2840 2881 * follows:
2841 2882 *
2842 2883 * index: an index object that lazily parses RevlogNG records
2843 2884 * cache: if data is inlined, a tuple (0, index_file_content), else None
2844 2885 * index_file_content could be a string, or a buffer
2845 2886 *
2846 2887 * added complications are for backwards compatibility
2847 2888 */
2848 2889 PyObject *parse_index2(PyObject *self, PyObject *args)
2849 2890 {
2850 2891 PyObject *tuple = NULL, *cache = NULL;
2851 2892 indexObject *idx;
2852 2893 int ret;
2853 2894
2854 2895 idx = PyObject_New(indexObject, &HgRevlogIndex_Type);
2855 2896 if (idx == NULL)
2856 2897 goto bail;
2857 2898
2858 2899 ret = index_init(idx, args);
2859 2900 if (ret == -1)
2860 2901 goto bail;
2861 2902
2862 2903 if (idx->inlined) {
2863 2904 cache = Py_BuildValue("iO", 0, idx->data);
2864 2905 if (cache == NULL)
2865 2906 goto bail;
2866 2907 } else {
2867 2908 cache = Py_None;
2868 2909 Py_INCREF(cache);
2869 2910 }
2870 2911
2871 2912 tuple = Py_BuildValue("NN", idx, cache);
2872 2913 if (!tuple)
2873 2914 goto bail;
2874 2915 return tuple;
2875 2916
2876 2917 bail:
2877 2918 Py_XDECREF(idx);
2878 2919 Py_XDECREF(cache);
2879 2920 Py_XDECREF(tuple);
2880 2921 return NULL;
2881 2922 }
2882 2923
2883 2924 static Revlog_CAPI CAPI = {
2884 2925 /* increment the abi_version field upon each change in the Revlog_CAPI
2885 2926 struct or in the ABI of the listed functions */
2886 2927 2,
2887 2928 index_length,
2888 2929 index_node,
2889 2930 HgRevlogIndex_GetParents,
2890 2931 };
2891 2932
2892 2933 void revlog_module_init(PyObject *mod)
2893 2934 {
2894 2935 PyObject *caps = NULL;
2895 2936 HgRevlogIndex_Type.tp_new = PyType_GenericNew;
2896 2937 if (PyType_Ready(&HgRevlogIndex_Type) < 0)
2897 2938 return;
2898 2939 Py_INCREF(&HgRevlogIndex_Type);
2899 2940 PyModule_AddObject(mod, "index", (PyObject *)&HgRevlogIndex_Type);
2900 2941
2901 2942 nodetreeType.tp_new = PyType_GenericNew;
2902 2943 if (PyType_Ready(&nodetreeType) < 0)
2903 2944 return;
2904 2945 Py_INCREF(&nodetreeType);
2905 2946 PyModule_AddObject(mod, "nodetree", (PyObject *)&nodetreeType);
2906 2947
2907 2948 if (!nullentry) {
2908 2949 nullentry =
2909 2950 Py_BuildValue(PY23("iiiiiiis#", "iiiiiiiy#"), 0, 0, 0, -1,
2910 2951 -1, -1, -1, nullid, (Py_ssize_t)20);
2911 2952 }
2912 2953 if (nullentry)
2913 2954 PyObject_GC_UnTrack(nullentry);
2914 2955
2915 2956 caps = PyCapsule_New(&CAPI, "mercurial.cext.parsers.revlog_CAPI", NULL);
2916 2957 if (caps != NULL)
2917 2958 PyModule_AddObject(mod, "revlog_CAPI", caps);
2918 2959 }
@@ -1,924 +1,929 b''
1 1 """ Mercurial phases support code
2 2
3 3 ---
4 4
5 5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 6 Logilab SA <contact@logilab.fr>
7 7 Augie Fackler <durin42@gmail.com>
8 8
9 9 This software may be used and distributed according to the terms
10 10 of the GNU General Public License version 2 or any later version.
11 11
12 12 ---
13 13
14 14 This module implements most phase logic in mercurial.
15 15
16 16
17 17 Basic Concept
18 18 =============
19 19
20 20 A 'changeset phase' is an indicator that tells us how a changeset is
21 21 manipulated and communicated. The details of each phase is described
22 22 below, here we describe the properties they have in common.
23 23
24 24 Like bookmarks, phases are not stored in history and thus are not
25 25 permanent and leave no audit trail.
26 26
27 27 First, no changeset can be in two phases at once. Phases are ordered,
28 28 so they can be considered from lowest to highest. The default, lowest
29 29 phase is 'public' - this is the normal phase of existing changesets. A
30 30 child changeset can not be in a lower phase than its parents.
31 31
32 32 These phases share a hierarchy of traits:
33 33
34 34 immutable shared
35 35 public: X X
36 36 draft: X
37 37 secret:
38 38
39 39 Local commits are draft by default.
40 40
41 41 Phase Movement and Exchange
42 42 ===========================
43 43
44 44 Phase data is exchanged by pushkey on pull and push. Some servers have
45 45 a publish option set, we call such a server a "publishing server".
46 46 Pushing a draft changeset to a publishing server changes the phase to
47 47 public.
48 48
49 49 A small list of fact/rules define the exchange of phase:
50 50
51 51 * old client never changes server states
52 52 * pull never changes server states
53 53 * publish and old server changesets are seen as public by client
54 54 * any secret changeset seen in another repository is lowered to at
55 55 least draft
56 56
57 57 Here is the final table summing up the 49 possible use cases of phase
58 58 exchange:
59 59
60 60 server
61 61 old publish non-publish
62 62 N X N D P N D P
63 63 old client
64 64 pull
65 65 N - X/X - X/D X/P - X/D X/P
66 66 X - X/X - X/D X/P - X/D X/P
67 67 push
68 68 X X/X X/X X/P X/P X/P X/D X/D X/P
69 69 new client
70 70 pull
71 71 N - P/X - P/D P/P - D/D P/P
72 72 D - P/X - P/D P/P - D/D P/P
73 73 P - P/X - P/D P/P - P/D P/P
74 74 push
75 75 D P/X P/X P/P P/P P/P D/D D/D P/P
76 76 P P/X P/X P/P P/P P/P P/P P/P P/P
77 77
78 78 Legend:
79 79
80 80 A/B = final state on client / state on server
81 81
82 82 * N = new/not present,
83 83 * P = public,
84 84 * D = draft,
85 85 * X = not tracked (i.e., the old client or server has no internal
86 86 way of recording the phase.)
87 87
88 88 passive = only pushes
89 89
90 90
91 91 A cell here can be read like this:
92 92
93 93 "When a new client pushes a draft changeset (D) to a publishing
94 94 server where it's not present (N), it's marked public on both
95 95 sides (P/P)."
96 96
97 97 Note: old client behave as a publishing server with draft only content
98 98 - other people see it as public
99 99 - content is pushed as draft
100 100
101 101 """
102 102
103 103 from __future__ import absolute_import
104 104
105 105 import errno
106 106 import struct
107 107
108 108 from .i18n import _
109 109 from .node import (
110 110 bin,
111 111 hex,
112 112 nullid,
113 113 nullrev,
114 114 short,
115 115 wdirrev,
116 116 )
117 117 from .pycompat import (
118 118 getattr,
119 119 setattr,
120 120 )
121 121 from . import (
122 122 error,
123 123 pycompat,
124 124 smartset,
125 125 txnutil,
126 126 util,
127 127 )
128 128
129 129 _fphasesentry = struct.Struct(b'>i20s')
130 130
131 131 # record phase index
132 132 public, draft, secret = range(3)
133 133 archived = 32 # non-continuous for compatibility
134 134 internal = 96 # non-continuous for compatibility
135 135 allphases = (public, draft, secret, archived, internal)
136 136 trackedphases = (draft, secret, archived, internal)
137 137 # record phase names
138 138 cmdphasenames = [b'public', b'draft', b'secret'] # known to `hg phase` command
139 139 phasenames = dict(enumerate(cmdphasenames))
140 140 phasenames[archived] = b'archived'
141 141 phasenames[internal] = b'internal'
142 142 # map phase name to phase number
143 143 phasenumber = {name: phase for phase, name in phasenames.items()}
144 144 # like phasenumber, but also include maps for the numeric and binary
145 145 # phase number to the phase number
146 146 phasenumber2 = phasenumber.copy()
147 147 phasenumber2.update({phase: phase for phase in phasenames})
148 148 phasenumber2.update({b'%i' % phase: phase for phase in phasenames})
149 149 # record phase property
150 150 mutablephases = (draft, secret, archived, internal)
151 151 remotehiddenphases = (secret, archived, internal)
152 152 localhiddenphases = (internal, archived)
153 153
154 154
155 155 def supportinternal(repo):
156 156 """True if the internal phase can be used on a repository"""
157 157 return b'internal-phase' in repo.requirements
158 158
159 159
160 160 def _readroots(repo, phasedefaults=None):
161 161 """Read phase roots from disk
162 162
163 163 phasedefaults is a list of fn(repo, roots) callable, which are
164 164 executed if the phase roots file does not exist. When phases are
165 165 being initialized on an existing repository, this could be used to
166 166 set selected changesets phase to something else than public.
167 167
168 168 Return (roots, dirty) where dirty is true if roots differ from
169 169 what is being stored.
170 170 """
171 171 repo = repo.unfiltered()
172 172 dirty = False
173 roots = [set() for i in range(max(allphases) + 1)]
173 roots = {i: set() for i in allphases}
174 174 try:
175 175 f, pending = txnutil.trypending(repo.root, repo.svfs, b'phaseroots')
176 176 try:
177 177 for line in f:
178 178 phase, nh = line.split()
179 179 roots[int(phase)].add(bin(nh))
180 180 finally:
181 181 f.close()
182 182 except IOError as inst:
183 183 if inst.errno != errno.ENOENT:
184 184 raise
185 185 if phasedefaults:
186 186 for f in phasedefaults:
187 187 roots = f(repo, roots)
188 188 dirty = True
189 189 return roots, dirty
190 190
191 191
192 192 def binaryencode(phasemapping):
193 193 """encode a 'phase -> nodes' mapping into a binary stream
194 194
195 195 The revision lists are encoded as (phase, root) pairs.
196 196 """
197 197 binarydata = []
198 198 for phase, nodes in pycompat.iteritems(phasemapping):
199 199 for head in nodes:
200 200 binarydata.append(_fphasesentry.pack(phase, head))
201 201 return b''.join(binarydata)
202 202
203 203
204 204 def binarydecode(stream):
205 205 """decode a binary stream into a 'phase -> nodes' mapping
206 206
207 207 The (phase, root) pairs are turned back into a dictionary with
208 208 the phase as index and the aggregated roots of that phase as value."""
209 209 headsbyphase = {i: [] for i in allphases}
210 210 entrysize = _fphasesentry.size
211 211 while True:
212 212 entry = stream.read(entrysize)
213 213 if len(entry) < entrysize:
214 214 if entry:
215 215 raise error.Abort(_(b'bad phase-heads stream'))
216 216 break
217 217 phase, node = _fphasesentry.unpack(entry)
218 218 headsbyphase[phase].append(node)
219 219 return headsbyphase
220 220
221 221
222 222 def _sortedrange_insert(data, idx, rev, t):
223 223 merge_before = False
224 224 if idx:
225 225 r1, t1 = data[idx - 1]
226 226 merge_before = r1[-1] + 1 == rev and t1 == t
227 227 merge_after = False
228 228 if idx < len(data):
229 229 r2, t2 = data[idx]
230 230 merge_after = r2[0] == rev + 1 and t2 == t
231 231
232 232 if merge_before and merge_after:
233 233 data[idx - 1] = (pycompat.xrange(r1[0], r2[-1] + 1), t)
234 234 data.pop(idx)
235 235 elif merge_before:
236 236 data[idx - 1] = (pycompat.xrange(r1[0], rev + 1), t)
237 237 elif merge_after:
238 238 data[idx] = (pycompat.xrange(rev, r2[-1] + 1), t)
239 239 else:
240 240 data.insert(idx, (pycompat.xrange(rev, rev + 1), t))
241 241
242 242
243 243 def _sortedrange_split(data, idx, rev, t):
244 244 r1, t1 = data[idx]
245 245 if t == t1:
246 246 return
247 247 t = (t1[0], t[1])
248 248 if len(r1) == 1:
249 249 data.pop(idx)
250 250 _sortedrange_insert(data, idx, rev, t)
251 251 elif r1[0] == rev:
252 252 data[idx] = (pycompat.xrange(rev + 1, r1[-1] + 1), t1)
253 253 _sortedrange_insert(data, idx, rev, t)
254 254 elif r1[-1] == rev:
255 255 data[idx] = (pycompat.xrange(r1[0], rev), t1)
256 256 _sortedrange_insert(data, idx + 1, rev, t)
257 257 else:
258 258 data[idx : idx + 1] = [
259 259 (pycompat.xrange(r1[0], rev), t1),
260 260 (pycompat.xrange(rev, rev + 1), t),
261 261 (pycompat.xrange(rev + 1, r1[-1] + 1), t1),
262 262 ]
263 263
264 264
265 265 def _trackphasechange(data, rev, old, new):
266 266 """add a phase move to the <data> list of ranges
267 267
268 268 If data is None, nothing happens.
269 269 """
270 270 if data is None:
271 271 return
272 272
273 273 # If data is empty, create a one-revision range and done
274 274 if not data:
275 275 data.insert(0, (pycompat.xrange(rev, rev + 1), (old, new)))
276 276 return
277 277
278 278 low = 0
279 279 high = len(data)
280 280 t = (old, new)
281 281 while low < high:
282 282 mid = (low + high) // 2
283 283 revs = data[mid][0]
284 284
285 285 if rev in revs:
286 286 _sortedrange_split(data, mid, rev, t)
287 287 return
288 288
289 289 if revs[0] == rev + 1:
290 290 if mid and data[mid - 1][0][-1] == rev:
291 291 _sortedrange_split(data, mid - 1, rev, t)
292 292 else:
293 293 _sortedrange_insert(data, mid, rev, t)
294 294 return
295 295
296 296 if revs[-1] == rev - 1:
297 297 if mid + 1 < len(data) and data[mid + 1][0][0] == rev:
298 298 _sortedrange_split(data, mid + 1, rev, t)
299 299 else:
300 300 _sortedrange_insert(data, mid + 1, rev, t)
301 301 return
302 302
303 303 if revs[0] > rev:
304 304 high = mid
305 305 else:
306 306 low = mid + 1
307 307
308 308 if low == len(data):
309 309 data.append((pycompat.xrange(rev, rev + 1), t))
310 310 return
311 311
312 312 r1, t1 = data[low]
313 313 if r1[0] > rev:
314 314 data.insert(low, (pycompat.xrange(rev, rev + 1), t))
315 315 else:
316 316 data.insert(low + 1, (pycompat.xrange(rev, rev + 1), t))
317 317
318 318
319 319 class phasecache(object):
320 320 def __init__(self, repo, phasedefaults, _load=True):
321 321 if _load:
322 322 # Cheap trick to allow shallow-copy without copy module
323 323 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
324 324 self._loadedrevslen = 0
325 325 self._phasesets = None
326 326 self.filterunknown(repo)
327 327 self.opener = repo.svfs
328 328
329 329 def hasnonpublicphases(self, repo):
330 330 """detect if there are revisions with non-public phase"""
331 331 repo = repo.unfiltered()
332 332 cl = repo.changelog
333 333 if len(cl) >= self._loadedrevslen:
334 334 self.invalidate()
335 335 self.loadphaserevs(repo)
336 return any(self.phaseroots[1:])
336 return any(
337 revs
338 for phase, revs in pycompat.iteritems(self.phaseroots)
339 if phase != public
340 )
337 341
338 342 def nonpublicphaseroots(self, repo):
339 343 """returns the roots of all non-public phases
340 344
341 345 The roots are not minimized, so if the secret revisions are
342 346 descendants of draft revisions, their roots will still be present.
343 347 """
344 348 repo = repo.unfiltered()
345 349 cl = repo.changelog
346 350 if len(cl) >= self._loadedrevslen:
347 351 self.invalidate()
348 352 self.loadphaserevs(repo)
349 return set().union(*[roots for roots in self.phaseroots[1:] if roots])
353 return set().union(
354 *[
355 revs
356 for phase, revs in pycompat.iteritems(self.phaseroots)
357 if phase != public
358 ]
359 )
350 360
351 361 def getrevset(self, repo, phases, subset=None):
352 362 """return a smartset for the given phases"""
353 363 self.loadphaserevs(repo) # ensure phase's sets are loaded
354 364 phases = set(phases)
355 365 publicphase = public in phases
356 366
357 367 if publicphase:
358 368 # In this case, phases keeps all the *other* phases.
359 369 phases = set(allphases).difference(phases)
360 370 if not phases:
361 371 return smartset.fullreposet(repo)
362 372
363 373 # fast path: _phasesets contains the interesting sets,
364 374 # might only need a union and post-filtering.
365 375 revsneedscopy = False
366 376 if len(phases) == 1:
367 377 [p] = phases
368 378 revs = self._phasesets[p]
369 379 revsneedscopy = True # Don't modify _phasesets
370 380 else:
371 381 # revs has the revisions in all *other* phases.
372 382 revs = set.union(*[self._phasesets[p] for p in phases])
373 383
374 384 def _addwdir(wdirsubset, wdirrevs):
375 385 if wdirrev in wdirsubset and repo[None].phase() in phases:
376 386 if revsneedscopy:
377 387 wdirrevs = wdirrevs.copy()
378 388 # The working dir would never be in the # cache, but it was in
379 389 # the subset being filtered for its phase (or filtered out,
380 390 # depending on publicphase), so add it to the output to be
381 391 # included (or filtered out).
382 392 wdirrevs.add(wdirrev)
383 393 return wdirrevs
384 394
385 395 if not publicphase:
386 396 if repo.changelog.filteredrevs:
387 397 revs = revs - repo.changelog.filteredrevs
388 398
389 399 if subset is None:
390 400 return smartset.baseset(revs)
391 401 else:
392 402 revs = _addwdir(subset, revs)
393 403 return subset & smartset.baseset(revs)
394 404 else:
395 405 if subset is None:
396 406 subset = smartset.fullreposet(repo)
397 407
398 408 revs = _addwdir(subset, revs)
399 409
400 410 if not revs:
401 411 return subset
402 412 return subset.filter(lambda r: r not in revs)
403 413
404 414 def copy(self):
405 415 # Shallow copy meant to ensure isolation in
406 416 # advance/retractboundary(), nothing more.
407 417 ph = self.__class__(None, None, _load=False)
408 ph.phaseroots = self.phaseroots[:]
418 ph.phaseroots = self.phaseroots.copy()
409 419 ph.dirty = self.dirty
410 420 ph.opener = self.opener
411 421 ph._loadedrevslen = self._loadedrevslen
412 422 ph._phasesets = self._phasesets
413 423 return ph
414 424
415 425 def replace(self, phcache):
416 426 """replace all values in 'self' with content of phcache"""
417 427 for a in (
418 428 b'phaseroots',
419 429 b'dirty',
420 430 b'opener',
421 431 b'_loadedrevslen',
422 432 b'_phasesets',
423 433 ):
424 434 setattr(self, a, getattr(phcache, a))
425 435
426 436 def _getphaserevsnative(self, repo):
427 437 repo = repo.unfiltered()
428 nativeroots = []
429 for phase in trackedphases:
430 nativeroots.append(
431 pycompat.maplist(repo.changelog.rev, self.phaseroots[phase])
432 )
433 revslen, phasesets = repo.changelog.computephases(nativeroots)
434 phasesets2 = [set() for phase in range(max(allphases) + 1)]
435 for phase, phaseset in zip(allphases, phasesets):
436 phasesets2[phase] = phaseset
437 return revslen, phasesets2
438 return repo.changelog.computephases(self.phaseroots)
438 439
439 440 def _computephaserevspure(self, repo):
440 441 repo = repo.unfiltered()
441 442 cl = repo.changelog
442 self._phasesets = [set() for phase in range(max(allphases) + 1)]
443 self._phasesets = {phase: set() for phase in allphases}
443 444 lowerroots = set()
444 445 for phase in reversed(trackedphases):
445 446 roots = pycompat.maplist(cl.rev, self.phaseroots[phase])
446 447 if roots:
447 448 ps = set(cl.descendants(roots))
448 449 for root in roots:
449 450 ps.add(root)
450 451 ps.difference_update(lowerroots)
451 452 lowerroots.update(ps)
452 453 self._phasesets[phase] = ps
453 454 self._loadedrevslen = len(cl)
454 455
455 456 def loadphaserevs(self, repo):
456 457 """ensure phase information is loaded in the object"""
457 458 if self._phasesets is None:
458 459 try:
459 460 res = self._getphaserevsnative(repo)
460 461 self._loadedrevslen, self._phasesets = res
461 462 except AttributeError:
462 463 self._computephaserevspure(repo)
463 464
464 465 def invalidate(self):
465 466 self._loadedrevslen = 0
466 467 self._phasesets = None
467 468
468 469 def phase(self, repo, rev):
469 470 # We need a repo argument here to be able to build _phasesets
470 471 # if necessary. The repository instance is not stored in
471 472 # phasecache to avoid reference cycles. The changelog instance
472 473 # is not stored because it is a filecache() property and can
473 474 # be replaced without us being notified.
474 475 if rev == nullrev:
475 476 return public
476 477 if rev < nullrev:
477 478 raise ValueError(_(b'cannot lookup negative revision'))
478 479 if rev >= self._loadedrevslen:
479 480 self.invalidate()
480 481 self.loadphaserevs(repo)
481 482 for phase in trackedphases:
482 483 if rev in self._phasesets[phase]:
483 484 return phase
484 485 return public
485 486
486 487 def write(self):
487 488 if not self.dirty:
488 489 return
489 490 f = self.opener(b'phaseroots', b'w', atomictemp=True, checkambig=True)
490 491 try:
491 492 self._write(f)
492 493 finally:
493 494 f.close()
494 495
495 496 def _write(self, fp):
496 for phase, roots in enumerate(self.phaseroots):
497 for phase, roots in pycompat.iteritems(self.phaseroots):
497 498 for h in sorted(roots):
498 499 fp.write(b'%i %s\n' % (phase, hex(h)))
499 500 self.dirty = False
500 501
501 502 def _updateroots(self, phase, newroots, tr):
502 503 self.phaseroots[phase] = newroots
503 504 self.invalidate()
504 505 self.dirty = True
505 506
506 507 tr.addfilegenerator(b'phase', (b'phaseroots',), self._write)
507 508 tr.hookargs[b'phases_moved'] = b'1'
508 509
509 510 def registernew(self, repo, tr, targetphase, nodes):
510 511 repo = repo.unfiltered()
511 512 self._retractboundary(repo, tr, targetphase, nodes)
512 513 if tr is not None and b'phases' in tr.changes:
513 514 phasetracking = tr.changes[b'phases']
514 515 torev = repo.changelog.rev
515 516 phase = self.phase
516 517 revs = [torev(node) for node in nodes]
517 518 revs.sort()
518 519 for rev in revs:
519 520 revphase = phase(repo, rev)
520 521 _trackphasechange(phasetracking, rev, None, revphase)
521 522 repo.invalidatevolatilesets()
522 523
523 524 def advanceboundary(self, repo, tr, targetphase, nodes, dryrun=None):
524 525 """Set all 'nodes' to phase 'targetphase'
525 526
526 527 Nodes with a phase lower than 'targetphase' are not affected.
527 528
528 529 If dryrun is True, no actions will be performed
529 530
530 531 Returns a set of revs whose phase is changed or should be changed
531 532 """
532 533 # Be careful to preserve shallow-copied values: do not update
533 534 # phaseroots values, replace them.
534 535 if tr is None:
535 536 phasetracking = None
536 537 else:
537 538 phasetracking = tr.changes.get(b'phases')
538 539
539 540 repo = repo.unfiltered()
540 541
541 542 changes = set() # set of revisions to be changed
542 543 delroots = [] # set of root deleted by this path
543 544 for phase in (phase for phase in allphases if phase > targetphase):
544 545 # filter nodes that are not in a compatible phase already
545 546 nodes = [
546 547 n for n in nodes if self.phase(repo, repo[n].rev()) >= phase
547 548 ]
548 549 if not nodes:
549 550 break # no roots to move anymore
550 551
551 552 olds = self.phaseroots[phase]
552 553
553 554 affected = repo.revs(b'%ln::%ln', olds, nodes)
554 555 changes.update(affected)
555 556 if dryrun:
556 557 continue
557 558 for r in affected:
558 559 _trackphasechange(
559 560 phasetracking, r, self.phase(repo, r), targetphase
560 561 )
561 562
562 563 roots = {
563 564 ctx.node()
564 565 for ctx in repo.set(b'roots((%ln::) - %ld)', olds, affected)
565 566 }
566 567 if olds != roots:
567 568 self._updateroots(phase, roots, tr)
568 569 # some roots may need to be declared for lower phases
569 570 delroots.extend(olds - roots)
570 571 if not dryrun:
571 572 # declare deleted root in the target phase
572 573 if targetphase != 0:
573 574 self._retractboundary(repo, tr, targetphase, delroots)
574 575 repo.invalidatevolatilesets()
575 576 return changes
576 577
577 578 def retractboundary(self, repo, tr, targetphase, nodes):
578 oldroots = self.phaseroots[: targetphase + 1]
579 oldroots = {
580 phase: revs
581 for phase, revs in pycompat.iteritems(self.phaseroots)
582 if phase <= targetphase
583 }
579 584 if tr is None:
580 585 phasetracking = None
581 586 else:
582 587 phasetracking = tr.changes.get(b'phases')
583 588 repo = repo.unfiltered()
584 589 if (
585 590 self._retractboundary(repo, tr, targetphase, nodes)
586 591 and phasetracking is not None
587 592 ):
588 593
589 594 # find the affected revisions
590 595 new = self.phaseroots[targetphase]
591 596 old = oldroots[targetphase]
592 597 affected = set(repo.revs(b'(%ln::) - (%ln::)', new, old))
593 598
594 599 # find the phase of the affected revision
595 600 for phase in pycompat.xrange(targetphase, -1, -1):
596 601 if phase:
597 roots = oldroots[phase]
602 roots = oldroots.get(phase, [])
598 603 revs = set(repo.revs(b'%ln::%ld', roots, affected))
599 604 affected -= revs
600 605 else: # public phase
601 606 revs = affected
602 607 for r in sorted(revs):
603 608 _trackphasechange(phasetracking, r, phase, targetphase)
604 609 repo.invalidatevolatilesets()
605 610
606 611 def _retractboundary(self, repo, tr, targetphase, nodes):
607 612 # Be careful to preserve shallow-copied values: do not update
608 613 # phaseroots values, replace them.
609 614 if targetphase in (archived, internal) and not supportinternal(repo):
610 615 name = phasenames[targetphase]
611 616 msg = b'this repository does not support the %s phase' % name
612 617 raise error.ProgrammingError(msg)
613 618
614 619 repo = repo.unfiltered()
615 620 torev = repo.changelog.rev
616 621 tonode = repo.changelog.node
617 622 currentroots = {torev(node) for node in self.phaseroots[targetphase]}
618 623 finalroots = oldroots = set(currentroots)
619 624 newroots = [torev(node) for node in nodes]
620 625 newroots = [
621 626 rev for rev in newroots if self.phase(repo, rev) < targetphase
622 627 ]
623 628
624 629 if newroots:
625 630 if nullrev in newroots:
626 631 raise error.Abort(_(b'cannot change null revision phase'))
627 632 currentroots.update(newroots)
628 633
629 634 # Only compute new roots for revs above the roots that are being
630 635 # retracted.
631 636 minnewroot = min(newroots)
632 637 aboveroots = [rev for rev in currentroots if rev >= minnewroot]
633 638 updatedroots = repo.revs(b'roots(%ld::)', aboveroots)
634 639
635 640 finalroots = {rev for rev in currentroots if rev < minnewroot}
636 641 finalroots.update(updatedroots)
637 642 if finalroots != oldroots:
638 643 self._updateroots(
639 644 targetphase, {tonode(rev) for rev in finalroots}, tr
640 645 )
641 646 return True
642 647 return False
643 648
644 649 def filterunknown(self, repo):
645 650 """remove unknown nodes from the phase boundary
646 651
647 652 Nothing is lost as unknown nodes only hold data for their descendants.
648 653 """
649 654 filtered = False
650 655 has_node = repo.changelog.index.has_node # to filter unknown nodes
651 for phase, nodes in enumerate(self.phaseroots):
656 for phase, nodes in pycompat.iteritems(self.phaseroots):
652 657 missing = sorted(node for node in nodes if not has_node(node))
653 658 if missing:
654 659 for mnode in missing:
655 660 repo.ui.debug(
656 661 b'removing unknown node %s from %i-phase boundary\n'
657 662 % (short(mnode), phase)
658 663 )
659 664 nodes.symmetric_difference_update(missing)
660 665 filtered = True
661 666 if filtered:
662 667 self.dirty = True
663 668 # filterunknown is called by repo.destroyed, we may have no changes in
664 669 # root but _phasesets contents is certainly invalid (or at least we
665 670 # have not proper way to check that). related to issue 3858.
666 671 #
667 672 # The other caller is __init__ that have no _phasesets initialized
668 673 # anyway. If this change we should consider adding a dedicated
669 674 # "destroyed" function to phasecache or a proper cache key mechanism
670 675 # (see branchmap one)
671 676 self.invalidate()
672 677
673 678
674 679 def advanceboundary(repo, tr, targetphase, nodes, dryrun=None):
675 680 """Add nodes to a phase changing other nodes phases if necessary.
676 681
677 682 This function move boundary *forward* this means that all nodes
678 683 are set in the target phase or kept in a *lower* phase.
679 684
680 685 Simplify boundary to contains phase roots only.
681 686
682 687 If dryrun is True, no actions will be performed
683 688
684 689 Returns a set of revs whose phase is changed or should be changed
685 690 """
686 691 phcache = repo._phasecache.copy()
687 692 changes = phcache.advanceboundary(
688 693 repo, tr, targetphase, nodes, dryrun=dryrun
689 694 )
690 695 if not dryrun:
691 696 repo._phasecache.replace(phcache)
692 697 return changes
693 698
694 699
695 700 def retractboundary(repo, tr, targetphase, nodes):
696 701 """Set nodes back to a phase changing other nodes phases if
697 702 necessary.
698 703
699 704 This function move boundary *backward* this means that all nodes
700 705 are set in the target phase or kept in a *higher* phase.
701 706
702 707 Simplify boundary to contains phase roots only."""
703 708 phcache = repo._phasecache.copy()
704 709 phcache.retractboundary(repo, tr, targetphase, nodes)
705 710 repo._phasecache.replace(phcache)
706 711
707 712
708 713 def registernew(repo, tr, targetphase, nodes):
709 714 """register a new revision and its phase
710 715
711 716 Code adding revisions to the repository should use this function to
712 717 set new changeset in their target phase (or higher).
713 718 """
714 719 phcache = repo._phasecache.copy()
715 720 phcache.registernew(repo, tr, targetphase, nodes)
716 721 repo._phasecache.replace(phcache)
717 722
718 723
719 724 def listphases(repo):
720 725 """List phases root for serialization over pushkey"""
721 726 # Use ordered dictionary so behavior is deterministic.
722 727 keys = util.sortdict()
723 728 value = b'%i' % draft
724 729 cl = repo.unfiltered().changelog
725 730 for root in repo._phasecache.phaseroots[draft]:
726 731 if repo._phasecache.phase(repo, cl.rev(root)) <= draft:
727 732 keys[hex(root)] = value
728 733
729 734 if repo.publishing():
730 735 # Add an extra data to let remote know we are a publishing
731 736 # repo. Publishing repo can't just pretend they are old repo.
732 737 # When pushing to a publishing repo, the client still need to
733 738 # push phase boundary
734 739 #
735 740 # Push do not only push changeset. It also push phase data.
736 741 # New phase data may apply to common changeset which won't be
737 742 # push (as they are common). Here is a very simple example:
738 743 #
739 744 # 1) repo A push changeset X as draft to repo B
740 745 # 2) repo B make changeset X public
741 746 # 3) repo B push to repo A. X is not pushed but the data that
742 747 # X as now public should
743 748 #
744 749 # The server can't handle it on it's own as it has no idea of
745 750 # client phase data.
746 751 keys[b'publishing'] = b'True'
747 752 return keys
748 753
749 754
750 755 def pushphase(repo, nhex, oldphasestr, newphasestr):
751 756 """List phases root for serialization over pushkey"""
752 757 repo = repo.unfiltered()
753 758 with repo.lock():
754 759 currentphase = repo[nhex].phase()
755 760 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
756 761 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
757 762 if currentphase == oldphase and newphase < oldphase:
758 763 with repo.transaction(b'pushkey-phase') as tr:
759 764 advanceboundary(repo, tr, newphase, [bin(nhex)])
760 765 return True
761 766 elif currentphase == newphase:
762 767 # raced, but got correct result
763 768 return True
764 769 else:
765 770 return False
766 771
767 772
768 773 def subsetphaseheads(repo, subset):
769 774 """Finds the phase heads for a subset of a history
770 775
771 776 Returns a list indexed by phase number where each item is a list of phase
772 777 head nodes.
773 778 """
774 779 cl = repo.changelog
775 780
776 781 headsbyphase = {i: [] for i in allphases}
777 782 # No need to keep track of secret phase; any heads in the subset that
778 783 # are not mentioned are implicitly secret.
779 784 for phase in allphases[:secret]:
780 785 revset = b"heads(%%ln & %s())" % phasenames[phase]
781 786 headsbyphase[phase] = [cl.node(r) for r in repo.revs(revset, subset)]
782 787 return headsbyphase
783 788
784 789
785 790 def updatephases(repo, trgetter, headsbyphase):
786 791 """Updates the repo with the given phase heads"""
787 792 # Now advance phase boundaries of all phases
788 793 #
789 794 # run the update (and fetch transaction) only if there are actually things
790 795 # to update. This avoid creating empty transaction during no-op operation.
791 796
792 797 for phase in allphases:
793 798 revset = b'%ln - _phase(%s)'
794 799 heads = [c.node() for c in repo.set(revset, headsbyphase[phase], phase)]
795 800 if heads:
796 801 advanceboundary(repo, trgetter(), phase, heads)
797 802
798 803
799 804 def analyzeremotephases(repo, subset, roots):
800 805 """Compute phases heads and root in a subset of node from root dict
801 806
802 807 * subset is heads of the subset
803 808 * roots is {<nodeid> => phase} mapping. key and value are string.
804 809
805 810 Accept unknown element input
806 811 """
807 812 repo = repo.unfiltered()
808 813 # build list from dictionary
809 814 draftroots = []
810 815 has_node = repo.changelog.index.has_node # to filter unknown nodes
811 816 for nhex, phase in pycompat.iteritems(roots):
812 817 if nhex == b'publishing': # ignore data related to publish option
813 818 continue
814 819 node = bin(nhex)
815 820 phase = int(phase)
816 821 if phase == public:
817 822 if node != nullid:
818 823 repo.ui.warn(
819 824 _(
820 825 b'ignoring inconsistent public root'
821 826 b' from remote: %s\n'
822 827 )
823 828 % nhex
824 829 )
825 830 elif phase == draft:
826 831 if has_node(node):
827 832 draftroots.append(node)
828 833 else:
829 834 repo.ui.warn(
830 835 _(b'ignoring unexpected root from remote: %i %s\n')
831 836 % (phase, nhex)
832 837 )
833 838 # compute heads
834 839 publicheads = newheads(repo, subset, draftroots)
835 840 return publicheads, draftroots
836 841
837 842
838 843 class remotephasessummary(object):
839 844 """summarize phase information on the remote side
840 845
841 846 :publishing: True is the remote is publishing
842 847 :publicheads: list of remote public phase heads (nodes)
843 848 :draftheads: list of remote draft phase heads (nodes)
844 849 :draftroots: list of remote draft phase root (nodes)
845 850 """
846 851
847 852 def __init__(self, repo, remotesubset, remoteroots):
848 853 unfi = repo.unfiltered()
849 854 self._allremoteroots = remoteroots
850 855
851 856 self.publishing = remoteroots.get(b'publishing', False)
852 857
853 858 ana = analyzeremotephases(repo, remotesubset, remoteroots)
854 859 self.publicheads, self.draftroots = ana
855 860 # Get the list of all "heads" revs draft on remote
856 861 dheads = unfi.set(b'heads(%ln::%ln)', self.draftroots, remotesubset)
857 862 self.draftheads = [c.node() for c in dheads]
858 863
859 864
860 865 def newheads(repo, heads, roots):
861 866 """compute new head of a subset minus another
862 867
863 868 * `heads`: define the first subset
864 869 * `roots`: define the second we subtract from the first"""
865 870 # prevent an import cycle
866 871 # phases > dagop > patch > copies > scmutil > obsolete > obsutil > phases
867 872 from . import dagop
868 873
869 874 repo = repo.unfiltered()
870 875 cl = repo.changelog
871 876 rev = cl.index.get_rev
872 877 if not roots:
873 878 return heads
874 879 if not heads or heads == [nullid]:
875 880 return []
876 881 # The logic operated on revisions, convert arguments early for convenience
877 882 new_heads = {rev(n) for n in heads if n != nullid}
878 883 roots = [rev(n) for n in roots]
879 884 # compute the area we need to remove
880 885 affected_zone = repo.revs(b"(%ld::%ld)", roots, new_heads)
881 886 # heads in the area are no longer heads
882 887 new_heads.difference_update(affected_zone)
883 888 # revisions in the area have children outside of it,
884 889 # They might be new heads
885 890 candidates = repo.revs(
886 891 b"parents(%ld + (%ld and merge())) and not null", roots, affected_zone
887 892 )
888 893 candidates -= affected_zone
889 894 if new_heads or candidates:
890 895 # remove candidate that are ancestors of other heads
891 896 new_heads.update(candidates)
892 897 prunestart = repo.revs(b"parents(%ld) and not null", new_heads)
893 898 pruned = dagop.reachableroots(repo, candidates, prunestart)
894 899 new_heads.difference_update(pruned)
895 900
896 901 return pycompat.maplist(cl.node, sorted(new_heads))
897 902
898 903
899 904 def newcommitphase(ui):
900 905 """helper to get the target phase of new commit
901 906
902 907 Handle all possible values for the phases.new-commit options.
903 908
904 909 """
905 910 v = ui.config(b'phases', b'new-commit')
906 911 try:
907 912 return phasenumber2[v]
908 913 except KeyError:
909 914 raise error.ConfigError(
910 915 _(b"phases.new-commit: not a valid phase name ('%s')") % v
911 916 )
912 917
913 918
914 919 def hassecret(repo):
915 920 """utility function that check if a repo have any secret changeset."""
916 921 return bool(repo._phasecache.phaseroots[secret])
917 922
918 923
919 924 def preparehookargs(node, old, new):
920 925 if old is None:
921 926 old = b''
922 927 else:
923 928 old = phasenames[old]
924 929 return {b'node': node, b'oldphase': old, b'phase': phasenames[new]}
@@ -1,157 +1,157 b''
1 1 # policy.py - module policy logic for Mercurial.
2 2 #
3 3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import os
11 11 import sys
12 12
13 13 from .pycompat import getattr
14 14
15 15 # Rules for how modules can be loaded. Values are:
16 16 #
17 17 # c - require C extensions
18 18 # rust+c - require Rust and C extensions
19 19 # rust+c-allow - allow Rust and C extensions with fallback to pure Python
20 20 # for each
21 21 # allow - allow pure Python implementation when C loading fails
22 22 # cffi - required cffi versions (implemented within pure module)
23 23 # cffi-allow - allow pure Python implementation if cffi version is missing
24 24 # py - only load pure Python modules
25 25 #
26 26 # By default, fall back to the pure modules so the in-place build can
27 27 # run without recompiling the C extensions. This will be overridden by
28 28 # __modulepolicy__ generated by setup.py.
29 29 policy = b'allow'
30 30 _packageprefs = {
31 31 # policy: (versioned package, pure package)
32 32 b'c': ('cext', None),
33 33 b'allow': ('cext', 'pure'),
34 34 b'cffi': ('cffi', None),
35 35 b'cffi-allow': ('cffi', 'pure'),
36 36 b'py': (None, 'pure'),
37 37 # For now, rust policies impact importrust only
38 38 b'rust+c': ('cext', None),
39 39 b'rust+c-allow': ('cext', 'pure'),
40 40 }
41 41
42 42 try:
43 43 from . import __modulepolicy__
44 44
45 45 policy = __modulepolicy__.modulepolicy
46 46 except ImportError:
47 47 pass
48 48
49 49 # PyPy doesn't load C extensions.
50 50 #
51 51 # The canonical way to do this is to test platform.python_implementation().
52 52 # But we don't import platform and don't bloat for it here.
53 53 if '__pypy__' in sys.builtin_module_names:
54 54 policy = b'cffi'
55 55
56 56 # Environment variable can always force settings.
57 57 if sys.version_info[0] >= 3:
58 58 if 'HGMODULEPOLICY' in os.environ:
59 59 policy = os.environ['HGMODULEPOLICY'].encode('utf-8')
60 60 else:
61 61 policy = os.environ.get('HGMODULEPOLICY', policy)
62 62
63 63
64 64 def _importfrom(pkgname, modname):
65 65 # from .<pkgname> import <modname> (where . is looked through this module)
66 66 fakelocals = {}
67 67 pkg = __import__(pkgname, globals(), fakelocals, [modname], level=1)
68 68 try:
69 69 fakelocals[modname] = mod = getattr(pkg, modname)
70 70 except AttributeError:
71 71 raise ImportError('cannot import name %s' % modname)
72 72 # force import; fakelocals[modname] may be replaced with the real module
73 73 getattr(mod, '__doc__', None)
74 74 return fakelocals[modname]
75 75
76 76
77 77 # keep in sync with "version" in C modules
78 78 _cextversions = {
79 79 ('cext', 'base85'): 1,
80 80 ('cext', 'bdiff'): 3,
81 81 ('cext', 'mpatch'): 1,
82 82 ('cext', 'osutil'): 4,
83 ('cext', 'parsers'): 16,
83 ('cext', 'parsers'): 17,
84 84 }
85 85
86 86 # map import request to other package or module
87 87 _modredirects = {
88 88 ('cext', 'charencode'): ('cext', 'parsers'),
89 89 ('cffi', 'base85'): ('pure', 'base85'),
90 90 ('cffi', 'charencode'): ('pure', 'charencode'),
91 91 ('cffi', 'parsers'): ('pure', 'parsers'),
92 92 }
93 93
94 94
95 95 def _checkmod(pkgname, modname, mod):
96 96 expected = _cextversions.get((pkgname, modname))
97 97 actual = getattr(mod, 'version', None)
98 98 if actual != expected:
99 99 raise ImportError(
100 100 'cannot import module %s.%s '
101 101 '(expected version: %d, actual: %r)'
102 102 % (pkgname, modname, expected, actual)
103 103 )
104 104
105 105
106 106 def importmod(modname):
107 107 """Import module according to policy and check API version"""
108 108 try:
109 109 verpkg, purepkg = _packageprefs[policy]
110 110 except KeyError:
111 111 raise ImportError('invalid HGMODULEPOLICY %r' % policy)
112 112 assert verpkg or purepkg
113 113 if verpkg:
114 114 pn, mn = _modredirects.get((verpkg, modname), (verpkg, modname))
115 115 try:
116 116 mod = _importfrom(pn, mn)
117 117 if pn == verpkg:
118 118 _checkmod(pn, mn, mod)
119 119 return mod
120 120 except ImportError:
121 121 if not purepkg:
122 122 raise
123 123 pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname))
124 124 return _importfrom(pn, mn)
125 125
126 126
127 127 def _isrustpermissive():
128 128 """Assuming the policy is a Rust one, tell if it's permissive."""
129 129 return policy.endswith(b'-allow')
130 130
131 131
132 132 def importrust(modname, member=None, default=None):
133 133 """Import Rust module according to policy and availability.
134 134
135 135 If policy isn't a Rust one, this returns `default`.
136 136
137 137 If either the module or its member is not available, this returns `default`
138 138 if policy is permissive and raises `ImportError` if not.
139 139 """
140 140 if not policy.startswith(b'rust'):
141 141 return default
142 142
143 143 try:
144 144 mod = _importfrom('rustext', modname)
145 145 except ImportError:
146 146 if _isrustpermissive():
147 147 return default
148 148 raise
149 149 if member is None:
150 150 return mod
151 151
152 152 try:
153 153 return getattr(mod, member)
154 154 except AttributeError:
155 155 if _isrustpermissive():
156 156 return default
157 157 raise ImportError("Cannot import name %s" % member)
@@ -1,223 +1,223 b''
1 1 revlog.parseindex must be able to parse the index file even if
2 2 an index entry is split between two 64k blocks. The ideal test
3 3 would be to create an index file with inline data where
4 4 64k < size < 64k + 64 (64k is the size of the read buffer, 64 is
5 5 the size of an index entry) and with an index entry starting right
6 6 before the 64k block boundary, and try to read it.
7 7 We approximate that by reducing the read buffer to 1 byte.
8 8
9 9 $ hg init a
10 10 $ cd a
11 11 $ echo abc > foo
12 12 $ hg add foo
13 13 $ hg commit -m 'add foo'
14 14 $ echo >> foo
15 15 $ hg commit -m 'change foo'
16 16 $ hg log -r 0:
17 17 changeset: 0:7c31755bf9b5
18 18 user: test
19 19 date: Thu Jan 01 00:00:00 1970 +0000
20 20 summary: add foo
21 21
22 22 changeset: 1:26333235a41c
23 23 tag: tip
24 24 user: test
25 25 date: Thu Jan 01 00:00:00 1970 +0000
26 26 summary: change foo
27 27
28 28 $ cat >> test.py << EOF
29 29 > from __future__ import print_function
30 30 > from mercurial import changelog, node, pycompat, vfs
31 31 >
32 32 > class singlebyteread(object):
33 33 > def __init__(self, real):
34 34 > self.real = real
35 35 >
36 36 > def read(self, size=-1):
37 37 > if size == 65536:
38 38 > size = 1
39 39 > return self.real.read(size)
40 40 >
41 41 > def __getattr__(self, key):
42 42 > return getattr(self.real, key)
43 43 >
44 44 > def __enter__(self):
45 45 > self.real.__enter__()
46 46 > return self
47 47 >
48 48 > def __exit__(self, *args, **kwargs):
49 49 > return self.real.__exit__(*args, **kwargs)
50 50 >
51 51 > def opener(*args):
52 52 > o = vfs.vfs(*args)
53 53 > def wrapper(*a, **kwargs):
54 54 > f = o(*a, **kwargs)
55 55 > return singlebyteread(f)
56 56 > wrapper.options = o.options
57 57 > return wrapper
58 58 >
59 59 > cl = changelog.changelog(opener(b'.hg/store'))
60 60 > print(len(cl), 'revisions:')
61 61 > for r in cl:
62 62 > print(pycompat.sysstr(node.short(cl.node(r))))
63 63 > EOF
64 64 $ "$PYTHON" test.py
65 65 2 revisions:
66 66 7c31755bf9b5
67 67 26333235a41c
68 68
69 69 $ cd ..
70 70
71 71 #if no-pure
72 72
73 73 Test SEGV caused by bad revision passed to reachableroots() (issue4775):
74 74
75 75 $ cd a
76 76
77 77 $ "$PYTHON" <<EOF
78 78 > from __future__ import print_function
79 79 > from mercurial import changelog, vfs
80 80 > cl = changelog.changelog(vfs.vfs(b'.hg/store'))
81 81 > print('good heads:')
82 82 > for head in [0, len(cl) - 1, -1]:
83 83 > print('%s: %r' % (head, cl.reachableroots(0, [head], [0])))
84 84 > print('bad heads:')
85 85 > for head in [len(cl), 10000, -2, -10000, None]:
86 86 > print('%s:' % head, end=' ')
87 87 > try:
88 88 > cl.reachableroots(0, [head], [0])
89 89 > print('uncaught buffer overflow?')
90 90 > except (IndexError, TypeError) as inst:
91 91 > print(inst)
92 92 > print('good roots:')
93 93 > for root in [0, len(cl) - 1, -1]:
94 94 > print('%s: %r' % (root, cl.reachableroots(root, [len(cl) - 1], [root])))
95 95 > print('out-of-range roots are ignored:')
96 96 > for root in [len(cl), 10000, -2, -10000]:
97 97 > print('%s: %r' % (root, cl.reachableroots(root, [len(cl) - 1], [root])))
98 98 > print('bad roots:')
99 99 > for root in [None]:
100 100 > print('%s:' % root, end=' ')
101 101 > try:
102 102 > cl.reachableroots(root, [len(cl) - 1], [root])
103 103 > print('uncaught error?')
104 104 > except TypeError as inst:
105 105 > print(inst)
106 106 > EOF
107 107 good heads:
108 108 0: [0]
109 109 1: [0]
110 110 -1: []
111 111 bad heads:
112 112 2: head out of range
113 113 10000: head out of range
114 114 -2: head out of range
115 115 -10000: head out of range
116 116 None: an integer is required( .got type NoneType.)? (re)
117 117 good roots:
118 118 0: [0]
119 119 1: [1]
120 120 -1: [-1]
121 121 out-of-range roots are ignored:
122 122 2: []
123 123 10000: []
124 124 -2: []
125 125 -10000: []
126 126 bad roots:
127 127 None: an integer is required( .got type NoneType.)? (re)
128 128
129 129 $ cd ..
130 130
131 131 Test corrupted p1/p2 fields that could cause SEGV at parsers.c:
132 132
133 133 $ mkdir invalidparent
134 134 $ cd invalidparent
135 135
136 136 $ hg clone --pull -q --config phases.publish=False ../a limit --config format.sparse-revlog=no
137 137 $ hg clone --pull -q --config phases.publish=False ../a neglimit --config format.sparse-revlog=no
138 138 $ hg clone --pull -q --config phases.publish=False ../a segv --config format.sparse-revlog=no
139 139 $ rm -R limit/.hg/cache neglimit/.hg/cache segv/.hg/cache
140 140
141 141 $ "$PYTHON" <<EOF
142 142 > data = open("limit/.hg/store/00changelog.i", "rb").read()
143 143 > poisons = [
144 144 > (b'limit', b'\0\0\0\x02'),
145 145 > (b'neglimit', b'\xff\xff\xff\xfe'),
146 146 > (b'segv', b'\0\x01\0\0'),
147 147 > ]
148 148 > for n, p in poisons:
149 149 > # corrupt p1 at rev0 and p2 at rev1
150 150 > d = data[:24] + p + data[28:127 + 28] + p + data[127 + 32:]
151 151 > open(n + b"/.hg/store/00changelog.i", "wb").write(d)
152 152 > EOF
153 153
154 154 $ hg -R limit debugrevlogindex -f1 -c
155 155 rev flag size link p1 p2 nodeid
156 156 0 0000 62 0 2 -1 7c31755bf9b5
157 157 1 0000 65 1 0 2 26333235a41c
158 158
159 159 $ hg -R limit debugdeltachain -c
160 160 rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio
161 161 0 1 1 -1 base 63 62 63 1.01613 63 0 0.00000
162 162 1 2 1 -1 base 66 65 66 1.01538 66 0 0.00000
163 163
164 164 $ hg -R neglimit debugrevlogindex -f1 -c
165 165 rev flag size link p1 p2 nodeid
166 166 0 0000 62 0 -2 -1 7c31755bf9b5
167 167 1 0000 65 1 0 -2 26333235a41c
168 168
169 169 $ hg -R segv debugrevlogindex -f1 -c
170 170 rev flag size link p1 p2 nodeid
171 171 0 0000 62 0 65536 -1 7c31755bf9b5
172 172 1 0000 65 1 0 65536 26333235a41c
173 173
174 174 $ hg -R segv debugdeltachain -c
175 175 rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio
176 176 0 1 1 -1 base 63 62 63 1.01613 63 0 0.00000
177 177 1 2 1 -1 base 66 65 66 1.01538 66 0 0.00000
178 178
179 179 $ cat <<EOF > test.py
180 180 > from __future__ import print_function
181 181 > import sys
182 182 > from mercurial import changelog, pycompat, vfs
183 183 > cl = changelog.changelog(vfs.vfs(pycompat.fsencode(sys.argv[1])))
184 184 > n0, n1 = cl.node(0), cl.node(1)
185 185 > ops = [
186 186 > ('reachableroots',
187 187 > lambda: cl.index.reachableroots2(0, [1], [0], False)),
188 > ('compute_phases_map_sets', lambda: cl.computephases([[0], []])),
188 > ('compute_phases_map_sets', lambda: cl.computephases({1: {cl.node(0)}})),
189 189 > ('index_headrevs', lambda: cl.headrevs()),
190 190 > ('find_gca_candidates', lambda: cl.commonancestorsheads(n0, n1)),
191 191 > ('find_deepest', lambda: cl.ancestor(n0, n1)),
192 192 > ]
193 193 > for l, f in ops:
194 194 > print(l + ':', end=' ')
195 195 > try:
196 196 > f()
197 197 > print('uncaught buffer overflow?')
198 198 > except ValueError as inst:
199 199 > print(inst)
200 200 > EOF
201 201
202 202 $ "$PYTHON" test.py limit/.hg/store
203 203 reachableroots: parent out of range
204 204 compute_phases_map_sets: parent out of range
205 205 index_headrevs: parent out of range
206 206 find_gca_candidates: parent out of range
207 207 find_deepest: parent out of range
208 208 $ "$PYTHON" test.py neglimit/.hg/store
209 209 reachableroots: parent out of range
210 210 compute_phases_map_sets: parent out of range
211 211 index_headrevs: parent out of range
212 212 find_gca_candidates: parent out of range
213 213 find_deepest: parent out of range
214 214 $ "$PYTHON" test.py segv/.hg/store
215 215 reachableroots: parent out of range
216 216 compute_phases_map_sets: parent out of range
217 217 index_headrevs: parent out of range
218 218 find_gca_candidates: parent out of range
219 219 find_deepest: parent out of range
220 220
221 221 $ cd ..
222 222
223 223 #endif
General Comments 0
You need to be logged in to leave comments. Login now