##// END OF EJS Templates
dirstate-item: introduce low level C function...
marmoute -
r48759:154e4dca default
parent child Browse files
Show More
@@ -1,1134 +1,1193 b''
1 1 /*
2 2 parsers.c - efficient content parsing
3 3
4 4 Copyright 2008 Olivia Mackall <olivia@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 const int dirstate_v1_from_p2 = -2;
33 33 static const int dirstate_v1_nonnormal = -1;
34 34 static const int ambiguous_time = -1;
35 35
36 36 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
37 37 {
38 38 Py_ssize_t expected_size;
39 39
40 40 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
41 41 return NULL;
42 42 }
43 43
44 44 return _dict_new_presized(expected_size);
45 45 }
46 46
47 47 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
48 48 PyObject *kwds)
49 49 {
50 50 /* We do all the initialization here and not a tp_init function because
51 51 * dirstate_item is immutable. */
52 52 dirstateItemObject *t;
53 53 int wc_tracked;
54 54 int p1_tracked;
55 55 int p2_tracked;
56 56 int merged;
57 57 int clean_p1;
58 58 int clean_p2;
59 59 int possibly_dirty;
60 60 PyObject *parentfiledata;
61 61 static char *keywords_name[] = {
62 62 "wc_tracked", "p1_tracked", "p2_tracked",
63 63 "merged", "clean_p1", "clean_p2",
64 64 "possibly_dirty", "parentfiledata", NULL,
65 65 };
66 66 wc_tracked = 0;
67 67 p1_tracked = 0;
68 68 p2_tracked = 0;
69 69 merged = 0;
70 70 clean_p1 = 0;
71 71 clean_p2 = 0;
72 72 possibly_dirty = 0;
73 73 parentfiledata = Py_None;
74 74 if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiiiiiiO", keywords_name,
75 75 &wc_tracked, &p1_tracked, &p2_tracked,
76 76 &merged, &clean_p1, &clean_p2,
77 77 &possibly_dirty, &parentfiledata
78 78
79 79 )) {
80 80 return NULL;
81 81 }
82 82 if (merged && (clean_p1 || clean_p2)) {
83 83 PyErr_SetString(PyExc_RuntimeError,
84 84 "`merged` argument incompatible with "
85 85 "`clean_p1`/`clean_p2`");
86 86 return NULL;
87 87 }
88 88 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
89 89 if (!t) {
90 90 return NULL;
91 91 }
92 92 t->state = 'r';
93 93 t->mode = 0;
94 94 t->size = dirstate_v1_nonnormal;
95 95 t->mtime = ambiguous_time;
96 96 if (!(p1_tracked || p2_tracked || wc_tracked)) {
97 97 /* Nothing special to do, file is untracked */
98 98 } else if (merged) {
99 99 t->state = 'm';
100 100 t->size = dirstate_v1_from_p2;
101 101 t->mtime = ambiguous_time;
102 102 } else if (!(p1_tracked || p2_tracked) && wc_tracked) {
103 103 t->state = 'a';
104 104 t->size = dirstate_v1_nonnormal;
105 105 t->mtime = ambiguous_time;
106 106 } else if ((p1_tracked || p2_tracked) && !wc_tracked) {
107 107 t->state = 'r';
108 108 t->size = 0;
109 109 t->mtime = 0;
110 110 } else if (clean_p2 && wc_tracked) {
111 111 t->state = 'n';
112 112 t->size = dirstate_v1_from_p2;
113 113 t->mtime = ambiguous_time;
114 114 } else if (!p1_tracked && p2_tracked && wc_tracked) {
115 115 t->state = 'n';
116 116 t->size = dirstate_v1_from_p2;
117 117 t->mtime = ambiguous_time;
118 118 } else if (possibly_dirty) {
119 119 t->state = 'n';
120 120 t->size = dirstate_v1_nonnormal;
121 121 t->mtime = ambiguous_time;
122 122 } else if (wc_tracked) {
123 123 /* this is a "normal" file */
124 124 if (parentfiledata == Py_None) {
125 125 PyErr_SetString(
126 126 PyExc_RuntimeError,
127 127 "failed to pass parentfiledata for a normal file");
128 128 return NULL;
129 129 }
130 130 if (!PyTuple_CheckExact(parentfiledata)) {
131 131 PyErr_SetString(
132 132 PyExc_TypeError,
133 133 "parentfiledata should be a Tuple or None");
134 134 return NULL;
135 135 }
136 136 t->state = 'n';
137 137 t->mode =
138 138 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
139 139 t->size =
140 140 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
141 141 t->mtime =
142 142 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
143 143 } else {
144 144 PyErr_SetString(PyExc_RuntimeError, "unreachable");
145 145 return NULL;
146 146 }
147 147 return (PyObject *)t;
148 148 }
149 149
150 150 static void dirstate_item_dealloc(PyObject *o)
151 151 {
152 152 PyObject_Del(o);
153 153 }
154 154
155 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
156 {
157 return (self->state == 'a' || self->state == 'm' || self->state == 'n');
158 }
159
160 static inline bool dirstate_item_c_added(dirstateItemObject *self)
161 {
162 return (self->state == 'a');
163 }
164
165 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
166 {
167 return (self->state == 'r');
168 }
169
170 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
171 {
172 return (self->state == 'm');
173 }
174
175 static inline bool dirstate_item_c_merged_removed(dirstateItemObject *self)
176 {
177 return (self->state == 'r' && self->size == dirstate_v1_nonnormal);
178 }
179
180 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
181 {
182 return (self->state == 'n' && self->size == dirstate_v1_from_p2);
183 }
184
185 static inline bool dirstate_item_c_from_p2_removed(dirstateItemObject *self)
186 {
187 return (self->state == 'r' && self->size == dirstate_v1_from_p2);
188 }
189
190 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
191 {
192 return self->state;
193 }
194
195 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
196 {
197 return self->mode;
198 }
199
200 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
201 {
202 return self->size;
203 }
204
205 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
206 {
207 return self->mtime;
208 }
209
155 210 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
156 211 {
157 return PyBytes_FromStringAndSize(&self->state, 1);
212 char state = dirstate_item_c_v1_state(self);
213 return PyBytes_FromStringAndSize(&state, 1);
158 214 };
159 215
160 216 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
161 217 {
162 return PyInt_FromLong(self->mode);
218 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
163 219 };
164 220
165 221 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
166 222 {
167 return PyInt_FromLong(self->size);
223 return PyInt_FromLong(dirstate_item_c_v1_size(self));
168 224 };
169 225
170 226 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
171 227 {
172 return PyInt_FromLong(self->mtime);
228 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
173 229 };
174 230
175 231 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
176 232 PyObject *value)
177 233 {
178 234 long now;
179 235 if (!pylong_to_long(value, &now)) {
180 236 return NULL;
181 237 }
182 if (self->state == 'n' && self->mtime == now) {
238 if (dirstate_item_c_v1_state(self) == 'n' &&
239 dirstate_item_c_v1_mtime(self) == now) {
183 240 Py_RETURN_TRUE;
184 241 } else {
185 242 Py_RETURN_FALSE;
186 243 }
187 244 };
188 245
189 246 /* This will never change since it's bound to V1
190 247 */
191 248 static inline dirstateItemObject *
192 249 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
193 250 {
194 251 dirstateItemObject *t =
195 252 PyObject_New(dirstateItemObject, &dirstateItemType);
196 253 if (!t) {
197 254 return NULL;
198 255 }
199 256 t->state = state;
200 257 t->mode = mode;
201 258 t->size = size;
202 259 t->mtime = mtime;
203 260 return t;
204 261 }
205 262
206 263 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
207 264 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
208 265 PyObject *args)
209 266 {
210 267 /* We do all the initialization here and not a tp_init function because
211 268 * dirstate_item is immutable. */
212 269 char state;
213 270 int size, mode, mtime;
214 271 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
215 272 return NULL;
216 273 }
217 274 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
218 275 };
219 276
220 277 /* constructor to help legacy API to build a new "added" item
221 278
222 279 Should eventually be removed */
223 280 static PyObject *dirstate_item_new_added(PyTypeObject *subtype)
224 281 {
225 282 dirstateItemObject *t;
226 283 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
227 284 if (!t) {
228 285 return NULL;
229 286 }
230 287 t->state = 'a';
231 288 t->mode = 0;
232 289 t->size = dirstate_v1_nonnormal;
233 290 t->mtime = ambiguous_time;
234 291 return (PyObject *)t;
235 292 };
236 293
237 294 /* constructor to help legacy API to build a new "merged" item
238 295
239 296 Should eventually be removed */
240 297 static PyObject *dirstate_item_new_merged(PyTypeObject *subtype)
241 298 {
242 299 dirstateItemObject *t;
243 300 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
244 301 if (!t) {
245 302 return NULL;
246 303 }
247 304 t->state = 'm';
248 305 t->mode = 0;
249 306 t->size = dirstate_v1_from_p2;
250 307 t->mtime = ambiguous_time;
251 308 return (PyObject *)t;
252 309 };
253 310
254 311 /* constructor to help legacy API to build a new "from_p2" item
255 312
256 313 Should eventually be removed */
257 314 static PyObject *dirstate_item_new_from_p2(PyTypeObject *subtype)
258 315 {
259 316 /* We do all the initialization here and not a tp_init function because
260 317 * dirstate_item is immutable. */
261 318 dirstateItemObject *t;
262 319 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
263 320 if (!t) {
264 321 return NULL;
265 322 }
266 323 t->state = 'n';
267 324 t->mode = 0;
268 325 t->size = dirstate_v1_from_p2;
269 326 t->mtime = ambiguous_time;
270 327 return (PyObject *)t;
271 328 };
272 329
273 330 /* constructor to help legacy API to build a new "possibly" item
274 331
275 332 Should eventually be removed */
276 333 static PyObject *dirstate_item_new_possibly_dirty(PyTypeObject *subtype)
277 334 {
278 335 /* We do all the initialization here and not a tp_init function because
279 336 * dirstate_item is immutable. */
280 337 dirstateItemObject *t;
281 338 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
282 339 if (!t) {
283 340 return NULL;
284 341 }
285 342 t->state = 'n';
286 343 t->mode = 0;
287 344 t->size = dirstate_v1_nonnormal;
288 345 t->mtime = ambiguous_time;
289 346 return (PyObject *)t;
290 347 };
291 348
292 349 /* constructor to help legacy API to build a new "normal" item
293 350
294 351 Should eventually be removed */
295 352 static PyObject *dirstate_item_new_normal(PyTypeObject *subtype, PyObject *args)
296 353 {
297 354 /* We do all the initialization here and not a tp_init function because
298 355 * dirstate_item is immutable. */
299 356 dirstateItemObject *t;
300 357 int size, mode, mtime;
301 358 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
302 359 return NULL;
303 360 }
304 361
305 362 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
306 363 if (!t) {
307 364 return NULL;
308 365 }
309 366 t->state = 'n';
310 367 t->mode = mode;
311 368 t->size = size;
312 369 t->mtime = mtime;
313 370 return (PyObject *)t;
314 371 };
315 372
316 373 /* This means the next status call will have to actually check its content
317 374 to make sure it is correct. */
318 375 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
319 376 {
320 377 self->mtime = ambiguous_time;
321 378 Py_RETURN_NONE;
322 379 }
323 380
324 381 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
325 382 {
326 383 if (self->state == 'm') {
327 384 self->size = dirstate_v1_nonnormal;
328 385 } else if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
329 386 self->size = dirstate_v1_from_p2;
330 387 } else {
331 388 self->size = 0;
332 389 }
333 390 self->state = 'r';
334 391 self->mode = 0;
335 392 self->mtime = 0;
336 393 Py_RETURN_NONE;
337 394 }
338 395
339 396 static PyMethodDef dirstate_item_methods[] = {
340 397 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
341 398 "return a \"state\" suitable for v1 serialization"},
342 399 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
343 400 "return a \"mode\" suitable for v1 serialization"},
344 401 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
345 402 "return a \"size\" suitable for v1 serialization"},
346 403 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
347 404 "return a \"mtime\" suitable for v1 serialization"},
348 405 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
349 406 "True if the stored mtime would be ambiguous with the current time"},
350 407 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
351 408 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
352 409 {"new_added", (PyCFunction)dirstate_item_new_added,
353 410 METH_NOARGS | METH_CLASS,
354 411 "constructor to help legacy API to build a new \"added\" item"},
355 412 {"new_merged", (PyCFunction)dirstate_item_new_merged,
356 413 METH_NOARGS | METH_CLASS,
357 414 "constructor to help legacy API to build a new \"merged\" item"},
358 415 {"new_from_p2", (PyCFunction)dirstate_item_new_from_p2,
359 416 METH_NOARGS | METH_CLASS,
360 417 "constructor to help legacy API to build a new \"from_p2\" item"},
361 418 {"new_possibly_dirty", (PyCFunction)dirstate_item_new_possibly_dirty,
362 419 METH_NOARGS | METH_CLASS,
363 420 "constructor to help legacy API to build a new \"possibly_dirty\" item"},
364 421 {"new_normal", (PyCFunction)dirstate_item_new_normal,
365 422 METH_VARARGS | METH_CLASS,
366 423 "constructor to help legacy API to build a new \"normal\" item"},
367 424 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
368 425 METH_NOARGS, "mark a file as \"possibly dirty\""},
369 426 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
370 427 "mark a file as \"untracked\""},
371 428 {NULL} /* Sentinel */
372 429 };
373 430
374 431 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
375 432 {
376 return PyInt_FromLong(self->mode);
433 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
377 434 };
378 435
379 436 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
380 437 {
381 return PyInt_FromLong(self->size);
438 return PyInt_FromLong(dirstate_item_c_v1_size(self));
382 439 };
383 440
384 441 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
385 442 {
386 return PyInt_FromLong(self->mtime);
443 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
387 444 };
388 445
389 446 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
390 447 {
391 return PyBytes_FromStringAndSize(&self->state, 1);
448 char state = dirstate_item_c_v1_state(self);
449 return PyBytes_FromStringAndSize(&state, 1);
392 450 };
393 451
394 452 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
395 453 {
396 if (self->state == 'a' || self->state == 'm' || self->state == 'n') {
454 if (dirstate_item_c_tracked(self)) {
397 455 Py_RETURN_TRUE;
398 456 } else {
399 457 Py_RETURN_FALSE;
400 458 }
401 459 };
402 460
403 461 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
404 462 {
405 if (self->state == 'a') {
463 if (dirstate_item_c_added(self)) {
406 464 Py_RETURN_TRUE;
407 465 } else {
408 466 Py_RETURN_FALSE;
409 467 }
410 468 };
411 469
412 470 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
413 471 {
414 if (self->state == 'm') {
472 if (dirstate_item_c_merged(self)) {
415 473 Py_RETURN_TRUE;
416 474 } else {
417 475 Py_RETURN_FALSE;
418 476 }
419 477 };
420 478
421 479 static PyObject *dirstate_item_get_merged_removed(dirstateItemObject *self)
422 480 {
423 if (self->state == 'r' && self->size == dirstate_v1_nonnormal) {
481 if (dirstate_item_c_merged_removed(self)) {
424 482 Py_RETURN_TRUE;
425 483 } else {
426 484 Py_RETURN_FALSE;
427 485 }
428 486 };
429 487
430 488 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
431 489 {
432 if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
490 if (dirstate_item_c_from_p2(self)) {
433 491 Py_RETURN_TRUE;
434 492 } else {
435 493 Py_RETURN_FALSE;
436 494 }
437 495 };
438 496
439 497 static PyObject *dirstate_item_get_from_p2_removed(dirstateItemObject *self)
440 498 {
441 if (self->state == 'r' && self->size == dirstate_v1_from_p2) {
499 if (dirstate_item_c_from_p2_removed(self)) {
442 500 Py_RETURN_TRUE;
443 501 } else {
444 502 Py_RETURN_FALSE;
445 503 }
446 504 };
447 505
448 506 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
449 507 {
450 if (self->state == 'r') {
508 if (dirstate_item_c_removed(self)) {
451 509 Py_RETURN_TRUE;
452 510 } else {
453 511 Py_RETURN_FALSE;
454 512 }
455 513 };
456 514
457 515 static PyObject *dm_nonnormal(dirstateItemObject *self)
458 516 {
459 if (self->state != 'n' || self->mtime == ambiguous_time) {
517 if ((dirstate_item_c_v1_state(self) != 'n') ||
518 (dirstate_item_c_v1_mtime(self) == ambiguous_time)) {
460 519 Py_RETURN_TRUE;
461 520 } else {
462 521 Py_RETURN_FALSE;
463 522 }
464 523 };
465 524 static PyObject *dm_otherparent(dirstateItemObject *self)
466 525 {
467 if (self->size == dirstate_v1_from_p2) {
526 if (dirstate_item_c_v1_mtime(self) == dirstate_v1_from_p2) {
468 527 Py_RETURN_TRUE;
469 528 } else {
470 529 Py_RETURN_FALSE;
471 530 }
472 531 };
473 532
474 533 static PyGetSetDef dirstate_item_getset[] = {
475 534 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
476 535 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
477 536 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
478 537 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
479 538 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
480 539 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
481 540 {"merged_removed", (getter)dirstate_item_get_merged_removed, NULL,
482 541 "merged_removed", NULL},
483 542 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
484 543 {"from_p2_removed", (getter)dirstate_item_get_from_p2_removed, NULL,
485 544 "from_p2_removed", NULL},
486 545 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
487 546 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
488 547 {"dm_nonnormal", (getter)dm_nonnormal, NULL, "dm_nonnormal", NULL},
489 548 {"dm_otherparent", (getter)dm_otherparent, NULL, "dm_otherparent", NULL},
490 549 {NULL} /* Sentinel */
491 550 };
492 551
493 552 PyTypeObject dirstateItemType = {
494 553 PyVarObject_HEAD_INIT(NULL, 0) /* header */
495 554 "dirstate_tuple", /* tp_name */
496 555 sizeof(dirstateItemObject), /* tp_basicsize */
497 556 0, /* tp_itemsize */
498 557 (destructor)dirstate_item_dealloc, /* tp_dealloc */
499 558 0, /* tp_print */
500 559 0, /* tp_getattr */
501 560 0, /* tp_setattr */
502 561 0, /* tp_compare */
503 562 0, /* tp_repr */
504 563 0, /* tp_as_number */
505 564 0, /* tp_as_sequence */
506 565 0, /* tp_as_mapping */
507 566 0, /* tp_hash */
508 567 0, /* tp_call */
509 568 0, /* tp_str */
510 569 0, /* tp_getattro */
511 570 0, /* tp_setattro */
512 571 0, /* tp_as_buffer */
513 572 Py_TPFLAGS_DEFAULT, /* tp_flags */
514 573 "dirstate tuple", /* tp_doc */
515 574 0, /* tp_traverse */
516 575 0, /* tp_clear */
517 576 0, /* tp_richcompare */
518 577 0, /* tp_weaklistoffset */
519 578 0, /* tp_iter */
520 579 0, /* tp_iternext */
521 580 dirstate_item_methods, /* tp_methods */
522 581 0, /* tp_members */
523 582 dirstate_item_getset, /* tp_getset */
524 583 0, /* tp_base */
525 584 0, /* tp_dict */
526 585 0, /* tp_descr_get */
527 586 0, /* tp_descr_set */
528 587 0, /* tp_dictoffset */
529 588 0, /* tp_init */
530 589 0, /* tp_alloc */
531 590 dirstate_item_new, /* tp_new */
532 591 };
533 592
534 593 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
535 594 {
536 595 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
537 596 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
538 597 char state, *cur, *str, *cpos;
539 598 int mode, size, mtime;
540 599 unsigned int flen, pos = 40;
541 600 Py_ssize_t len = 40;
542 601 Py_ssize_t readlen;
543 602
544 603 if (!PyArg_ParseTuple(
545 604 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
546 605 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
547 606 goto quit;
548 607 }
549 608
550 609 len = readlen;
551 610
552 611 /* read parents */
553 612 if (len < 40) {
554 613 PyErr_SetString(PyExc_ValueError,
555 614 "too little data for parents");
556 615 goto quit;
557 616 }
558 617
559 618 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
560 619 str + 20, (Py_ssize_t)20);
561 620 if (!parents) {
562 621 goto quit;
563 622 }
564 623
565 624 /* read filenames */
566 625 while (pos >= 40 && pos < len) {
567 626 if (pos + 17 > len) {
568 627 PyErr_SetString(PyExc_ValueError,
569 628 "overflow in dirstate");
570 629 goto quit;
571 630 }
572 631 cur = str + pos;
573 632 /* unpack header */
574 633 state = *cur;
575 634 mode = getbe32(cur + 1);
576 635 size = getbe32(cur + 5);
577 636 mtime = getbe32(cur + 9);
578 637 flen = getbe32(cur + 13);
579 638 pos += 17;
580 639 cur += 17;
581 640 if (flen > len - pos) {
582 641 PyErr_SetString(PyExc_ValueError,
583 642 "overflow in dirstate");
584 643 goto quit;
585 644 }
586 645
587 646 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
588 647 size, mtime);
589 648 cpos = memchr(cur, 0, flen);
590 649 if (cpos) {
591 650 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
592 651 cname = PyBytes_FromStringAndSize(
593 652 cpos + 1, flen - (cpos - cur) - 1);
594 653 if (!fname || !cname ||
595 654 PyDict_SetItem(cmap, fname, cname) == -1 ||
596 655 PyDict_SetItem(dmap, fname, entry) == -1) {
597 656 goto quit;
598 657 }
599 658 Py_DECREF(cname);
600 659 } else {
601 660 fname = PyBytes_FromStringAndSize(cur, flen);
602 661 if (!fname ||
603 662 PyDict_SetItem(dmap, fname, entry) == -1) {
604 663 goto quit;
605 664 }
606 665 }
607 666 Py_DECREF(fname);
608 667 Py_DECREF(entry);
609 668 fname = cname = entry = NULL;
610 669 pos += flen;
611 670 }
612 671
613 672 ret = parents;
614 673 Py_INCREF(ret);
615 674 quit:
616 675 Py_XDECREF(fname);
617 676 Py_XDECREF(cname);
618 677 Py_XDECREF(entry);
619 678 Py_XDECREF(parents);
620 679 return ret;
621 680 }
622 681
623 682 /*
624 683 * Build a set of non-normal and other parent entries from the dirstate dmap
625 684 */
626 685 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
627 686 {
628 687 PyObject *dmap, *fname, *v;
629 688 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
630 689 Py_ssize_t pos;
631 690
632 691 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
633 692 &dmap)) {
634 693 goto bail;
635 694 }
636 695
637 696 nonnset = PySet_New(NULL);
638 697 if (nonnset == NULL) {
639 698 goto bail;
640 699 }
641 700
642 701 otherpset = PySet_New(NULL);
643 702 if (otherpset == NULL) {
644 703 goto bail;
645 704 }
646 705
647 706 pos = 0;
648 707 while (PyDict_Next(dmap, &pos, &fname, &v)) {
649 708 dirstateItemObject *t;
650 709 if (!dirstate_tuple_check(v)) {
651 710 PyErr_SetString(PyExc_TypeError,
652 711 "expected a dirstate tuple");
653 712 goto bail;
654 713 }
655 714 t = (dirstateItemObject *)v;
656 715
657 716 if (t->state == 'n' && t->size == -2) {
658 717 if (PySet_Add(otherpset, fname) == -1) {
659 718 goto bail;
660 719 }
661 720 }
662 721
663 722 if (t->state == 'n' && t->mtime != -1) {
664 723 continue;
665 724 }
666 725 if (PySet_Add(nonnset, fname) == -1) {
667 726 goto bail;
668 727 }
669 728 }
670 729
671 730 result = Py_BuildValue("(OO)", nonnset, otherpset);
672 731 if (result == NULL) {
673 732 goto bail;
674 733 }
675 734 Py_DECREF(nonnset);
676 735 Py_DECREF(otherpset);
677 736 return result;
678 737 bail:
679 738 Py_XDECREF(nonnset);
680 739 Py_XDECREF(otherpset);
681 740 Py_XDECREF(result);
682 741 return NULL;
683 742 }
684 743
685 744 /*
686 745 * Efficiently pack a dirstate object into its on-disk format.
687 746 */
688 747 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
689 748 {
690 749 PyObject *packobj = NULL;
691 750 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
692 751 Py_ssize_t nbytes, pos, l;
693 752 PyObject *k, *v = NULL, *pn;
694 753 char *p, *s;
695 754 int now;
696 755
697 756 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
698 757 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
699 758 &now)) {
700 759 return NULL;
701 760 }
702 761
703 762 if (PyTuple_Size(pl) != 2) {
704 763 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
705 764 return NULL;
706 765 }
707 766
708 767 /* Figure out how much we need to allocate. */
709 768 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
710 769 PyObject *c;
711 770 if (!PyBytes_Check(k)) {
712 771 PyErr_SetString(PyExc_TypeError, "expected string key");
713 772 goto bail;
714 773 }
715 774 nbytes += PyBytes_GET_SIZE(k) + 17;
716 775 c = PyDict_GetItem(copymap, k);
717 776 if (c) {
718 777 if (!PyBytes_Check(c)) {
719 778 PyErr_SetString(PyExc_TypeError,
720 779 "expected string key");
721 780 goto bail;
722 781 }
723 782 nbytes += PyBytes_GET_SIZE(c) + 1;
724 783 }
725 784 }
726 785
727 786 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
728 787 if (packobj == NULL) {
729 788 goto bail;
730 789 }
731 790
732 791 p = PyBytes_AS_STRING(packobj);
733 792
734 793 pn = PyTuple_GET_ITEM(pl, 0);
735 794 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
736 795 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
737 796 goto bail;
738 797 }
739 798 memcpy(p, s, l);
740 799 p += 20;
741 800 pn = PyTuple_GET_ITEM(pl, 1);
742 801 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
743 802 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
744 803 goto bail;
745 804 }
746 805 memcpy(p, s, l);
747 806 p += 20;
748 807
749 808 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
750 809 dirstateItemObject *tuple;
751 810 char state;
752 811 int mode, size, mtime;
753 812 Py_ssize_t len, l;
754 813 PyObject *o;
755 814 char *t;
756 815
757 816 if (!dirstate_tuple_check(v)) {
758 817 PyErr_SetString(PyExc_TypeError,
759 818 "expected a dirstate tuple");
760 819 goto bail;
761 820 }
762 821 tuple = (dirstateItemObject *)v;
763 822
764 823 state = tuple->state;
765 824 mode = tuple->mode;
766 825 size = tuple->size;
767 826 mtime = tuple->mtime;
768 827 if (state == 'n' && mtime == now) {
769 828 /* See pure/parsers.py:pack_dirstate for why we do
770 829 * this. */
771 830 mtime = -1;
772 831 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
773 832 state, mode, size, mtime);
774 833 if (!mtime_unset) {
775 834 goto bail;
776 835 }
777 836 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
778 837 goto bail;
779 838 }
780 839 Py_DECREF(mtime_unset);
781 840 mtime_unset = NULL;
782 841 }
783 842 *p++ = state;
784 843 putbe32((uint32_t)mode, p);
785 844 putbe32((uint32_t)size, p + 4);
786 845 putbe32((uint32_t)mtime, p + 8);
787 846 t = p + 12;
788 847 p += 16;
789 848 len = PyBytes_GET_SIZE(k);
790 849 memcpy(p, PyBytes_AS_STRING(k), len);
791 850 p += len;
792 851 o = PyDict_GetItem(copymap, k);
793 852 if (o) {
794 853 *p++ = '\0';
795 854 l = PyBytes_GET_SIZE(o);
796 855 memcpy(p, PyBytes_AS_STRING(o), l);
797 856 p += l;
798 857 len += l + 1;
799 858 }
800 859 putbe32((uint32_t)len, t);
801 860 }
802 861
803 862 pos = p - PyBytes_AS_STRING(packobj);
804 863 if (pos != nbytes) {
805 864 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
806 865 (long)pos, (long)nbytes);
807 866 goto bail;
808 867 }
809 868
810 869 return packobj;
811 870 bail:
812 871 Py_XDECREF(mtime_unset);
813 872 Py_XDECREF(packobj);
814 873 Py_XDECREF(v);
815 874 return NULL;
816 875 }
817 876
818 877 #define BUMPED_FIX 1
819 878 #define USING_SHA_256 2
820 879 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
821 880
822 881 static PyObject *readshas(const char *source, unsigned char num,
823 882 Py_ssize_t hashwidth)
824 883 {
825 884 int i;
826 885 PyObject *list = PyTuple_New(num);
827 886 if (list == NULL) {
828 887 return NULL;
829 888 }
830 889 for (i = 0; i < num; i++) {
831 890 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
832 891 if (hash == NULL) {
833 892 Py_DECREF(list);
834 893 return NULL;
835 894 }
836 895 PyTuple_SET_ITEM(list, i, hash);
837 896 source += hashwidth;
838 897 }
839 898 return list;
840 899 }
841 900
842 901 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
843 902 uint32_t *msize)
844 903 {
845 904 const char *data = databegin;
846 905 const char *meta;
847 906
848 907 double mtime;
849 908 int16_t tz;
850 909 uint16_t flags;
851 910 unsigned char nsuccs, nparents, nmetadata;
852 911 Py_ssize_t hashwidth = 20;
853 912
854 913 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
855 914 PyObject *metadata = NULL, *ret = NULL;
856 915 int i;
857 916
858 917 if (data + FM1_HEADER_SIZE > dataend) {
859 918 goto overflow;
860 919 }
861 920
862 921 *msize = getbe32(data);
863 922 data += 4;
864 923 mtime = getbefloat64(data);
865 924 data += 8;
866 925 tz = getbeint16(data);
867 926 data += 2;
868 927 flags = getbeuint16(data);
869 928 data += 2;
870 929
871 930 if (flags & USING_SHA_256) {
872 931 hashwidth = 32;
873 932 }
874 933
875 934 nsuccs = (unsigned char)(*data++);
876 935 nparents = (unsigned char)(*data++);
877 936 nmetadata = (unsigned char)(*data++);
878 937
879 938 if (databegin + *msize > dataend) {
880 939 goto overflow;
881 940 }
882 941 dataend = databegin + *msize; /* narrow down to marker size */
883 942
884 943 if (data + hashwidth > dataend) {
885 944 goto overflow;
886 945 }
887 946 prec = PyBytes_FromStringAndSize(data, hashwidth);
888 947 data += hashwidth;
889 948 if (prec == NULL) {
890 949 goto bail;
891 950 }
892 951
893 952 if (data + nsuccs * hashwidth > dataend) {
894 953 goto overflow;
895 954 }
896 955 succs = readshas(data, nsuccs, hashwidth);
897 956 if (succs == NULL) {
898 957 goto bail;
899 958 }
900 959 data += nsuccs * hashwidth;
901 960
902 961 if (nparents == 1 || nparents == 2) {
903 962 if (data + nparents * hashwidth > dataend) {
904 963 goto overflow;
905 964 }
906 965 parents = readshas(data, nparents, hashwidth);
907 966 if (parents == NULL) {
908 967 goto bail;
909 968 }
910 969 data += nparents * hashwidth;
911 970 } else {
912 971 parents = Py_None;
913 972 Py_INCREF(parents);
914 973 }
915 974
916 975 if (data + 2 * nmetadata > dataend) {
917 976 goto overflow;
918 977 }
919 978 meta = data + (2 * nmetadata);
920 979 metadata = PyTuple_New(nmetadata);
921 980 if (metadata == NULL) {
922 981 goto bail;
923 982 }
924 983 for (i = 0; i < nmetadata; i++) {
925 984 PyObject *tmp, *left = NULL, *right = NULL;
926 985 Py_ssize_t leftsize = (unsigned char)(*data++);
927 986 Py_ssize_t rightsize = (unsigned char)(*data++);
928 987 if (meta + leftsize + rightsize > dataend) {
929 988 goto overflow;
930 989 }
931 990 left = PyBytes_FromStringAndSize(meta, leftsize);
932 991 meta += leftsize;
933 992 right = PyBytes_FromStringAndSize(meta, rightsize);
934 993 meta += rightsize;
935 994 tmp = PyTuple_New(2);
936 995 if (!left || !right || !tmp) {
937 996 Py_XDECREF(left);
938 997 Py_XDECREF(right);
939 998 Py_XDECREF(tmp);
940 999 goto bail;
941 1000 }
942 1001 PyTuple_SET_ITEM(tmp, 0, left);
943 1002 PyTuple_SET_ITEM(tmp, 1, right);
944 1003 PyTuple_SET_ITEM(metadata, i, tmp);
945 1004 }
946 1005 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
947 1006 (int)tz * 60, parents);
948 1007 goto bail; /* return successfully */
949 1008
950 1009 overflow:
951 1010 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
952 1011 bail:
953 1012 Py_XDECREF(prec);
954 1013 Py_XDECREF(succs);
955 1014 Py_XDECREF(metadata);
956 1015 Py_XDECREF(parents);
957 1016 return ret;
958 1017 }
959 1018
960 1019 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
961 1020 {
962 1021 const char *data, *dataend;
963 1022 Py_ssize_t datalen, offset, stop;
964 1023 PyObject *markers = NULL;
965 1024
966 1025 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
967 1026 &offset, &stop)) {
968 1027 return NULL;
969 1028 }
970 1029 if (offset < 0) {
971 1030 PyErr_SetString(PyExc_ValueError,
972 1031 "invalid negative offset in fm1readmarkers");
973 1032 return NULL;
974 1033 }
975 1034 if (stop > datalen) {
976 1035 PyErr_SetString(
977 1036 PyExc_ValueError,
978 1037 "stop longer than data length in fm1readmarkers");
979 1038 return NULL;
980 1039 }
981 1040 dataend = data + datalen;
982 1041 data += offset;
983 1042 markers = PyList_New(0);
984 1043 if (!markers) {
985 1044 return NULL;
986 1045 }
987 1046 while (offset < stop) {
988 1047 uint32_t msize;
989 1048 int error;
990 1049 PyObject *record = fm1readmarker(data, dataend, &msize);
991 1050 if (!record) {
992 1051 goto bail;
993 1052 }
994 1053 error = PyList_Append(markers, record);
995 1054 Py_DECREF(record);
996 1055 if (error) {
997 1056 goto bail;
998 1057 }
999 1058 data += msize;
1000 1059 offset += msize;
1001 1060 }
1002 1061 return markers;
1003 1062 bail:
1004 1063 Py_DECREF(markers);
1005 1064 return NULL;
1006 1065 }
1007 1066
1008 1067 static char parsers_doc[] = "Efficient content parsing.";
1009 1068
1010 1069 PyObject *encodedir(PyObject *self, PyObject *args);
1011 1070 PyObject *pathencode(PyObject *self, PyObject *args);
1012 1071 PyObject *lowerencode(PyObject *self, PyObject *args);
1013 1072 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1014 1073
1015 1074 static PyMethodDef methods[] = {
1016 1075 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1017 1076 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
1018 1077 "create a set containing non-normal and other parent entries of given "
1019 1078 "dirstate\n"},
1020 1079 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1021 1080 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1022 1081 "parse a revlog index\n"},
1023 1082 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1024 1083 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1025 1084 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1026 1085 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1027 1086 "construct a dict with an expected size\n"},
1028 1087 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1029 1088 "make file foldmap\n"},
1030 1089 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1031 1090 "escape a UTF-8 byte string to JSON (fast path)\n"},
1032 1091 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1033 1092 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1034 1093 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1035 1094 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1036 1095 "parse v1 obsolete markers\n"},
1037 1096 {NULL, NULL}};
1038 1097
1039 1098 void dirs_module_init(PyObject *mod);
1040 1099 void manifest_module_init(PyObject *mod);
1041 1100 void revlog_module_init(PyObject *mod);
1042 1101
1043 1102 static const int version = 20;
1044 1103
1045 1104 static void module_init(PyObject *mod)
1046 1105 {
1047 1106 PyObject *capsule = NULL;
1048 1107 PyModule_AddIntConstant(mod, "version", version);
1049 1108
1050 1109 /* This module constant has two purposes. First, it lets us unit test
1051 1110 * the ImportError raised without hard-coding any error text. This
1052 1111 * means we can change the text in the future without breaking tests,
1053 1112 * even across changesets without a recompile. Second, its presence
1054 1113 * can be used to determine whether the version-checking logic is
1055 1114 * present, which also helps in testing across changesets without a
1056 1115 * recompile. Note that this means the pure-Python version of parsers
1057 1116 * should not have this module constant. */
1058 1117 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1059 1118
1060 1119 dirs_module_init(mod);
1061 1120 manifest_module_init(mod);
1062 1121 revlog_module_init(mod);
1063 1122
1064 1123 capsule = PyCapsule_New(
1065 1124 dirstate_item_from_v1_data,
1066 1125 "mercurial.cext.parsers.make_dirstate_item_CAPI", NULL);
1067 1126 if (capsule != NULL)
1068 1127 PyModule_AddObject(mod, "make_dirstate_item_CAPI", capsule);
1069 1128
1070 1129 if (PyType_Ready(&dirstateItemType) < 0) {
1071 1130 return;
1072 1131 }
1073 1132 Py_INCREF(&dirstateItemType);
1074 1133 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1075 1134 }
1076 1135
1077 1136 static int check_python_version(void)
1078 1137 {
1079 1138 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1080 1139 long hexversion;
1081 1140 if (!sys) {
1082 1141 return -1;
1083 1142 }
1084 1143 ver = PyObject_GetAttrString(sys, "hexversion");
1085 1144 Py_DECREF(sys);
1086 1145 if (!ver) {
1087 1146 return -1;
1088 1147 }
1089 1148 hexversion = PyInt_AsLong(ver);
1090 1149 Py_DECREF(ver);
1091 1150 /* sys.hexversion is a 32-bit number by default, so the -1 case
1092 1151 * should only occur in unusual circumstances (e.g. if sys.hexversion
1093 1152 * is manually set to an invalid value). */
1094 1153 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1095 1154 PyErr_Format(PyExc_ImportError,
1096 1155 "%s: The Mercurial extension "
1097 1156 "modules were compiled with Python " PY_VERSION
1098 1157 ", but "
1099 1158 "Mercurial is currently using Python with "
1100 1159 "sys.hexversion=%ld: "
1101 1160 "Python %s\n at: %s",
1102 1161 versionerrortext, hexversion, Py_GetVersion(),
1103 1162 Py_GetProgramFullPath());
1104 1163 return -1;
1105 1164 }
1106 1165 return 0;
1107 1166 }
1108 1167
1109 1168 #ifdef IS_PY3K
1110 1169 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1111 1170 parsers_doc, -1, methods};
1112 1171
1113 1172 PyMODINIT_FUNC PyInit_parsers(void)
1114 1173 {
1115 1174 PyObject *mod;
1116 1175
1117 1176 if (check_python_version() == -1)
1118 1177 return NULL;
1119 1178 mod = PyModule_Create(&parsers_module);
1120 1179 module_init(mod);
1121 1180 return mod;
1122 1181 }
1123 1182 #else
1124 1183 PyMODINIT_FUNC initparsers(void)
1125 1184 {
1126 1185 PyObject *mod;
1127 1186
1128 1187 if (check_python_version() == -1) {
1129 1188 return;
1130 1189 }
1131 1190 mod = Py_InitModule3("parsers", methods, parsers_doc);
1132 1191 module_init(mod);
1133 1192 }
1134 1193 #endif
General Comments 0
You need to be logged in to leave comments. Login now