##// END OF EJS Templates
merge with stable
Matt Mackall -
r16755:d0b9ebba merge default
parent child Browse files
Show More
@@ -1,578 +1,581 b''
1 1 /*
2 2 osutil.c - native operating system services
3 3
4 4 Copyright 2007 Matt Mackall 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 _ATFILE_SOURCE
11 11 #include <Python.h>
12 12 #include <fcntl.h>
13 13 #include <stdio.h>
14 14 #include <string.h>
15 15 #include <errno.h>
16 16
17 17 #ifdef _WIN32
18 18 #include <windows.h>
19 19 #include <io.h>
20 20 #else
21 21 #include <dirent.h>
22 22 #include <sys/stat.h>
23 23 #include <sys/types.h>
24 24 #include <unistd.h>
25 25 #endif
26 26
27 27 #include "util.h"
28 28
29 29 /* some platforms lack the PATH_MAX definition (eg. GNU/Hurd) */
30 30 #ifndef PATH_MAX
31 31 #define PATH_MAX 4096
32 32 #endif
33 33
34 34 #ifdef _WIN32
35 35 /*
36 36 stat struct compatible with hg expectations
37 37 Mercurial only uses st_mode, st_size and st_mtime
38 38 the rest is kept to minimize changes between implementations
39 39 */
40 40 struct hg_stat {
41 41 int st_dev;
42 42 int st_mode;
43 43 int st_nlink;
44 44 __int64 st_size;
45 45 int st_mtime;
46 46 int st_ctime;
47 47 };
48 48 struct listdir_stat {
49 49 PyObject_HEAD
50 50 struct hg_stat st;
51 51 };
52 52 #else
53 53 struct listdir_stat {
54 54 PyObject_HEAD
55 55 struct stat st;
56 56 };
57 57 #endif
58 58
59 59 #define listdir_slot(name) \
60 60 static PyObject *listdir_stat_##name(PyObject *self, void *x) \
61 61 { \
62 62 return PyInt_FromLong(((struct listdir_stat *)self)->st.name); \
63 63 }
64 64
65 65 listdir_slot(st_dev)
66 66 listdir_slot(st_mode)
67 67 listdir_slot(st_nlink)
68 68 #ifdef _WIN32
69 69 static PyObject *listdir_stat_st_size(PyObject *self, void *x)
70 70 {
71 71 return PyLong_FromLongLong(
72 72 (PY_LONG_LONG)((struct listdir_stat *)self)->st.st_size);
73 73 }
74 74 #else
75 75 listdir_slot(st_size)
76 76 #endif
77 77 listdir_slot(st_mtime)
78 78 listdir_slot(st_ctime)
79 79
80 80 static struct PyGetSetDef listdir_stat_getsets[] = {
81 81 {"st_dev", listdir_stat_st_dev, 0, 0, 0},
82 82 {"st_mode", listdir_stat_st_mode, 0, 0, 0},
83 83 {"st_nlink", listdir_stat_st_nlink, 0, 0, 0},
84 84 {"st_size", listdir_stat_st_size, 0, 0, 0},
85 85 {"st_mtime", listdir_stat_st_mtime, 0, 0, 0},
86 86 {"st_ctime", listdir_stat_st_ctime, 0, 0, 0},
87 87 {0, 0, 0, 0, 0}
88 88 };
89 89
90 90 static PyObject *listdir_stat_new(PyTypeObject *t, PyObject *a, PyObject *k)
91 91 {
92 92 return t->tp_alloc(t, 0);
93 93 }
94 94
95 95 static void listdir_stat_dealloc(PyObject *o)
96 96 {
97 97 o->ob_type->tp_free(o);
98 98 }
99 99
100 100 static PyTypeObject listdir_stat_type = {
101 101 PyVarObject_HEAD_INIT(NULL, 0)
102 102 "osutil.stat", /*tp_name*/
103 103 sizeof(struct listdir_stat), /*tp_basicsize*/
104 104 0, /*tp_itemsize*/
105 105 (destructor)listdir_stat_dealloc, /*tp_dealloc*/
106 106 0, /*tp_print*/
107 107 0, /*tp_getattr*/
108 108 0, /*tp_setattr*/
109 109 0, /*tp_compare*/
110 110 0, /*tp_repr*/
111 111 0, /*tp_as_number*/
112 112 0, /*tp_as_sequence*/
113 113 0, /*tp_as_mapping*/
114 114 0, /*tp_hash */
115 115 0, /*tp_call*/
116 116 0, /*tp_str*/
117 117 0, /*tp_getattro*/
118 118 0, /*tp_setattro*/
119 119 0, /*tp_as_buffer*/
120 120 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
121 121 "stat objects", /* tp_doc */
122 122 0, /* tp_traverse */
123 123 0, /* tp_clear */
124 124 0, /* tp_richcompare */
125 125 0, /* tp_weaklistoffset */
126 126 0, /* tp_iter */
127 127 0, /* tp_iternext */
128 128 0, /* tp_methods */
129 129 0, /* tp_members */
130 130 listdir_stat_getsets, /* tp_getset */
131 131 0, /* tp_base */
132 132 0, /* tp_dict */
133 133 0, /* tp_descr_get */
134 134 0, /* tp_descr_set */
135 135 0, /* tp_dictoffset */
136 136 0, /* tp_init */
137 137 0, /* tp_alloc */
138 138 listdir_stat_new, /* tp_new */
139 139 };
140 140
141 141 #ifdef _WIN32
142 142
143 143 static int to_python_time(const FILETIME *tm)
144 144 {
145 145 /* number of seconds between epoch and January 1 1601 */
146 146 const __int64 a0 = (__int64)134774L * (__int64)24L * (__int64)3600L;
147 147 /* conversion factor from 100ns to 1s */
148 148 const __int64 a1 = 10000000;
149 149 /* explicit (int) cast to suspend compiler warnings */
150 150 return (int)((((__int64)tm->dwHighDateTime << 32)
151 151 + tm->dwLowDateTime) / a1 - a0);
152 152 }
153 153
154 154 static PyObject *make_item(const WIN32_FIND_DATAA *fd, int wantstat)
155 155 {
156 156 PyObject *py_st;
157 157 struct hg_stat *stp;
158 158
159 159 int kind = (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
160 160 ? _S_IFDIR : _S_IFREG;
161 161
162 162 if (!wantstat)
163 163 return Py_BuildValue("si", fd->cFileName, kind);
164 164
165 165 py_st = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
166 166 if (!py_st)
167 167 return NULL;
168 168
169 169 stp = &((struct listdir_stat *)py_st)->st;
170 170 /*
171 171 use kind as st_mode
172 172 rwx bits on Win32 are meaningless
173 173 and Hg does not use them anyway
174 174 */
175 175 stp->st_mode = kind;
176 176 stp->st_mtime = to_python_time(&fd->ftLastWriteTime);
177 177 stp->st_ctime = to_python_time(&fd->ftCreationTime);
178 178 if (kind == _S_IFREG)
179 179 stp->st_size = ((__int64)fd->nFileSizeHigh << 32)
180 180 + fd->nFileSizeLow;
181 181 return Py_BuildValue("siN", fd->cFileName,
182 182 kind, py_st);
183 183 }
184 184
185 185 static PyObject *_listdir(char *path, int plen, int wantstat, char *skip)
186 186 {
187 187 PyObject *rval = NULL; /* initialize - return value */
188 188 PyObject *list;
189 189 HANDLE fh;
190 190 WIN32_FIND_DATAA fd;
191 191 char *pattern;
192 192
193 193 /* build the path + \* pattern string */
194 194 pattern = malloc(plen + 3); /* path + \* + \0 */
195 195 if (!pattern) {
196 196 PyErr_NoMemory();
197 197 goto error_nomem;
198 198 }
199 199 strcpy(pattern, path);
200 200
201 201 if (plen > 0) {
202 202 char c = path[plen-1];
203 203 if (c != ':' && c != '/' && c != '\\')
204 204 pattern[plen++] = '\\';
205 205 }
206 206 strcpy(pattern + plen, "*");
207 207
208 208 fh = FindFirstFileA(pattern, &fd);
209 209 if (fh == INVALID_HANDLE_VALUE) {
210 210 PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
211 211 goto error_file;
212 212 }
213 213
214 214 list = PyList_New(0);
215 215 if (!list)
216 216 goto error_list;
217 217
218 218 do {
219 219 PyObject *item;
220 220
221 221 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
222 222 if (!strcmp(fd.cFileName, ".")
223 223 || !strcmp(fd.cFileName, ".."))
224 224 continue;
225 225
226 226 if (skip && !strcmp(fd.cFileName, skip)) {
227 227 rval = PyList_New(0);
228 228 goto error;
229 229 }
230 230 }
231 231
232 232 item = make_item(&fd, wantstat);
233 233 if (!item)
234 234 goto error;
235 235
236 236 if (PyList_Append(list, item)) {
237 237 Py_XDECREF(item);
238 238 goto error;
239 239 }
240 240
241 241 Py_XDECREF(item);
242 242 } while (FindNextFileA(fh, &fd));
243 243
244 244 if (GetLastError() != ERROR_NO_MORE_FILES) {
245 245 PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
246 246 goto error;
247 247 }
248 248
249 249 rval = list;
250 250 Py_XINCREF(rval);
251 251 error:
252 252 Py_XDECREF(list);
253 253 error_list:
254 254 FindClose(fh);
255 255 error_file:
256 256 free(pattern);
257 257 error_nomem:
258 258 return rval;
259 259 }
260 260
261 261 #else
262 262
263 263 int entkind(struct dirent *ent)
264 264 {
265 265 #ifdef DT_REG
266 266 switch (ent->d_type) {
267 267 case DT_REG: return S_IFREG;
268 268 case DT_DIR: return S_IFDIR;
269 269 case DT_LNK: return S_IFLNK;
270 270 case DT_BLK: return S_IFBLK;
271 271 case DT_CHR: return S_IFCHR;
272 272 case DT_FIFO: return S_IFIFO;
273 273 case DT_SOCK: return S_IFSOCK;
274 274 }
275 275 #endif
276 276 return -1;
277 277 }
278 278
279 279 static PyObject *_listdir(char *path, int pathlen, int keepstat, char *skip)
280 280 {
281 281 PyObject *list, *elem, *stat, *ret = NULL;
282 282 char fullpath[PATH_MAX + 10];
283 283 int kind, err;
284 284 struct stat st;
285 285 struct dirent *ent;
286 286 DIR *dir;
287 287 #ifdef AT_SYMLINK_NOFOLLOW
288 288 int dfd = -1;
289 289 #endif
290 290
291 291 if (pathlen >= PATH_MAX) {
292 292 errno = ENAMETOOLONG;
293 293 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
294 294 goto error_value;
295 295 }
296 296 strncpy(fullpath, path, PATH_MAX);
297 297 fullpath[pathlen] = '/';
298 298
299 299 #ifdef AT_SYMLINK_NOFOLLOW
300 300 dfd = open(path, O_RDONLY);
301 301 if (dfd == -1) {
302 302 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
303 303 goto error_value;
304 304 }
305 305 dir = fdopendir(dfd);
306 306 #else
307 307 dir = opendir(path);
308 308 #endif
309 309 if (!dir) {
310 310 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
311 311 goto error_dir;
312 312 }
313 313
314 314 list = PyList_New(0);
315 315 if (!list)
316 316 goto error_list;
317 317
318 318 while ((ent = readdir(dir))) {
319 319 if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
320 320 continue;
321 321
322 322 kind = entkind(ent);
323 323 if (kind == -1 || keepstat) {
324 324 #ifdef AT_SYMLINK_NOFOLLOW
325 325 err = fstatat(dfd, ent->d_name, &st,
326 326 AT_SYMLINK_NOFOLLOW);
327 327 #else
328 328 strncpy(fullpath + pathlen + 1, ent->d_name,
329 329 PATH_MAX - pathlen);
330 330 fullpath[PATH_MAX] = 0;
331 331 err = lstat(fullpath, &st);
332 332 #endif
333 333 if (err == -1) {
334 /* race with file deletion? */
335 if (errno == ENOENT)
336 continue;
334 337 strncpy(fullpath + pathlen + 1, ent->d_name,
335 338 PATH_MAX - pathlen);
336 339 fullpath[PATH_MAX] = 0;
337 340 PyErr_SetFromErrnoWithFilename(PyExc_OSError,
338 341 fullpath);
339 342 goto error;
340 343 }
341 344 kind = st.st_mode & S_IFMT;
342 345 }
343 346
344 347 /* quit early? */
345 348 if (skip && kind == S_IFDIR && !strcmp(ent->d_name, skip)) {
346 349 ret = PyList_New(0);
347 350 goto error;
348 351 }
349 352
350 353 if (keepstat) {
351 354 stat = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
352 355 if (!stat)
353 356 goto error;
354 357 memcpy(&((struct listdir_stat *)stat)->st, &st, sizeof(st));
355 358 elem = Py_BuildValue("siN", ent->d_name, kind, stat);
356 359 } else
357 360 elem = Py_BuildValue("si", ent->d_name, kind);
358 361 if (!elem)
359 362 goto error;
360 363
361 364 PyList_Append(list, elem);
362 365 Py_DECREF(elem);
363 366 }
364 367
365 368 ret = list;
366 369 Py_INCREF(ret);
367 370
368 371 error:
369 372 Py_DECREF(list);
370 373 error_list:
371 374 closedir(dir);
372 375 error_dir:
373 376 #ifdef AT_SYMLINK_NOFOLLOW
374 377 close(dfd);
375 378 #endif
376 379 error_value:
377 380 return ret;
378 381 }
379 382
380 383 #endif /* ndef _WIN32 */
381 384
382 385 static PyObject *listdir(PyObject *self, PyObject *args, PyObject *kwargs)
383 386 {
384 387 PyObject *statobj = NULL; /* initialize - optional arg */
385 388 PyObject *skipobj = NULL; /* initialize - optional arg */
386 389 char *path, *skip = NULL;
387 390 int wantstat, plen;
388 391
389 392 static char *kwlist[] = {"path", "stat", "skip", NULL};
390 393
391 394 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|OO:listdir",
392 395 kwlist, &path, &plen, &statobj, &skipobj))
393 396 return NULL;
394 397
395 398 wantstat = statobj && PyObject_IsTrue(statobj);
396 399
397 400 if (skipobj && skipobj != Py_None) {
398 401 skip = PyBytes_AsString(skipobj);
399 402 if (!skip)
400 403 return NULL;
401 404 }
402 405
403 406 return _listdir(path, plen, wantstat, skip);
404 407 }
405 408
406 409 #ifdef _WIN32
407 410 static PyObject *posixfile(PyObject *self, PyObject *args, PyObject *kwds)
408 411 {
409 412 static char *kwlist[] = {"name", "mode", "buffering", NULL};
410 413 PyObject *file_obj = NULL;
411 414 char *name = NULL;
412 415 char *mode = "rb";
413 416 DWORD access = 0;
414 417 DWORD creation;
415 418 HANDLE handle;
416 419 int fd, flags = 0;
417 420 int bufsize = -1;
418 421 char m0, m1, m2;
419 422 char fpmode[4];
420 423 int fppos = 0;
421 424 int plus;
422 425 FILE *fp;
423 426
424 427 if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|si:posixfile", kwlist,
425 428 Py_FileSystemDefaultEncoding,
426 429 &name, &mode, &bufsize))
427 430 return NULL;
428 431
429 432 m0 = mode[0];
430 433 m1 = m0 ? mode[1] : '\0';
431 434 m2 = m1 ? mode[2] : '\0';
432 435 plus = m1 == '+' || m2 == '+';
433 436
434 437 fpmode[fppos++] = m0;
435 438 if (m1 == 'b' || m2 == 'b') {
436 439 flags = _O_BINARY;
437 440 fpmode[fppos++] = 'b';
438 441 }
439 442 else
440 443 flags = _O_TEXT;
441 444 if (m0 == 'r' && !plus) {
442 445 flags |= _O_RDONLY;
443 446 access = GENERIC_READ;
444 447 } else {
445 448 /*
446 449 work around http://support.microsoft.com/kb/899149 and
447 450 set _O_RDWR for 'w' and 'a', even if mode has no '+'
448 451 */
449 452 flags |= _O_RDWR;
450 453 access = GENERIC_READ | GENERIC_WRITE;
451 454 fpmode[fppos++] = '+';
452 455 }
453 456 fpmode[fppos++] = '\0';
454 457
455 458 switch (m0) {
456 459 case 'r':
457 460 creation = OPEN_EXISTING;
458 461 break;
459 462 case 'w':
460 463 creation = CREATE_ALWAYS;
461 464 break;
462 465 case 'a':
463 466 creation = OPEN_ALWAYS;
464 467 flags |= _O_APPEND;
465 468 break;
466 469 default:
467 470 PyErr_Format(PyExc_ValueError,
468 471 "mode string must begin with one of 'r', 'w', "
469 472 "or 'a', not '%c'", m0);
470 473 goto bail;
471 474 }
472 475
473 476 handle = CreateFile(name, access,
474 477 FILE_SHARE_READ | FILE_SHARE_WRITE |
475 478 FILE_SHARE_DELETE,
476 479 NULL,
477 480 creation,
478 481 FILE_ATTRIBUTE_NORMAL,
479 482 0);
480 483
481 484 if (handle == INVALID_HANDLE_VALUE) {
482 485 PyErr_SetFromWindowsErrWithFilename(GetLastError(), name);
483 486 goto bail;
484 487 }
485 488
486 489 fd = _open_osfhandle((intptr_t)handle, flags);
487 490
488 491 if (fd == -1) {
489 492 CloseHandle(handle);
490 493 PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
491 494 goto bail;
492 495 }
493 496 #ifndef IS_PY3K
494 497 fp = _fdopen(fd, fpmode);
495 498 if (fp == NULL) {
496 499 _close(fd);
497 500 PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
498 501 goto bail;
499 502 }
500 503
501 504 file_obj = PyFile_FromFile(fp, name, mode, fclose);
502 505 if (file_obj == NULL) {
503 506 fclose(fp);
504 507 goto bail;
505 508 }
506 509
507 510 PyFile_SetBufSize(file_obj, bufsize);
508 511 #else
509 512 file_obj = PyFile_FromFd(fd, name, mode, bufsize, NULL, NULL, NULL, 1);
510 513 if (file_obj == NULL)
511 514 goto bail;
512 515 #endif
513 516 bail:
514 517 PyMem_Free(name);
515 518 return file_obj;
516 519 }
517 520 #endif
518 521
519 522 #ifdef __APPLE__
520 523 #include <ApplicationServices/ApplicationServices.h>
521 524
522 525 static PyObject *isgui(PyObject *self)
523 526 {
524 527 CFDictionaryRef dict = CGSessionCopyCurrentDictionary();
525 528
526 529 if (dict != NULL) {
527 530 CFRelease(dict);
528 531 Py_RETURN_TRUE;
529 532 } else {
530 533 Py_RETURN_FALSE;
531 534 }
532 535 }
533 536 #endif
534 537
535 538 static char osutil_doc[] = "Native operating system services.";
536 539
537 540 static PyMethodDef methods[] = {
538 541 {"listdir", (PyCFunction)listdir, METH_VARARGS | METH_KEYWORDS,
539 542 "list a directory\n"},
540 543 #ifdef _WIN32
541 544 {"posixfile", (PyCFunction)posixfile, METH_VARARGS | METH_KEYWORDS,
542 545 "Open a file with POSIX-like semantics.\n"
543 546 "On error, this function may raise either a WindowsError or an IOError."},
544 547 #endif
545 548 #ifdef __APPLE__
546 549 {
547 550 "isgui", (PyCFunction)isgui, METH_NOARGS,
548 551 "Is a CoreGraphics session available?"
549 552 },
550 553 #endif
551 554 {NULL, NULL}
552 555 };
553 556
554 557 #ifdef IS_PY3K
555 558 static struct PyModuleDef osutil_module = {
556 559 PyModuleDef_HEAD_INIT,
557 560 "osutil",
558 561 osutil_doc,
559 562 -1,
560 563 methods
561 564 };
562 565
563 566 PyMODINIT_FUNC PyInit_osutil(void)
564 567 {
565 568 if (PyType_Ready(&listdir_stat_type) < 0)
566 569 return NULL;
567 570
568 571 return PyModule_Create(&osutil_module);
569 572 }
570 573 #else
571 574 PyMODINIT_FUNC initosutil(void)
572 575 {
573 576 if (PyType_Ready(&listdir_stat_type) == -1)
574 577 return;
575 578
576 579 Py_InitModule3("osutil", methods, osutil_doc);
577 580 }
578 581 #endif
@@ -1,1522 +1,1526 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.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 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 11 import bookmarks as bookmarksmod
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15
16 16 def _revancestors(repo, revs, followfirst):
17 17 """Like revlog.ancestors(), but supports followfirst."""
18 18 cut = followfirst and 1 or None
19 19 cl = repo.changelog
20 20 visit = list(revs)
21 21 seen = set([node.nullrev])
22 22 while visit:
23 23 for parent in cl.parentrevs(visit.pop(0))[:cut]:
24 24 if parent not in seen:
25 25 visit.append(parent)
26 26 seen.add(parent)
27 27 yield parent
28 28
29 29 def _revdescendants(repo, revs, followfirst):
30 30 """Like revlog.descendants() but supports followfirst."""
31 31 cut = followfirst and 1 or None
32 32 cl = repo.changelog
33 33 first = min(revs)
34 34 nullrev = node.nullrev
35 35 if first == nullrev:
36 36 # Are there nodes with a null first parent and a non-null
37 37 # second one? Maybe. Do we care? Probably not.
38 38 for i in cl:
39 39 yield i
40 40 return
41 41
42 42 seen = set(revs)
43 43 for i in xrange(first + 1, len(cl)):
44 44 for x in cl.parentrevs(i)[:cut]:
45 45 if x != nullrev and x in seen:
46 46 seen.add(i)
47 47 yield i
48 48 break
49 49
50 50 elements = {
51 51 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
52 52 "~": (18, None, ("ancestor", 18)),
53 53 "^": (18, None, ("parent", 18), ("parentpost", 18)),
54 54 "-": (5, ("negate", 19), ("minus", 5)),
55 55 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
56 56 ("dagrangepost", 17)),
57 57 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
58 58 ("dagrangepost", 17)),
59 59 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
60 60 "not": (10, ("not", 10)),
61 61 "!": (10, ("not", 10)),
62 62 "and": (5, None, ("and", 5)),
63 63 "&": (5, None, ("and", 5)),
64 64 "or": (4, None, ("or", 4)),
65 65 "|": (4, None, ("or", 4)),
66 66 "+": (4, None, ("or", 4)),
67 67 ",": (2, None, ("list", 2)),
68 68 ")": (0, None, None),
69 69 "symbol": (0, ("symbol",), None),
70 70 "string": (0, ("string",), None),
71 71 "end": (0, None, None),
72 72 }
73 73
74 74 keywords = set(['and', 'or', 'not'])
75 75
76 76 def tokenize(program):
77 77 pos, l = 0, len(program)
78 78 while pos < l:
79 79 c = program[pos]
80 80 if c.isspace(): # skip inter-token whitespace
81 81 pass
82 82 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
83 83 yield ('::', None, pos)
84 84 pos += 1 # skip ahead
85 85 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
86 86 yield ('..', None, pos)
87 87 pos += 1 # skip ahead
88 88 elif c in "():,-|&+!~^": # handle simple operators
89 89 yield (c, None, pos)
90 90 elif (c in '"\'' or c == 'r' and
91 91 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
92 92 if c == 'r':
93 93 pos += 1
94 94 c = program[pos]
95 95 decode = lambda x: x
96 96 else:
97 97 decode = lambda x: x.decode('string-escape')
98 98 pos += 1
99 99 s = pos
100 100 while pos < l: # find closing quote
101 101 d = program[pos]
102 102 if d == '\\': # skip over escaped characters
103 103 pos += 2
104 104 continue
105 105 if d == c:
106 106 yield ('string', decode(program[s:pos]), s)
107 107 break
108 108 pos += 1
109 109 else:
110 110 raise error.ParseError(_("unterminated string"), s)
111 111 # gather up a symbol/keyword
112 112 elif c.isalnum() or c in '._' or ord(c) > 127:
113 113 s = pos
114 114 pos += 1
115 115 while pos < l: # find end of symbol
116 116 d = program[pos]
117 117 if not (d.isalnum() or d in "._/" or ord(d) > 127):
118 118 break
119 119 if d == '.' and program[pos - 1] == '.': # special case for ..
120 120 pos -= 1
121 121 break
122 122 pos += 1
123 123 sym = program[s:pos]
124 124 if sym in keywords: # operator keywords
125 125 yield (sym, None, s)
126 126 else:
127 127 yield ('symbol', sym, s)
128 128 pos -= 1
129 129 else:
130 130 raise error.ParseError(_("syntax error"), pos)
131 131 pos += 1
132 132 yield ('end', None, pos)
133 133
134 134 # helpers
135 135
136 136 def getstring(x, err):
137 137 if x and (x[0] == 'string' or x[0] == 'symbol'):
138 138 return x[1]
139 139 raise error.ParseError(err)
140 140
141 141 def getlist(x):
142 142 if not x:
143 143 return []
144 144 if x[0] == 'list':
145 145 return getlist(x[1]) + [x[2]]
146 146 return [x]
147 147
148 148 def getargs(x, min, max, err):
149 149 l = getlist(x)
150 150 if len(l) < min or (max >= 0 and len(l) > max):
151 151 raise error.ParseError(err)
152 152 return l
153 153
154 154 def getset(repo, subset, x):
155 155 if not x:
156 156 raise error.ParseError(_("missing argument"))
157 157 return methods[x[0]](repo, subset, *x[1:])
158 158
159 159 # operator methods
160 160
161 161 def stringset(repo, subset, x):
162 162 x = repo[x].rev()
163 163 if x == -1 and len(subset) == len(repo):
164 164 return [-1]
165 165 if len(subset) == len(repo) or x in subset:
166 166 return [x]
167 167 return []
168 168
169 169 def symbolset(repo, subset, x):
170 170 if x in symbols:
171 171 raise error.ParseError(_("can't use %s here") % x)
172 172 return stringset(repo, subset, x)
173 173
174 174 def rangeset(repo, subset, x, y):
175 175 m = getset(repo, subset, x)
176 176 if not m:
177 177 m = getset(repo, range(len(repo)), x)
178 178
179 179 n = getset(repo, subset, y)
180 180 if not n:
181 181 n = getset(repo, range(len(repo)), y)
182 182
183 183 if not m or not n:
184 184 return []
185 185 m, n = m[0], n[-1]
186 186
187 187 if m < n:
188 188 r = range(m, n + 1)
189 189 else:
190 190 r = range(m, n - 1, -1)
191 191 s = set(subset)
192 192 return [x for x in r if x in s]
193 193
194 194 def andset(repo, subset, x, y):
195 195 return getset(repo, getset(repo, subset, x), y)
196 196
197 197 def orset(repo, subset, x, y):
198 198 xl = getset(repo, subset, x)
199 199 s = set(xl)
200 200 yl = getset(repo, [r for r in subset if r not in s], y)
201 201 return xl + yl
202 202
203 203 def notset(repo, subset, x):
204 204 s = set(getset(repo, subset, x))
205 205 return [r for r in subset if r not in s]
206 206
207 207 def listset(repo, subset, a, b):
208 208 raise error.ParseError(_("can't use a list in this context"))
209 209
210 210 def func(repo, subset, a, b):
211 211 if a[0] == 'symbol' and a[1] in symbols:
212 212 return symbols[a[1]](repo, subset, b)
213 213 raise error.ParseError(_("not a function: %s") % a[1])
214 214
215 215 # functions
216 216
217 217 def adds(repo, subset, x):
218 218 """``adds(pattern)``
219 219 Changesets that add a file matching pattern.
220 220 """
221 221 # i18n: "adds" is a keyword
222 222 pat = getstring(x, _("adds requires a pattern"))
223 223 return checkstatus(repo, subset, pat, 1)
224 224
225 225 def ancestor(repo, subset, x):
226 226 """``ancestor(single, single)``
227 227 Greatest common ancestor of the two changesets.
228 228 """
229 229 # i18n: "ancestor" is a keyword
230 230 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
231 231 r = range(len(repo))
232 232 a = getset(repo, r, l[0])
233 233 b = getset(repo, r, l[1])
234 234 if len(a) != 1 or len(b) != 1:
235 235 # i18n: "ancestor" is a keyword
236 236 raise error.ParseError(_("ancestor arguments must be single revisions"))
237 237 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
238 238
239 239 return [r for r in an if r in subset]
240 240
241 241 def _ancestors(repo, subset, x, followfirst=False):
242 242 args = getset(repo, range(len(repo)), x)
243 243 if not args:
244 244 return []
245 245 s = set(_revancestors(repo, args, followfirst)) | set(args)
246 246 return [r for r in subset if r in s]
247 247
248 248 def ancestors(repo, subset, x):
249 249 """``ancestors(set)``
250 250 Changesets that are ancestors of a changeset in set.
251 251 """
252 252 return _ancestors(repo, subset, x)
253 253
254 254 def _firstancestors(repo, subset, x):
255 255 # ``_firstancestors(set)``
256 256 # Like ``ancestors(set)`` but follows only the first parents.
257 257 return _ancestors(repo, subset, x, followfirst=True)
258 258
259 259 def ancestorspec(repo, subset, x, n):
260 260 """``set~n``
261 261 Changesets that are the Nth ancestor (first parents only) of a changeset
262 262 in set.
263 263 """
264 264 try:
265 265 n = int(n[1])
266 266 except (TypeError, ValueError):
267 267 raise error.ParseError(_("~ expects a number"))
268 268 ps = set()
269 269 cl = repo.changelog
270 270 for r in getset(repo, subset, x):
271 271 for i in range(n):
272 272 r = cl.parentrevs(r)[0]
273 273 ps.add(r)
274 274 return [r for r in subset if r in ps]
275 275
276 276 def author(repo, subset, x):
277 277 """``author(string)``
278 278 Alias for ``user(string)``.
279 279 """
280 280 # i18n: "author" is a keyword
281 281 n = encoding.lower(getstring(x, _("author requires a string")))
282 282 return [r for r in subset if n in encoding.lower(repo[r].user())]
283 283
284 284 def bisect(repo, subset, x):
285 285 """``bisect(string)``
286 286 Changesets marked in the specified bisect status:
287 287
288 288 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
289 289 - ``goods``, ``bads`` : csets topologicaly good/bad
290 290 - ``range`` : csets taking part in the bisection
291 291 - ``pruned`` : csets that are goods, bads or skipped
292 292 - ``untested`` : csets whose fate is yet unknown
293 293 - ``ignored`` : csets ignored due to DAG topology
294 294 - ``current`` : the cset currently being bisected
295 295 """
296 296 status = getstring(x, _("bisect requires a string")).lower()
297 297 state = set(hbisect.get(repo, status))
298 298 return [r for r in subset if r in state]
299 299
300 300 # Backward-compatibility
301 301 # - no help entry so that we do not advertise it any more
302 302 def bisected(repo, subset, x):
303 303 return bisect(repo, subset, x)
304 304
305 305 def bookmark(repo, subset, x):
306 306 """``bookmark([name])``
307 307 The named bookmark or all bookmarks.
308 308 """
309 309 # i18n: "bookmark" is a keyword
310 310 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
311 311 if args:
312 312 bm = getstring(args[0],
313 313 # i18n: "bookmark" is a keyword
314 314 _('the argument to bookmark must be a string'))
315 315 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
316 316 if not bmrev:
317 317 raise util.Abort(_("bookmark '%s' does not exist") % bm)
318 318 bmrev = repo[bmrev].rev()
319 319 return [r for r in subset if r == bmrev]
320 320 bms = set([repo[r].rev()
321 321 for r in bookmarksmod.listbookmarks(repo).values()])
322 322 return [r for r in subset if r in bms]
323 323
324 324 def branch(repo, subset, x):
325 325 """``branch(string or set)``
326 326 All changesets belonging to the given branch or the branches of the given
327 327 changesets.
328 328 """
329 329 try:
330 330 b = getstring(x, '')
331 331 if b in repo.branchmap():
332 332 return [r for r in subset if repo[r].branch() == b]
333 333 except error.ParseError:
334 334 # not a string, but another revspec, e.g. tip()
335 335 pass
336 336
337 337 s = getset(repo, range(len(repo)), x)
338 338 b = set()
339 339 for r in s:
340 340 b.add(repo[r].branch())
341 341 s = set(s)
342 342 return [r for r in subset if r in s or repo[r].branch() in b]
343 343
344 344 def checkstatus(repo, subset, pat, field):
345 345 m = None
346 346 s = []
347 347 hasset = matchmod.patkind(pat) == 'set'
348 348 fname = None
349 349 for r in subset:
350 350 c = repo[r]
351 351 if not m or hasset:
352 352 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
353 353 if not m.anypats() and len(m.files()) == 1:
354 354 fname = m.files()[0]
355 355 if fname is not None:
356 356 if fname not in c.files():
357 357 continue
358 358 else:
359 359 for f in c.files():
360 360 if m(f):
361 361 break
362 362 else:
363 363 continue
364 364 files = repo.status(c.p1().node(), c.node())[field]
365 365 if fname is not None:
366 366 if fname in files:
367 367 s.append(r)
368 368 else:
369 369 for f in files:
370 370 if m(f):
371 371 s.append(r)
372 372 break
373 373 return s
374 374
375 375 def _children(repo, narrow, parentset):
376 376 cs = set()
377 377 pr = repo.changelog.parentrevs
378 378 for r in narrow:
379 379 for p in pr(r):
380 380 if p in parentset:
381 381 cs.add(r)
382 382 return cs
383 383
384 384 def children(repo, subset, x):
385 385 """``children(set)``
386 386 Child changesets of changesets in set.
387 387 """
388 388 s = set(getset(repo, range(len(repo)), x))
389 389 cs = _children(repo, subset, s)
390 390 return [r for r in subset if r in cs]
391 391
392 392 def closed(repo, subset, x):
393 393 """``closed()``
394 394 Changeset is closed.
395 395 """
396 396 # i18n: "closed" is a keyword
397 397 getargs(x, 0, 0, _("closed takes no arguments"))
398 398 return [r for r in subset if repo[r].closesbranch()]
399 399
400 400 def contains(repo, subset, x):
401 401 """``contains(pattern)``
402 402 Revision contains a file matching pattern. See :hg:`help patterns`
403 403 for information about file patterns.
404 404 """
405 405 # i18n: "contains" is a keyword
406 406 pat = getstring(x, _("contains requires a pattern"))
407 407 m = None
408 408 s = []
409 409 if not matchmod.patkind(pat):
410 410 for r in subset:
411 411 if pat in repo[r]:
412 412 s.append(r)
413 413 else:
414 414 for r in subset:
415 415 c = repo[r]
416 416 if not m or matchmod.patkind(pat) == 'set':
417 417 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
418 418 for f in c.manifest():
419 419 if m(f):
420 420 s.append(r)
421 421 break
422 422 return s
423 423
424 424 def date(repo, subset, x):
425 425 """``date(interval)``
426 426 Changesets within the interval, see :hg:`help dates`.
427 427 """
428 428 # i18n: "date" is a keyword
429 429 ds = getstring(x, _("date requires a string"))
430 430 dm = util.matchdate(ds)
431 431 return [r for r in subset if dm(repo[r].date()[0])]
432 432
433 433 def desc(repo, subset, x):
434 434 """``desc(string)``
435 435 Search commit message for string. The match is case-insensitive.
436 436 """
437 437 # i18n: "desc" is a keyword
438 438 ds = encoding.lower(getstring(x, _("desc requires a string")))
439 439 l = []
440 440 for r in subset:
441 441 c = repo[r]
442 442 if ds in encoding.lower(c.description()):
443 443 l.append(r)
444 444 return l
445 445
446 446 def _descendants(repo, subset, x, followfirst=False):
447 447 args = getset(repo, range(len(repo)), x)
448 448 if not args:
449 449 return []
450 450 s = set(_revdescendants(repo, args, followfirst)) | set(args)
451 451 return [r for r in subset if r in s]
452 452
453 453 def descendants(repo, subset, x):
454 454 """``descendants(set)``
455 455 Changesets which are descendants of changesets in set.
456 456 """
457 457 return _descendants(repo, subset, x)
458 458
459 459 def _firstdescendants(repo, subset, x):
460 460 # ``_firstdescendants(set)``
461 461 # Like ``descendants(set)`` but follows only the first parents.
462 462 return _descendants(repo, subset, x, followfirst=True)
463 463
464 464 def draft(repo, subset, x):
465 465 """``draft()``
466 466 Changeset in draft phase."""
467 467 getargs(x, 0, 0, _("draft takes no arguments"))
468 468 pc = repo._phasecache
469 469 return [r for r in subset if pc.phase(repo, r) == phases.draft]
470 470
471 471 def extra(repo, subset, x):
472 472 """``extra(label, [value])``
473 473 Changesets with the given label in the extra metadata, with the given
474 474 optional value."""
475 475
476 476 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
477 477 label = getstring(l[0], _('first argument to extra must be a string'))
478 478 value = None
479 479
480 480 if len(l) > 1:
481 481 value = getstring(l[1], _('second argument to extra must be a string'))
482 482
483 483 def _matchvalue(r):
484 484 extra = repo[r].extra()
485 485 return label in extra and (value is None or value == extra[label])
486 486
487 487 return [r for r in subset if _matchvalue(r)]
488 488
489 489 def filelog(repo, subset, x):
490 490 """``filelog(pattern)``
491 491 Changesets connected to the specified filelog.
492 492 """
493 493
494 494 pat = getstring(x, _("filelog requires a pattern"))
495 495 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
496 496 ctx=repo[None])
497 497 s = set()
498 498
499 499 if not matchmod.patkind(pat):
500 500 for f in m.files():
501 501 fl = repo.file(f)
502 502 for fr in fl:
503 503 s.add(fl.linkrev(fr))
504 504 else:
505 505 for f in repo[None]:
506 506 if m(f):
507 507 fl = repo.file(f)
508 508 for fr in fl:
509 509 s.add(fl.linkrev(fr))
510 510
511 511 return [r for r in subset if r in s]
512 512
513 513 def first(repo, subset, x):
514 514 """``first(set, [n])``
515 515 An alias for limit().
516 516 """
517 517 return limit(repo, subset, x)
518 518
519 519 def _follow(repo, subset, x, name, followfirst=False):
520 520 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
521 521 c = repo['.']
522 522 if l:
523 523 x = getstring(l[0], _("%s expected a filename") % name)
524 524 if x in c:
525 525 cx = c[x]
526 526 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
527 527 # include the revision responsible for the most recent version
528 528 s.add(cx.linkrev())
529 529 else:
530 530 return []
531 531 else:
532 532 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
533 533
534 534 return [r for r in subset if r in s]
535 535
536 536 def follow(repo, subset, x):
537 537 """``follow([file])``
538 538 An alias for ``::.`` (ancestors of the working copy's first parent).
539 539 If a filename is specified, the history of the given file is followed,
540 540 including copies.
541 541 """
542 542 return _follow(repo, subset, x, 'follow')
543 543
544 544 def _followfirst(repo, subset, x):
545 545 # ``followfirst([file])``
546 546 # Like ``follow([file])`` but follows only the first parent of
547 547 # every revision or file revision.
548 548 return _follow(repo, subset, x, '_followfirst', followfirst=True)
549 549
550 550 def getall(repo, subset, x):
551 551 """``all()``
552 552 All changesets, the same as ``0:tip``.
553 553 """
554 554 # i18n: "all" is a keyword
555 555 getargs(x, 0, 0, _("all takes no arguments"))
556 556 return subset
557 557
558 558 def grep(repo, subset, x):
559 559 """``grep(regex)``
560 560 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
561 561 to ensure special escape characters are handled correctly. Unlike
562 562 ``keyword(string)``, the match is case-sensitive.
563 563 """
564 564 try:
565 565 # i18n: "grep" is a keyword
566 566 gr = re.compile(getstring(x, _("grep requires a string")))
567 567 except re.error, e:
568 568 raise error.ParseError(_('invalid match pattern: %s') % e)
569 569 l = []
570 570 for r in subset:
571 571 c = repo[r]
572 572 for e in c.files() + [c.user(), c.description()]:
573 573 if gr.search(e):
574 574 l.append(r)
575 575 break
576 576 return l
577 577
578 578 def _matchfiles(repo, subset, x):
579 579 # _matchfiles takes a revset list of prefixed arguments:
580 580 #
581 581 # [p:foo, i:bar, x:baz]
582 582 #
583 583 # builds a match object from them and filters subset. Allowed
584 584 # prefixes are 'p:' for regular patterns, 'i:' for include
585 585 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
586 586 # a revision identifier, or the empty string to reference the
587 587 # working directory, from which the match object is
588 588 # initialized. Use 'd:' to set the default matching mode, default
589 589 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
590 590
591 591 # i18n: "_matchfiles" is a keyword
592 592 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
593 593 pats, inc, exc = [], [], []
594 594 hasset = False
595 595 rev, default = None, None
596 596 for arg in l:
597 597 s = getstring(arg, _("_matchfiles requires string arguments"))
598 598 prefix, value = s[:2], s[2:]
599 599 if prefix == 'p:':
600 600 pats.append(value)
601 601 elif prefix == 'i:':
602 602 inc.append(value)
603 603 elif prefix == 'x:':
604 604 exc.append(value)
605 605 elif prefix == 'r:':
606 606 if rev is not None:
607 607 raise error.ParseError(_('_matchfiles expected at most one '
608 608 'revision'))
609 609 rev = value
610 610 elif prefix == 'd:':
611 611 if default is not None:
612 612 raise error.ParseError(_('_matchfiles expected at most one '
613 613 'default mode'))
614 614 default = value
615 615 else:
616 616 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
617 617 if not hasset and matchmod.patkind(value) == 'set':
618 618 hasset = True
619 619 if not default:
620 620 default = 'glob'
621 621 m = None
622 622 s = []
623 623 for r in subset:
624 624 c = repo[r]
625 625 if not m or (hasset and rev is None):
626 626 ctx = c
627 627 if rev is not None:
628 628 ctx = repo[rev or None]
629 629 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
630 630 exclude=exc, ctx=ctx, default=default)
631 631 for f in c.files():
632 632 if m(f):
633 633 s.append(r)
634 634 break
635 635 return s
636 636
637 637 def hasfile(repo, subset, x):
638 638 """``file(pattern)``
639 639 Changesets affecting files matched by pattern.
640 640 """
641 641 # i18n: "file" is a keyword
642 642 pat = getstring(x, _("file requires a pattern"))
643 643 return _matchfiles(repo, subset, ('string', 'p:' + pat))
644 644
645 645 def head(repo, subset, x):
646 646 """``head()``
647 647 Changeset is a named branch head.
648 648 """
649 649 # i18n: "head" is a keyword
650 650 getargs(x, 0, 0, _("head takes no arguments"))
651 651 hs = set()
652 652 for b, ls in repo.branchmap().iteritems():
653 653 hs.update(repo[h].rev() for h in ls)
654 654 return [r for r in subset if r in hs]
655 655
656 656 def heads(repo, subset, x):
657 657 """``heads(set)``
658 658 Members of set with no children in set.
659 659 """
660 660 s = getset(repo, subset, x)
661 661 ps = set(parents(repo, subset, x))
662 662 return [r for r in s if r not in ps]
663 663
664 664 def keyword(repo, subset, x):
665 665 """``keyword(string)``
666 666 Search commit message, user name, and names of changed files for
667 667 string. The match is case-insensitive.
668 668 """
669 669 # i18n: "keyword" is a keyword
670 670 kw = encoding.lower(getstring(x, _("keyword requires a string")))
671 671 l = []
672 672 for r in subset:
673 673 c = repo[r]
674 674 t = " ".join(c.files() + [c.user(), c.description()])
675 675 if kw in encoding.lower(t):
676 676 l.append(r)
677 677 return l
678 678
679 679 def limit(repo, subset, x):
680 680 """``limit(set, [n])``
681 681 First n members of set, defaulting to 1.
682 682 """
683 683 # i18n: "limit" is a keyword
684 684 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
685 685 try:
686 686 lim = 1
687 687 if len(l) == 2:
688 688 # i18n: "limit" is a keyword
689 689 lim = int(getstring(l[1], _("limit requires a number")))
690 690 except (TypeError, ValueError):
691 691 # i18n: "limit" is a keyword
692 692 raise error.ParseError(_("limit expects a number"))
693 693 ss = set(subset)
694 694 os = getset(repo, range(len(repo)), l[0])[:lim]
695 695 return [r for r in os if r in ss]
696 696
697 697 def last(repo, subset, x):
698 698 """``last(set, [n])``
699 699 Last n members of set, defaulting to 1.
700 700 """
701 701 # i18n: "last" is a keyword
702 702 l = getargs(x, 1, 2, _("last requires one or two arguments"))
703 703 try:
704 704 lim = 1
705 705 if len(l) == 2:
706 706 # i18n: "last" is a keyword
707 707 lim = int(getstring(l[1], _("last requires a number")))
708 708 except (TypeError, ValueError):
709 709 # i18n: "last" is a keyword
710 710 raise error.ParseError(_("last expects a number"))
711 711 ss = set(subset)
712 712 os = getset(repo, range(len(repo)), l[0])[-lim:]
713 713 return [r for r in os if r in ss]
714 714
715 715 def maxrev(repo, subset, x):
716 716 """``max(set)``
717 717 Changeset with highest revision number in set.
718 718 """
719 719 os = getset(repo, range(len(repo)), x)
720 720 if os:
721 721 m = max(os)
722 722 if m in subset:
723 723 return [m]
724 724 return []
725 725
726 726 def merge(repo, subset, x):
727 727 """``merge()``
728 728 Changeset is a merge changeset.
729 729 """
730 730 # i18n: "merge" is a keyword
731 731 getargs(x, 0, 0, _("merge takes no arguments"))
732 732 cl = repo.changelog
733 733 return [r for r in subset if cl.parentrevs(r)[1] != -1]
734 734
735 735 def minrev(repo, subset, x):
736 736 """``min(set)``
737 737 Changeset with lowest revision number in set.
738 738 """
739 739 os = getset(repo, range(len(repo)), x)
740 740 if os:
741 741 m = min(os)
742 742 if m in subset:
743 743 return [m]
744 744 return []
745 745
746 746 def modifies(repo, subset, x):
747 747 """``modifies(pattern)``
748 748 Changesets modifying files matched by pattern.
749 749 """
750 750 # i18n: "modifies" is a keyword
751 751 pat = getstring(x, _("modifies requires a pattern"))
752 752 return checkstatus(repo, subset, pat, 0)
753 753
754 754 def node_(repo, subset, x):
755 755 """``id(string)``
756 756 Revision non-ambiguously specified by the given hex string prefix.
757 757 """
758 758 # i18n: "id" is a keyword
759 759 l = getargs(x, 1, 1, _("id requires one argument"))
760 760 # i18n: "id" is a keyword
761 761 n = getstring(l[0], _("id requires a string"))
762 762 if len(n) == 40:
763 763 rn = repo[n].rev()
764 764 else:
765 765 rn = None
766 766 pm = repo.changelog._partialmatch(n)
767 767 if pm is not None:
768 768 rn = repo.changelog.rev(pm)
769 769
770 770 return [r for r in subset if r == rn]
771 771
772 772 def outgoing(repo, subset, x):
773 773 """``outgoing([path])``
774 774 Changesets not found in the specified destination repository, or the
775 775 default push location.
776 776 """
777 777 import hg # avoid start-up nasties
778 778 # i18n: "outgoing" is a keyword
779 779 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
780 780 # i18n: "outgoing" is a keyword
781 781 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
782 782 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
783 783 dest, branches = hg.parseurl(dest)
784 784 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
785 785 if revs:
786 786 revs = [repo.lookup(rev) for rev in revs]
787 787 other = hg.peer(repo, {}, dest)
788 788 repo.ui.pushbuffer()
789 789 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
790 790 repo.ui.popbuffer()
791 791 cl = repo.changelog
792 792 o = set([cl.rev(r) for r in outgoing.missing])
793 793 return [r for r in subset if r in o]
794 794
795 795 def p1(repo, subset, x):
796 796 """``p1([set])``
797 797 First parent of changesets in set, or the working directory.
798 798 """
799 799 if x is None:
800 800 p = repo[x].p1().rev()
801 801 return [r for r in subset if r == p]
802 802
803 803 ps = set()
804 804 cl = repo.changelog
805 805 for r in getset(repo, range(len(repo)), x):
806 806 ps.add(cl.parentrevs(r)[0])
807 807 return [r for r in subset if r in ps]
808 808
809 809 def p2(repo, subset, x):
810 810 """``p2([set])``
811 811 Second parent of changesets in set, or the working directory.
812 812 """
813 813 if x is None:
814 814 ps = repo[x].parents()
815 815 try:
816 816 p = ps[1].rev()
817 817 return [r for r in subset if r == p]
818 818 except IndexError:
819 819 return []
820 820
821 821 ps = set()
822 822 cl = repo.changelog
823 823 for r in getset(repo, range(len(repo)), x):
824 824 ps.add(cl.parentrevs(r)[1])
825 825 return [r for r in subset if r in ps]
826 826
827 827 def parents(repo, subset, x):
828 828 """``parents([set])``
829 829 The set of all parents for all changesets in set, or the working directory.
830 830 """
831 831 if x is None:
832 832 ps = tuple(p.rev() for p in repo[x].parents())
833 833 return [r for r in subset if r in ps]
834 834
835 835 ps = set()
836 836 cl = repo.changelog
837 837 for r in getset(repo, range(len(repo)), x):
838 838 ps.update(cl.parentrevs(r))
839 839 return [r for r in subset if r in ps]
840 840
841 841 def parentspec(repo, subset, x, n):
842 842 """``set^0``
843 843 The set.
844 844 ``set^1`` (or ``set^``), ``set^2``
845 845 First or second parent, respectively, of all changesets in set.
846 846 """
847 847 try:
848 848 n = int(n[1])
849 849 if n not in (0, 1, 2):
850 850 raise ValueError
851 851 except (TypeError, ValueError):
852 852 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
853 853 ps = set()
854 854 cl = repo.changelog
855 855 for r in getset(repo, subset, x):
856 856 if n == 0:
857 857 ps.add(r)
858 858 elif n == 1:
859 859 ps.add(cl.parentrevs(r)[0])
860 860 elif n == 2:
861 861 parents = cl.parentrevs(r)
862 862 if len(parents) > 1:
863 863 ps.add(parents[1])
864 864 return [r for r in subset if r in ps]
865 865
866 866 def present(repo, subset, x):
867 867 """``present(set)``
868 868 An empty set, if any revision in set isn't found; otherwise,
869 869 all revisions in set.
870
871 If any of specified revisions is not present in the local repository,
872 the query is normally aborted. But this predicate allows the query
873 to continue even in such cases.
870 874 """
871 875 try:
872 876 return getset(repo, subset, x)
873 877 except error.RepoLookupError:
874 878 return []
875 879
876 880 def public(repo, subset, x):
877 881 """``public()``
878 882 Changeset in public phase."""
879 883 getargs(x, 0, 0, _("public takes no arguments"))
880 884 pc = repo._phasecache
881 885 return [r for r in subset if pc.phase(repo, r) == phases.public]
882 886
883 887 def remote(repo, subset, x):
884 888 """``remote([id [,path]])``
885 889 Local revision that corresponds to the given identifier in a
886 890 remote repository, if present. Here, the '.' identifier is a
887 891 synonym for the current local branch.
888 892 """
889 893
890 894 import hg # avoid start-up nasties
891 895 # i18n: "remote" is a keyword
892 896 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
893 897
894 898 q = '.'
895 899 if len(l) > 0:
896 900 # i18n: "remote" is a keyword
897 901 q = getstring(l[0], _("remote requires a string id"))
898 902 if q == '.':
899 903 q = repo['.'].branch()
900 904
901 905 dest = ''
902 906 if len(l) > 1:
903 907 # i18n: "remote" is a keyword
904 908 dest = getstring(l[1], _("remote requires a repository path"))
905 909 dest = repo.ui.expandpath(dest or 'default')
906 910 dest, branches = hg.parseurl(dest)
907 911 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
908 912 if revs:
909 913 revs = [repo.lookup(rev) for rev in revs]
910 914 other = hg.peer(repo, {}, dest)
911 915 n = other.lookup(q)
912 916 if n in repo:
913 917 r = repo[n].rev()
914 918 if r in subset:
915 919 return [r]
916 920 return []
917 921
918 922 def removes(repo, subset, x):
919 923 """``removes(pattern)``
920 924 Changesets which remove files matching pattern.
921 925 """
922 926 # i18n: "removes" is a keyword
923 927 pat = getstring(x, _("removes requires a pattern"))
924 928 return checkstatus(repo, subset, pat, 2)
925 929
926 930 def rev(repo, subset, x):
927 931 """``rev(number)``
928 932 Revision with the given numeric identifier.
929 933 """
930 934 # i18n: "rev" is a keyword
931 935 l = getargs(x, 1, 1, _("rev requires one argument"))
932 936 try:
933 937 # i18n: "rev" is a keyword
934 938 l = int(getstring(l[0], _("rev requires a number")))
935 939 except (TypeError, ValueError):
936 940 # i18n: "rev" is a keyword
937 941 raise error.ParseError(_("rev expects a number"))
938 942 return [r for r in subset if r == l]
939 943
940 944 def matching(repo, subset, x):
941 945 """``matching(revision [, field])``
942 946 Changesets in which a given set of fields match the set of fields in the
943 947 selected revision or set.
944 948
945 949 To match more than one field pass the list of fields to match separated
946 950 by spaces (e.g. ``author description``).
947 951
948 952 Valid fields are most regular revision fields and some special fields.
949 953
950 954 Regular revision fields are ``description``, ``author``, ``branch``,
951 955 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
952 956 Note that ``author`` and ``user`` are synonyms.
953 957
954 958 Special fields are ``summary`` and ``metadata``:
955 959 ``summary`` matches the first line of the description.
956 960 ``metadata`` is equivalent to matching ``description user date``
957 961 (i.e. it matches the main metadata fields).
958 962
959 963 ``metadata`` is the default field which is used when no fields are
960 964 specified. You can match more than one field at a time.
961 965 """
962 966 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
963 967
964 968 revs = getset(repo, xrange(len(repo)), l[0])
965 969
966 970 fieldlist = ['metadata']
967 971 if len(l) > 1:
968 972 fieldlist = getstring(l[1],
969 973 _("matching requires a string "
970 974 "as its second argument")).split()
971 975
972 976 # Make sure that there are no repeated fields, and expand the
973 977 # 'special' 'metadata' field type
974 978 fields = []
975 979 for field in fieldlist:
976 980 if field == 'metadata':
977 981 fields += ['user', 'description', 'date']
978 982 else:
979 983 if field == 'author':
980 984 field = 'user'
981 985 fields.append(field)
982 986 fields = set(fields)
983 987 if 'summary' in fields and 'description' in fields:
984 988 # If a revision matches its description it also matches its summary
985 989 fields.discard('summary')
986 990
987 991 # We may want to match more than one field
988 992 # Not all fields take the same amount of time to be matched
989 993 # Sort the selected fields in order of increasing matching cost
990 994 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
991 995 'files', 'description', 'substate']
992 996 def fieldkeyfunc(f):
993 997 try:
994 998 return fieldorder.index(f)
995 999 except ValueError:
996 1000 # assume an unknown field is very costly
997 1001 return len(fieldorder)
998 1002 fields = list(fields)
999 1003 fields.sort(key=fieldkeyfunc)
1000 1004
1001 1005 # Each field will be matched with its own "getfield" function
1002 1006 # which will be added to the getfieldfuncs array of functions
1003 1007 getfieldfuncs = []
1004 1008 _funcs = {
1005 1009 'user': lambda r: repo[r].user(),
1006 1010 'branch': lambda r: repo[r].branch(),
1007 1011 'date': lambda r: repo[r].date(),
1008 1012 'description': lambda r: repo[r].description(),
1009 1013 'files': lambda r: repo[r].files(),
1010 1014 'parents': lambda r: repo[r].parents(),
1011 1015 'phase': lambda r: repo[r].phase(),
1012 1016 'substate': lambda r: repo[r].substate,
1013 1017 'summary': lambda r: repo[r].description().splitlines()[0],
1014 1018 }
1015 1019 for info in fields:
1016 1020 getfield = _funcs.get(info, None)
1017 1021 if getfield is None:
1018 1022 raise error.ParseError(
1019 1023 _("unexpected field name passed to matching: %s") % info)
1020 1024 getfieldfuncs.append(getfield)
1021 1025 # convert the getfield array of functions into a "getinfo" function
1022 1026 # which returns an array of field values (or a single value if there
1023 1027 # is only one field to match)
1024 1028 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1025 1029
1026 1030 matches = set()
1027 1031 for rev in revs:
1028 1032 target = getinfo(rev)
1029 1033 for r in subset:
1030 1034 match = True
1031 1035 for n, f in enumerate(getfieldfuncs):
1032 1036 if target[n] != f(r):
1033 1037 match = False
1034 1038 break
1035 1039 if match:
1036 1040 matches.add(r)
1037 1041 return [r for r in subset if r in matches]
1038 1042
1039 1043 def reverse(repo, subset, x):
1040 1044 """``reverse(set)``
1041 1045 Reverse order of set.
1042 1046 """
1043 1047 l = getset(repo, subset, x)
1044 1048 l.reverse()
1045 1049 return l
1046 1050
1047 1051 def roots(repo, subset, x):
1048 1052 """``roots(set)``
1049 1053 Changesets in set with no parent changeset in set.
1050 1054 """
1051 1055 s = set(getset(repo, xrange(len(repo)), x))
1052 1056 subset = [r for r in subset if r in s]
1053 1057 cs = _children(repo, subset, s)
1054 1058 return [r for r in subset if r not in cs]
1055 1059
1056 1060 def secret(repo, subset, x):
1057 1061 """``secret()``
1058 1062 Changeset in secret phase."""
1059 1063 getargs(x, 0, 0, _("secret takes no arguments"))
1060 1064 pc = repo._phasecache
1061 1065 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1062 1066
1063 1067 def sort(repo, subset, x):
1064 1068 """``sort(set[, [-]key...])``
1065 1069 Sort set by keys. The default sort order is ascending, specify a key
1066 1070 as ``-key`` to sort in descending order.
1067 1071
1068 1072 The keys can be:
1069 1073
1070 1074 - ``rev`` for the revision number,
1071 1075 - ``branch`` for the branch name,
1072 1076 - ``desc`` for the commit message (description),
1073 1077 - ``user`` for user name (``author`` can be used as an alias),
1074 1078 - ``date`` for the commit date
1075 1079 """
1076 1080 # i18n: "sort" is a keyword
1077 1081 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1078 1082 keys = "rev"
1079 1083 if len(l) == 2:
1080 1084 keys = getstring(l[1], _("sort spec must be a string"))
1081 1085
1082 1086 s = l[0]
1083 1087 keys = keys.split()
1084 1088 l = []
1085 1089 def invert(s):
1086 1090 return "".join(chr(255 - ord(c)) for c in s)
1087 1091 for r in getset(repo, subset, s):
1088 1092 c = repo[r]
1089 1093 e = []
1090 1094 for k in keys:
1091 1095 if k == 'rev':
1092 1096 e.append(r)
1093 1097 elif k == '-rev':
1094 1098 e.append(-r)
1095 1099 elif k == 'branch':
1096 1100 e.append(c.branch())
1097 1101 elif k == '-branch':
1098 1102 e.append(invert(c.branch()))
1099 1103 elif k == 'desc':
1100 1104 e.append(c.description())
1101 1105 elif k == '-desc':
1102 1106 e.append(invert(c.description()))
1103 1107 elif k in 'user author':
1104 1108 e.append(c.user())
1105 1109 elif k in '-user -author':
1106 1110 e.append(invert(c.user()))
1107 1111 elif k == 'date':
1108 1112 e.append(c.date()[0])
1109 1113 elif k == '-date':
1110 1114 e.append(-c.date()[0])
1111 1115 else:
1112 1116 raise error.ParseError(_("unknown sort key %r") % k)
1113 1117 e.append(r)
1114 1118 l.append(e)
1115 1119 l.sort()
1116 1120 return [e[-1] for e in l]
1117 1121
1118 1122 def tag(repo, subset, x):
1119 1123 """``tag([name])``
1120 1124 The specified tag by name, or all tagged revisions if no name is given.
1121 1125 """
1122 1126 # i18n: "tag" is a keyword
1123 1127 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1124 1128 cl = repo.changelog
1125 1129 if args:
1126 1130 tn = getstring(args[0],
1127 1131 # i18n: "tag" is a keyword
1128 1132 _('the argument to tag must be a string'))
1129 1133 if not repo.tags().get(tn, None):
1130 1134 raise util.Abort(_("tag '%s' does not exist") % tn)
1131 1135 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1132 1136 else:
1133 1137 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1134 1138 return [r for r in subset if r in s]
1135 1139
1136 1140 def tagged(repo, subset, x):
1137 1141 return tag(repo, subset, x)
1138 1142
1139 1143 def user(repo, subset, x):
1140 1144 """``user(string)``
1141 1145 User name contains string. The match is case-insensitive.
1142 1146 """
1143 1147 return author(repo, subset, x)
1144 1148
1145 1149 # for internal use
1146 1150 def _list(repo, subset, x):
1147 1151 s = getstring(x, "internal error")
1148 1152 if not s:
1149 1153 return []
1150 1154 if not isinstance(subset, set):
1151 1155 subset = set(subset)
1152 1156 ls = [repo[r].rev() for r in s.split('\0')]
1153 1157 return [r for r in ls if r in subset]
1154 1158
1155 1159 symbols = {
1156 1160 "adds": adds,
1157 1161 "all": getall,
1158 1162 "ancestor": ancestor,
1159 1163 "ancestors": ancestors,
1160 1164 "_firstancestors": _firstancestors,
1161 1165 "author": author,
1162 1166 "bisect": bisect,
1163 1167 "bisected": bisected,
1164 1168 "bookmark": bookmark,
1165 1169 "branch": branch,
1166 1170 "children": children,
1167 1171 "closed": closed,
1168 1172 "contains": contains,
1169 1173 "date": date,
1170 1174 "desc": desc,
1171 1175 "descendants": descendants,
1172 1176 "_firstdescendants": _firstdescendants,
1173 1177 "draft": draft,
1174 1178 "extra": extra,
1175 1179 "file": hasfile,
1176 1180 "filelog": filelog,
1177 1181 "first": first,
1178 1182 "follow": follow,
1179 1183 "_followfirst": _followfirst,
1180 1184 "grep": grep,
1181 1185 "head": head,
1182 1186 "heads": heads,
1183 1187 "id": node_,
1184 1188 "keyword": keyword,
1185 1189 "last": last,
1186 1190 "limit": limit,
1187 1191 "_matchfiles": _matchfiles,
1188 1192 "max": maxrev,
1189 1193 "merge": merge,
1190 1194 "min": minrev,
1191 1195 "modifies": modifies,
1192 1196 "outgoing": outgoing,
1193 1197 "p1": p1,
1194 1198 "p2": p2,
1195 1199 "parents": parents,
1196 1200 "present": present,
1197 1201 "public": public,
1198 1202 "remote": remote,
1199 1203 "removes": removes,
1200 1204 "rev": rev,
1201 1205 "reverse": reverse,
1202 1206 "roots": roots,
1203 1207 "sort": sort,
1204 1208 "secret": secret,
1205 1209 "matching": matching,
1206 1210 "tag": tag,
1207 1211 "tagged": tagged,
1208 1212 "user": user,
1209 1213 "_list": _list,
1210 1214 }
1211 1215
1212 1216 methods = {
1213 1217 "range": rangeset,
1214 1218 "string": stringset,
1215 1219 "symbol": symbolset,
1216 1220 "and": andset,
1217 1221 "or": orset,
1218 1222 "not": notset,
1219 1223 "list": listset,
1220 1224 "func": func,
1221 1225 "ancestor": ancestorspec,
1222 1226 "parent": parentspec,
1223 1227 "parentpost": p1,
1224 1228 }
1225 1229
1226 1230 def optimize(x, small):
1227 1231 if x is None:
1228 1232 return 0, x
1229 1233
1230 1234 smallbonus = 1
1231 1235 if small:
1232 1236 smallbonus = .5
1233 1237
1234 1238 op = x[0]
1235 1239 if op == 'minus':
1236 1240 return optimize(('and', x[1], ('not', x[2])), small)
1237 1241 elif op == 'dagrange':
1238 1242 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1239 1243 ('func', ('symbol', 'ancestors'), x[2])), small)
1240 1244 elif op == 'dagrangepre':
1241 1245 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1242 1246 elif op == 'dagrangepost':
1243 1247 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1244 1248 elif op == 'rangepre':
1245 1249 return optimize(('range', ('string', '0'), x[1]), small)
1246 1250 elif op == 'rangepost':
1247 1251 return optimize(('range', x[1], ('string', 'tip')), small)
1248 1252 elif op == 'negate':
1249 1253 return optimize(('string',
1250 1254 '-' + getstring(x[1], _("can't negate that"))), small)
1251 1255 elif op in 'string symbol negate':
1252 1256 return smallbonus, x # single revisions are small
1253 1257 elif op == 'and' or op == 'dagrange':
1254 1258 wa, ta = optimize(x[1], True)
1255 1259 wb, tb = optimize(x[2], True)
1256 1260 w = min(wa, wb)
1257 1261 if wa > wb:
1258 1262 return w, (op, tb, ta)
1259 1263 return w, (op, ta, tb)
1260 1264 elif op == 'or':
1261 1265 wa, ta = optimize(x[1], False)
1262 1266 wb, tb = optimize(x[2], False)
1263 1267 if wb < wa:
1264 1268 wb, wa = wa, wb
1265 1269 return max(wa, wb), (op, ta, tb)
1266 1270 elif op == 'not':
1267 1271 o = optimize(x[1], not small)
1268 1272 return o[0], (op, o[1])
1269 1273 elif op == 'parentpost':
1270 1274 o = optimize(x[1], small)
1271 1275 return o[0], (op, o[1])
1272 1276 elif op == 'group':
1273 1277 return optimize(x[1], small)
1274 1278 elif op in 'range list parent ancestorspec':
1275 1279 if op == 'parent':
1276 1280 # x^:y means (x^) : y, not x ^ (:y)
1277 1281 post = ('parentpost', x[1])
1278 1282 if x[2][0] == 'dagrangepre':
1279 1283 return optimize(('dagrange', post, x[2][1]), small)
1280 1284 elif x[2][0] == 'rangepre':
1281 1285 return optimize(('range', post, x[2][1]), small)
1282 1286
1283 1287 wa, ta = optimize(x[1], small)
1284 1288 wb, tb = optimize(x[2], small)
1285 1289 return wa + wb, (op, ta, tb)
1286 1290 elif op == 'func':
1287 1291 f = getstring(x[1], _("not a symbol"))
1288 1292 wa, ta = optimize(x[2], small)
1289 1293 if f in ("author branch closed date desc file grep keyword "
1290 1294 "outgoing user"):
1291 1295 w = 10 # slow
1292 1296 elif f in "modifies adds removes":
1293 1297 w = 30 # slower
1294 1298 elif f == "contains":
1295 1299 w = 100 # very slow
1296 1300 elif f == "ancestor":
1297 1301 w = 1 * smallbonus
1298 1302 elif f in "reverse limit first":
1299 1303 w = 0
1300 1304 elif f in "sort":
1301 1305 w = 10 # assume most sorts look at changelog
1302 1306 else:
1303 1307 w = 1
1304 1308 return w + wa, (op, x[1], ta)
1305 1309 return 1, x
1306 1310
1307 1311 class revsetalias(object):
1308 1312 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1309 1313 args = None
1310 1314
1311 1315 def __init__(self, name, value):
1312 1316 '''Aliases like:
1313 1317
1314 1318 h = heads(default)
1315 1319 b($1) = ancestors($1) - ancestors(default)
1316 1320 '''
1317 1321 m = self.funcre.search(name)
1318 1322 if m:
1319 1323 self.name = m.group(1)
1320 1324 self.tree = ('func', ('symbol', m.group(1)))
1321 1325 self.args = [x.strip() for x in m.group(2).split(',')]
1322 1326 for arg in self.args:
1323 1327 value = value.replace(arg, repr(arg))
1324 1328 else:
1325 1329 self.name = name
1326 1330 self.tree = ('symbol', name)
1327 1331
1328 1332 self.replacement, pos = parse(value)
1329 1333 if pos != len(value):
1330 1334 raise error.ParseError(_('invalid token'), pos)
1331 1335
1332 1336 def _getalias(aliases, tree):
1333 1337 """If tree looks like an unexpanded alias, return it. Return None
1334 1338 otherwise.
1335 1339 """
1336 1340 if isinstance(tree, tuple) and tree:
1337 1341 if tree[0] == 'symbol' and len(tree) == 2:
1338 1342 name = tree[1]
1339 1343 alias = aliases.get(name)
1340 1344 if alias and alias.args is None and alias.tree == tree:
1341 1345 return alias
1342 1346 if tree[0] == 'func' and len(tree) > 1:
1343 1347 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1344 1348 name = tree[1][1]
1345 1349 alias = aliases.get(name)
1346 1350 if alias and alias.args is not None and alias.tree == tree[:2]:
1347 1351 return alias
1348 1352 return None
1349 1353
1350 1354 def _expandargs(tree, args):
1351 1355 """Replace all occurences of ('string', name) with the
1352 1356 substitution value of the same name in args, recursively.
1353 1357 """
1354 1358 if not isinstance(tree, tuple):
1355 1359 return tree
1356 1360 if len(tree) == 2 and tree[0] == 'string':
1357 1361 return args.get(tree[1], tree)
1358 1362 return tuple(_expandargs(t, args) for t in tree)
1359 1363
1360 1364 def _expandaliases(aliases, tree, expanding):
1361 1365 """Expand aliases in tree, recursively.
1362 1366
1363 1367 'aliases' is a dictionary mapping user defined aliases to
1364 1368 revsetalias objects.
1365 1369 """
1366 1370 if not isinstance(tree, tuple):
1367 1371 # Do not expand raw strings
1368 1372 return tree
1369 1373 alias = _getalias(aliases, tree)
1370 1374 if alias is not None:
1371 1375 if alias in expanding:
1372 1376 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1373 1377 'detected') % alias.name)
1374 1378 expanding.append(alias)
1375 1379 result = alias.replacement
1376 1380 if alias.args is not None:
1377 1381 l = getlist(tree[2])
1378 1382 if len(l) != len(alias.args):
1379 1383 raise error.ParseError(
1380 1384 _('invalid number of arguments: %s') % len(l))
1381 1385 result = _expandargs(result, dict(zip(alias.args, l)))
1382 1386 # Recurse in place, the base expression may have been rewritten
1383 1387 result = _expandaliases(aliases, result, expanding)
1384 1388 expanding.pop()
1385 1389 else:
1386 1390 result = tuple(_expandaliases(aliases, t, expanding)
1387 1391 for t in tree)
1388 1392 return result
1389 1393
1390 1394 def findaliases(ui, tree):
1391 1395 aliases = {}
1392 1396 for k, v in ui.configitems('revsetalias'):
1393 1397 alias = revsetalias(k, v)
1394 1398 aliases[alias.name] = alias
1395 1399 return _expandaliases(aliases, tree, [])
1396 1400
1397 1401 parse = parser.parser(tokenize, elements).parse
1398 1402
1399 1403 def match(ui, spec):
1400 1404 if not spec:
1401 1405 raise error.ParseError(_("empty query"))
1402 1406 tree, pos = parse(spec)
1403 1407 if (pos != len(spec)):
1404 1408 raise error.ParseError(_("invalid token"), pos)
1405 1409 if ui:
1406 1410 tree = findaliases(ui, tree)
1407 1411 weight, tree = optimize(tree, True)
1408 1412 def mfunc(repo, subset):
1409 1413 return getset(repo, subset, tree)
1410 1414 return mfunc
1411 1415
1412 1416 def formatspec(expr, *args):
1413 1417 '''
1414 1418 This is a convenience function for using revsets internally, and
1415 1419 escapes arguments appropriately. Aliases are intentionally ignored
1416 1420 so that intended expression behavior isn't accidentally subverted.
1417 1421
1418 1422 Supported arguments:
1419 1423
1420 1424 %r = revset expression, parenthesized
1421 1425 %d = int(arg), no quoting
1422 1426 %s = string(arg), escaped and single-quoted
1423 1427 %b = arg.branch(), escaped and single-quoted
1424 1428 %n = hex(arg), single-quoted
1425 1429 %% = a literal '%'
1426 1430
1427 1431 Prefixing the type with 'l' specifies a parenthesized list of that type.
1428 1432
1429 1433 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1430 1434 '(10 or 11):: and ((this()) or (that()))'
1431 1435 >>> formatspec('%d:: and not %d::', 10, 20)
1432 1436 '10:: and not 20::'
1433 1437 >>> formatspec('%ld or %ld', [], [1])
1434 1438 "_list('') or 1"
1435 1439 >>> formatspec('keyword(%s)', 'foo\\xe9')
1436 1440 "keyword('foo\\\\xe9')"
1437 1441 >>> b = lambda: 'default'
1438 1442 >>> b.branch = b
1439 1443 >>> formatspec('branch(%b)', b)
1440 1444 "branch('default')"
1441 1445 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1442 1446 "root(_list('a\\x00b\\x00c\\x00d'))"
1443 1447 '''
1444 1448
1445 1449 def quote(s):
1446 1450 return repr(str(s))
1447 1451
1448 1452 def argtype(c, arg):
1449 1453 if c == 'd':
1450 1454 return str(int(arg))
1451 1455 elif c == 's':
1452 1456 return quote(arg)
1453 1457 elif c == 'r':
1454 1458 parse(arg) # make sure syntax errors are confined
1455 1459 return '(%s)' % arg
1456 1460 elif c == 'n':
1457 1461 return quote(node.hex(arg))
1458 1462 elif c == 'b':
1459 1463 return quote(arg.branch())
1460 1464
1461 1465 def listexp(s, t):
1462 1466 l = len(s)
1463 1467 if l == 0:
1464 1468 return "_list('')"
1465 1469 elif l == 1:
1466 1470 return argtype(t, s[0])
1467 1471 elif t == 'd':
1468 1472 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1469 1473 elif t == 's':
1470 1474 return "_list('%s')" % "\0".join(s)
1471 1475 elif t == 'n':
1472 1476 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1473 1477 elif t == 'b':
1474 1478 return "_list('%s')" % "\0".join(a.branch() for a in s)
1475 1479
1476 1480 m = l // 2
1477 1481 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1478 1482
1479 1483 ret = ''
1480 1484 pos = 0
1481 1485 arg = 0
1482 1486 while pos < len(expr):
1483 1487 c = expr[pos]
1484 1488 if c == '%':
1485 1489 pos += 1
1486 1490 d = expr[pos]
1487 1491 if d == '%':
1488 1492 ret += d
1489 1493 elif d in 'dsnbr':
1490 1494 ret += argtype(d, args[arg])
1491 1495 arg += 1
1492 1496 elif d == 'l':
1493 1497 # a list of some type
1494 1498 pos += 1
1495 1499 d = expr[pos]
1496 1500 ret += listexp(list(args[arg]), d)
1497 1501 arg += 1
1498 1502 else:
1499 1503 raise util.Abort('unexpected revspec format character %s' % d)
1500 1504 else:
1501 1505 ret += c
1502 1506 pos += 1
1503 1507
1504 1508 return ret
1505 1509
1506 1510 def prettyformat(tree):
1507 1511 def _prettyformat(tree, level, lines):
1508 1512 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1509 1513 lines.append((level, str(tree)))
1510 1514 else:
1511 1515 lines.append((level, '(%s' % tree[0]))
1512 1516 for s in tree[1:]:
1513 1517 _prettyformat(s, level + 1, lines)
1514 1518 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1515 1519
1516 1520 lines = []
1517 1521 _prettyformat(tree, 0, lines)
1518 1522 output = '\n'.join((' '*l + s) for l, s in lines)
1519 1523 return output
1520 1524
1521 1525 # tell hggettext to extract docstrings from these functions:
1522 1526 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now