##// END OF EJS Templates
revrange: pass repo to revset parser...
Matt Mackall -
r20781:8ecfa225 default
parent child Browse files
Show More
@@ -1,934 +1,934
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 from i18n import _
9 9 from mercurial.node import nullrev
10 10 import util, error, osutil, revset, similar, encoding, phases, parsers
11 11 import pathutil
12 12 import match as matchmod
13 13 import os, errno, re, glob
14 14
15 15 if os.name == 'nt':
16 16 import scmwindows as scmplatform
17 17 else:
18 18 import scmposix as scmplatform
19 19
20 20 systemrcpath = scmplatform.systemrcpath
21 21 userrcpath = scmplatform.userrcpath
22 22
23 23 def itersubrepos(ctx1, ctx2):
24 24 """find subrepos in ctx1 or ctx2"""
25 25 # Create a (subpath, ctx) mapping where we prefer subpaths from
26 26 # ctx1. The subpaths from ctx2 are important when the .hgsub file
27 27 # has been modified (in ctx2) but not yet committed (in ctx1).
28 28 subpaths = dict.fromkeys(ctx2.substate, ctx2)
29 29 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
30 30 for subpath, ctx in sorted(subpaths.iteritems()):
31 31 yield subpath, ctx.sub(subpath)
32 32
33 33 def nochangesfound(ui, repo, excluded=None):
34 34 '''Report no changes for push/pull, excluded is None or a list of
35 35 nodes excluded from the push/pull.
36 36 '''
37 37 secretlist = []
38 38 if excluded:
39 39 for n in excluded:
40 40 if n not in repo:
41 41 # discovery should not have included the filtered revision,
42 42 # we have to explicitly exclude it until discovery is cleanup.
43 43 continue
44 44 ctx = repo[n]
45 45 if ctx.phase() >= phases.secret and not ctx.extinct():
46 46 secretlist.append(n)
47 47
48 48 if secretlist:
49 49 ui.status(_("no changes found (ignored %d secret changesets)\n")
50 50 % len(secretlist))
51 51 else:
52 52 ui.status(_("no changes found\n"))
53 53
54 54 def checknewlabel(repo, lbl, kind):
55 55 # Do not use the "kind" parameter in ui output.
56 56 # It makes strings difficult to translate.
57 57 if lbl in ['tip', '.', 'null']:
58 58 raise util.Abort(_("the name '%s' is reserved") % lbl)
59 59 for c in (':', '\0', '\n', '\r'):
60 60 if c in lbl:
61 61 raise util.Abort(_("%r cannot be used in a name") % c)
62 62 try:
63 63 int(lbl)
64 64 raise util.Abort(_("cannot use an integer as a name"))
65 65 except ValueError:
66 66 pass
67 67
68 68 def checkfilename(f):
69 69 '''Check that the filename f is an acceptable filename for a tracked file'''
70 70 if '\r' in f or '\n' in f:
71 71 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
72 72
73 73 def checkportable(ui, f):
74 74 '''Check if filename f is portable and warn or abort depending on config'''
75 75 checkfilename(f)
76 76 abort, warn = checkportabilityalert(ui)
77 77 if abort or warn:
78 78 msg = util.checkwinfilename(f)
79 79 if msg:
80 80 msg = "%s: %r" % (msg, f)
81 81 if abort:
82 82 raise util.Abort(msg)
83 83 ui.warn(_("warning: %s\n") % msg)
84 84
85 85 def checkportabilityalert(ui):
86 86 '''check if the user's config requests nothing, a warning, or abort for
87 87 non-portable filenames'''
88 88 val = ui.config('ui', 'portablefilenames', 'warn')
89 89 lval = val.lower()
90 90 bval = util.parsebool(val)
91 91 abort = os.name == 'nt' or lval == 'abort'
92 92 warn = bval or lval == 'warn'
93 93 if bval is None and not (warn or abort or lval == 'ignore'):
94 94 raise error.ConfigError(
95 95 _("ui.portablefilenames value is invalid ('%s')") % val)
96 96 return abort, warn
97 97
98 98 class casecollisionauditor(object):
99 99 def __init__(self, ui, abort, dirstate):
100 100 self._ui = ui
101 101 self._abort = abort
102 102 allfiles = '\0'.join(dirstate._map)
103 103 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
104 104 self._dirstate = dirstate
105 105 # The purpose of _newfiles is so that we don't complain about
106 106 # case collisions if someone were to call this object with the
107 107 # same filename twice.
108 108 self._newfiles = set()
109 109
110 110 def __call__(self, f):
111 111 if f in self._newfiles:
112 112 return
113 113 fl = encoding.lower(f)
114 114 if fl in self._loweredfiles and f not in self._dirstate:
115 115 msg = _('possible case-folding collision for %s') % f
116 116 if self._abort:
117 117 raise util.Abort(msg)
118 118 self._ui.warn(_("warning: %s\n") % msg)
119 119 self._loweredfiles.add(fl)
120 120 self._newfiles.add(f)
121 121
122 122 class abstractvfs(object):
123 123 """Abstract base class; cannot be instantiated"""
124 124
125 125 def __init__(self, *args, **kwargs):
126 126 '''Prevent instantiation; don't call this from subclasses.'''
127 127 raise NotImplementedError('attempted instantiating ' + str(type(self)))
128 128
129 129 def tryread(self, path):
130 130 '''gracefully return an empty string for missing files'''
131 131 try:
132 132 return self.read(path)
133 133 except IOError, inst:
134 134 if inst.errno != errno.ENOENT:
135 135 raise
136 136 return ""
137 137
138 138 def open(self, path, mode="r", text=False, atomictemp=False):
139 139 self.open = self.__call__
140 140 return self.__call__(path, mode, text, atomictemp)
141 141
142 142 def read(self, path):
143 143 fp = self(path, 'rb')
144 144 try:
145 145 return fp.read()
146 146 finally:
147 147 fp.close()
148 148
149 149 def write(self, path, data):
150 150 fp = self(path, 'wb')
151 151 try:
152 152 return fp.write(data)
153 153 finally:
154 154 fp.close()
155 155
156 156 def append(self, path, data):
157 157 fp = self(path, 'ab')
158 158 try:
159 159 return fp.write(data)
160 160 finally:
161 161 fp.close()
162 162
163 163 def chmod(self, path, mode):
164 164 return os.chmod(self.join(path), mode)
165 165
166 166 def exists(self, path=None):
167 167 return os.path.exists(self.join(path))
168 168
169 169 def fstat(self, fp):
170 170 return util.fstat(fp)
171 171
172 172 def isdir(self, path=None):
173 173 return os.path.isdir(self.join(path))
174 174
175 175 def isfile(self, path=None):
176 176 return os.path.isfile(self.join(path))
177 177
178 178 def islink(self, path=None):
179 179 return os.path.islink(self.join(path))
180 180
181 181 def lstat(self, path=None):
182 182 return os.lstat(self.join(path))
183 183
184 184 def makedir(self, path=None, notindexed=True):
185 185 return util.makedir(self.join(path), notindexed)
186 186
187 187 def makedirs(self, path=None, mode=None):
188 188 return util.makedirs(self.join(path), mode)
189 189
190 190 def makelock(self, info, path):
191 191 return util.makelock(info, self.join(path))
192 192
193 193 def mkdir(self, path=None):
194 194 return os.mkdir(self.join(path))
195 195
196 196 def readdir(self, path=None, stat=None, skip=None):
197 197 return osutil.listdir(self.join(path), stat, skip)
198 198
199 199 def readlock(self, path):
200 200 return util.readlock(self.join(path))
201 201
202 202 def rename(self, src, dst):
203 203 return util.rename(self.join(src), self.join(dst))
204 204
205 205 def readlink(self, path):
206 206 return os.readlink(self.join(path))
207 207
208 208 def setflags(self, path, l, x):
209 209 return util.setflags(self.join(path), l, x)
210 210
211 211 def stat(self, path=None):
212 212 return os.stat(self.join(path))
213 213
214 214 def unlink(self, path=None):
215 215 return util.unlink(self.join(path))
216 216
217 217 def utime(self, path=None, t=None):
218 218 return os.utime(self.join(path), t)
219 219
220 220 class vfs(abstractvfs):
221 221 '''Operate files relative to a base directory
222 222
223 223 This class is used to hide the details of COW semantics and
224 224 remote file access from higher level code.
225 225 '''
226 226 def __init__(self, base, audit=True, expandpath=False, realpath=False):
227 227 if expandpath:
228 228 base = util.expandpath(base)
229 229 if realpath:
230 230 base = os.path.realpath(base)
231 231 self.base = base
232 232 self._setmustaudit(audit)
233 233 self.createmode = None
234 234 self._trustnlink = None
235 235
236 236 def _getmustaudit(self):
237 237 return self._audit
238 238
239 239 def _setmustaudit(self, onoff):
240 240 self._audit = onoff
241 241 if onoff:
242 242 self.audit = pathutil.pathauditor(self.base)
243 243 else:
244 244 self.audit = util.always
245 245
246 246 mustaudit = property(_getmustaudit, _setmustaudit)
247 247
248 248 @util.propertycache
249 249 def _cansymlink(self):
250 250 return util.checklink(self.base)
251 251
252 252 @util.propertycache
253 253 def _chmod(self):
254 254 return util.checkexec(self.base)
255 255
256 256 def _fixfilemode(self, name):
257 257 if self.createmode is None or not self._chmod:
258 258 return
259 259 os.chmod(name, self.createmode & 0666)
260 260
261 261 def __call__(self, path, mode="r", text=False, atomictemp=False):
262 262 if self._audit:
263 263 r = util.checkosfilename(path)
264 264 if r:
265 265 raise util.Abort("%s: %r" % (r, path))
266 266 self.audit(path)
267 267 f = self.join(path)
268 268
269 269 if not text and "b" not in mode:
270 270 mode += "b" # for that other OS
271 271
272 272 nlink = -1
273 273 if mode not in ('r', 'rb'):
274 274 dirname, basename = util.split(f)
275 275 # If basename is empty, then the path is malformed because it points
276 276 # to a directory. Let the posixfile() call below raise IOError.
277 277 if basename:
278 278 if atomictemp:
279 279 util.ensuredirs(dirname, self.createmode)
280 280 return util.atomictempfile(f, mode, self.createmode)
281 281 try:
282 282 if 'w' in mode:
283 283 util.unlink(f)
284 284 nlink = 0
285 285 else:
286 286 # nlinks() may behave differently for files on Windows
287 287 # shares if the file is open.
288 288 fd = util.posixfile(f)
289 289 nlink = util.nlinks(f)
290 290 if nlink < 1:
291 291 nlink = 2 # force mktempcopy (issue1922)
292 292 fd.close()
293 293 except (OSError, IOError), e:
294 294 if e.errno != errno.ENOENT:
295 295 raise
296 296 nlink = 0
297 297 util.ensuredirs(dirname, self.createmode)
298 298 if nlink > 0:
299 299 if self._trustnlink is None:
300 300 self._trustnlink = nlink > 1 or util.checknlink(f)
301 301 if nlink > 1 or not self._trustnlink:
302 302 util.rename(util.mktempcopy(f), f)
303 303 fp = util.posixfile(f, mode)
304 304 if nlink == 0:
305 305 self._fixfilemode(f)
306 306 return fp
307 307
308 308 def symlink(self, src, dst):
309 309 self.audit(dst)
310 310 linkname = self.join(dst)
311 311 try:
312 312 os.unlink(linkname)
313 313 except OSError:
314 314 pass
315 315
316 316 util.ensuredirs(os.path.dirname(linkname), self.createmode)
317 317
318 318 if self._cansymlink:
319 319 try:
320 320 os.symlink(src, linkname)
321 321 except OSError, err:
322 322 raise OSError(err.errno, _('could not symlink to %r: %s') %
323 323 (src, err.strerror), linkname)
324 324 else:
325 325 self.write(dst, src)
326 326
327 327 def join(self, path):
328 328 if path:
329 329 return os.path.join(self.base, path)
330 330 else:
331 331 return self.base
332 332
333 333 opener = vfs
334 334
335 335 class auditvfs(object):
336 336 def __init__(self, vfs):
337 337 self.vfs = vfs
338 338
339 339 def _getmustaudit(self):
340 340 return self.vfs.mustaudit
341 341
342 342 def _setmustaudit(self, onoff):
343 343 self.vfs.mustaudit = onoff
344 344
345 345 mustaudit = property(_getmustaudit, _setmustaudit)
346 346
347 347 class filtervfs(abstractvfs, auditvfs):
348 348 '''Wrapper vfs for filtering filenames with a function.'''
349 349
350 350 def __init__(self, vfs, filter):
351 351 auditvfs.__init__(self, vfs)
352 352 self._filter = filter
353 353
354 354 def __call__(self, path, *args, **kwargs):
355 355 return self.vfs(self._filter(path), *args, **kwargs)
356 356
357 357 def join(self, path):
358 358 if path:
359 359 return self.vfs.join(self._filter(path))
360 360 else:
361 361 return self.vfs.join(path)
362 362
363 363 filteropener = filtervfs
364 364
365 365 class readonlyvfs(abstractvfs, auditvfs):
366 366 '''Wrapper vfs preventing any writing.'''
367 367
368 368 def __init__(self, vfs):
369 369 auditvfs.__init__(self, vfs)
370 370
371 371 def __call__(self, path, mode='r', *args, **kw):
372 372 if mode not in ('r', 'rb'):
373 373 raise util.Abort('this vfs is read only')
374 374 return self.vfs(path, mode, *args, **kw)
375 375
376 376
377 377 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
378 378 '''yield every hg repository under path, always recursively.
379 379 The recurse flag will only control recursion into repo working dirs'''
380 380 def errhandler(err):
381 381 if err.filename == path:
382 382 raise err
383 383 samestat = getattr(os.path, 'samestat', None)
384 384 if followsym and samestat is not None:
385 385 def adddir(dirlst, dirname):
386 386 match = False
387 387 dirstat = os.stat(dirname)
388 388 for lstdirstat in dirlst:
389 389 if samestat(dirstat, lstdirstat):
390 390 match = True
391 391 break
392 392 if not match:
393 393 dirlst.append(dirstat)
394 394 return not match
395 395 else:
396 396 followsym = False
397 397
398 398 if (seen_dirs is None) and followsym:
399 399 seen_dirs = []
400 400 adddir(seen_dirs, path)
401 401 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
402 402 dirs.sort()
403 403 if '.hg' in dirs:
404 404 yield root # found a repository
405 405 qroot = os.path.join(root, '.hg', 'patches')
406 406 if os.path.isdir(os.path.join(qroot, '.hg')):
407 407 yield qroot # we have a patch queue repo here
408 408 if recurse:
409 409 # avoid recursing inside the .hg directory
410 410 dirs.remove('.hg')
411 411 else:
412 412 dirs[:] = [] # don't descend further
413 413 elif followsym:
414 414 newdirs = []
415 415 for d in dirs:
416 416 fname = os.path.join(root, d)
417 417 if adddir(seen_dirs, fname):
418 418 if os.path.islink(fname):
419 419 for hgname in walkrepos(fname, True, seen_dirs):
420 420 yield hgname
421 421 else:
422 422 newdirs.append(d)
423 423 dirs[:] = newdirs
424 424
425 425 def osrcpath():
426 426 '''return default os-specific hgrc search path'''
427 427 path = systemrcpath()
428 428 path.extend(userrcpath())
429 429 path = [os.path.normpath(f) for f in path]
430 430 return path
431 431
432 432 _rcpath = None
433 433
434 434 def rcpath():
435 435 '''return hgrc search path. if env var HGRCPATH is set, use it.
436 436 for each item in path, if directory, use files ending in .rc,
437 437 else use item.
438 438 make HGRCPATH empty to only look in .hg/hgrc of current repo.
439 439 if no HGRCPATH, use default os-specific path.'''
440 440 global _rcpath
441 441 if _rcpath is None:
442 442 if 'HGRCPATH' in os.environ:
443 443 _rcpath = []
444 444 for p in os.environ['HGRCPATH'].split(os.pathsep):
445 445 if not p:
446 446 continue
447 447 p = util.expandpath(p)
448 448 if os.path.isdir(p):
449 449 for f, kind in osutil.listdir(p):
450 450 if f.endswith('.rc'):
451 451 _rcpath.append(os.path.join(p, f))
452 452 else:
453 453 _rcpath.append(p)
454 454 else:
455 455 _rcpath = osrcpath()
456 456 return _rcpath
457 457
458 458 def revsingle(repo, revspec, default='.'):
459 459 if not revspec and revspec != 0:
460 460 return repo[default]
461 461
462 462 l = revrange(repo, [revspec])
463 463 if len(l) < 1:
464 464 raise util.Abort(_('empty revision set'))
465 465 return repo[l[-1]]
466 466
467 467 def revpair(repo, revs):
468 468 if not revs:
469 469 return repo.dirstate.p1(), None
470 470
471 471 l = revrange(repo, revs)
472 472
473 473 if len(l) == 0:
474 474 if revs:
475 475 raise util.Abort(_('empty revision range'))
476 476 return repo.dirstate.p1(), None
477 477
478 478 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
479 479 return repo.lookup(l[0]), None
480 480
481 481 return repo.lookup(l[0]), repo.lookup(l[-1])
482 482
483 483 _revrangesep = ':'
484 484
485 485 def revrange(repo, revs):
486 486 """Yield revision as strings from a list of revision specifications."""
487 487
488 488 def revfix(repo, val, defval):
489 489 if not val and val != 0 and defval is not None:
490 490 return defval
491 491 return repo[val].rev()
492 492
493 493 seen, l = set(), revset.baseset([])
494 494 for spec in revs:
495 495 if l and not seen:
496 496 seen = set(l)
497 497 # attempt to parse old-style ranges first to deal with
498 498 # things like old-tag which contain query metacharacters
499 499 try:
500 500 if isinstance(spec, int):
501 501 seen.add(spec)
502 502 l = l + [spec]
503 503 continue
504 504
505 505 if _revrangesep in spec:
506 506 start, end = spec.split(_revrangesep, 1)
507 507 start = revfix(repo, start, 0)
508 508 end = revfix(repo, end, len(repo) - 1)
509 509 if end == nullrev and start < 0:
510 510 start = nullrev
511 511 rangeiter = repo.changelog.revs(start, end)
512 512 if not seen and not l:
513 513 # by far the most common case: revs = ["-1:0"]
514 514 l = revset.baseset(rangeiter)
515 515 # defer syncing seen until next iteration
516 516 continue
517 517 newrevs = set(rangeiter)
518 518 if seen:
519 519 newrevs.difference_update(seen)
520 520 seen.update(newrevs)
521 521 else:
522 522 seen = newrevs
523 523 l = l + sorted(newrevs, reverse=start > end)
524 524 continue
525 525 elif spec and spec in repo: # single unquoted rev
526 526 rev = revfix(repo, spec, None)
527 527 if rev in seen:
528 528 continue
529 529 seen.add(rev)
530 530 l = l + [rev]
531 531 continue
532 532 except error.RepoLookupError:
533 533 pass
534 534
535 535 # fall through to new-style queries if old-style fails
536 m = revset.match(repo.ui, spec)
536 m = revset.match(repo.ui, spec, repo)
537 537 if seen or l:
538 538 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
539 539 l = l + dl
540 540 seen.update(dl)
541 541 else:
542 542 l = m(repo, revset.spanset(repo))
543 543
544 544 return l
545 545
546 546 def expandpats(pats):
547 547 if not util.expandglobs:
548 548 return list(pats)
549 549 ret = []
550 550 for p in pats:
551 551 kind, name = matchmod._patsplit(p, None)
552 552 if kind is None:
553 553 try:
554 554 globbed = glob.glob(name)
555 555 except re.error:
556 556 globbed = [name]
557 557 if globbed:
558 558 ret.extend(globbed)
559 559 continue
560 560 ret.append(p)
561 561 return ret
562 562
563 563 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
564 564 if pats == ("",):
565 565 pats = []
566 566 if not globbed and default == 'relpath':
567 567 pats = expandpats(pats or [])
568 568
569 569 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
570 570 default)
571 571 def badfn(f, msg):
572 572 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
573 573 m.bad = badfn
574 574 return m, pats
575 575
576 576 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
577 577 return matchandpats(ctx, pats, opts, globbed, default)[0]
578 578
579 579 def matchall(repo):
580 580 return matchmod.always(repo.root, repo.getcwd())
581 581
582 582 def matchfiles(repo, files):
583 583 return matchmod.exact(repo.root, repo.getcwd(), files)
584 584
585 585 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
586 586 if dry_run is None:
587 587 dry_run = opts.get('dry_run')
588 588 if similarity is None:
589 589 similarity = float(opts.get('similarity') or 0)
590 590 # we'd use status here, except handling of symlinks and ignore is tricky
591 591 m = match(repo[None], pats, opts)
592 592 rejected = []
593 593 m.bad = lambda x, y: rejected.append(x)
594 594
595 595 added, unknown, deleted, removed = _interestingfiles(repo, m)
596 596
597 597 unknownset = set(unknown)
598 598 toprint = unknownset.copy()
599 599 toprint.update(deleted)
600 600 for abs in sorted(toprint):
601 601 if repo.ui.verbose or not m.exact(abs):
602 602 rel = m.rel(abs)
603 603 if abs in unknownset:
604 604 status = _('adding %s\n') % ((pats and rel) or abs)
605 605 else:
606 606 status = _('removing %s\n') % ((pats and rel) or abs)
607 607 repo.ui.status(status)
608 608
609 609 renames = _findrenames(repo, m, added + unknown, removed + deleted,
610 610 similarity)
611 611
612 612 if not dry_run:
613 613 _markchanges(repo, unknown, deleted, renames)
614 614
615 615 for f in rejected:
616 616 if f in m.files():
617 617 return 1
618 618 return 0
619 619
620 620 def marktouched(repo, files, similarity=0.0):
621 621 '''Assert that files have somehow been operated upon. files are relative to
622 622 the repo root.'''
623 623 m = matchfiles(repo, files)
624 624 rejected = []
625 625 m.bad = lambda x, y: rejected.append(x)
626 626
627 627 added, unknown, deleted, removed = _interestingfiles(repo, m)
628 628
629 629 if repo.ui.verbose:
630 630 unknownset = set(unknown)
631 631 toprint = unknownset.copy()
632 632 toprint.update(deleted)
633 633 for abs in sorted(toprint):
634 634 if abs in unknownset:
635 635 status = _('adding %s\n') % abs
636 636 else:
637 637 status = _('removing %s\n') % abs
638 638 repo.ui.status(status)
639 639
640 640 renames = _findrenames(repo, m, added + unknown, removed + deleted,
641 641 similarity)
642 642
643 643 _markchanges(repo, unknown, deleted, renames)
644 644
645 645 for f in rejected:
646 646 if f in m.files():
647 647 return 1
648 648 return 0
649 649
650 650 def _interestingfiles(repo, matcher):
651 651 '''Walk dirstate with matcher, looking for files that addremove would care
652 652 about.
653 653
654 654 This is different from dirstate.status because it doesn't care about
655 655 whether files are modified or clean.'''
656 656 added, unknown, deleted, removed = [], [], [], []
657 657 audit_path = pathutil.pathauditor(repo.root)
658 658
659 659 ctx = repo[None]
660 660 dirstate = repo.dirstate
661 661 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
662 662 full=False)
663 663 for abs, st in walkresults.iteritems():
664 664 dstate = dirstate[abs]
665 665 if dstate == '?' and audit_path.check(abs):
666 666 unknown.append(abs)
667 667 elif dstate != 'r' and not st:
668 668 deleted.append(abs)
669 669 # for finding renames
670 670 elif dstate == 'r':
671 671 removed.append(abs)
672 672 elif dstate == 'a':
673 673 added.append(abs)
674 674
675 675 return added, unknown, deleted, removed
676 676
677 677 def _findrenames(repo, matcher, added, removed, similarity):
678 678 '''Find renames from removed files to added ones.'''
679 679 renames = {}
680 680 if similarity > 0:
681 681 for old, new, score in similar.findrenames(repo, added, removed,
682 682 similarity):
683 683 if (repo.ui.verbose or not matcher.exact(old)
684 684 or not matcher.exact(new)):
685 685 repo.ui.status(_('recording removal of %s as rename to %s '
686 686 '(%d%% similar)\n') %
687 687 (matcher.rel(old), matcher.rel(new),
688 688 score * 100))
689 689 renames[new] = old
690 690 return renames
691 691
692 692 def _markchanges(repo, unknown, deleted, renames):
693 693 '''Marks the files in unknown as added, the files in deleted as removed,
694 694 and the files in renames as copied.'''
695 695 wctx = repo[None]
696 696 wlock = repo.wlock()
697 697 try:
698 698 wctx.forget(deleted)
699 699 wctx.add(unknown)
700 700 for new, old in renames.iteritems():
701 701 wctx.copy(old, new)
702 702 finally:
703 703 wlock.release()
704 704
705 705 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
706 706 """Update the dirstate to reflect the intent of copying src to dst. For
707 707 different reasons it might not end with dst being marked as copied from src.
708 708 """
709 709 origsrc = repo.dirstate.copied(src) or src
710 710 if dst == origsrc: # copying back a copy?
711 711 if repo.dirstate[dst] not in 'mn' and not dryrun:
712 712 repo.dirstate.normallookup(dst)
713 713 else:
714 714 if repo.dirstate[origsrc] == 'a' and origsrc == src:
715 715 if not ui.quiet:
716 716 ui.warn(_("%s has not been committed yet, so no copy "
717 717 "data will be stored for %s.\n")
718 718 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
719 719 if repo.dirstate[dst] in '?r' and not dryrun:
720 720 wctx.add([dst])
721 721 elif not dryrun:
722 722 wctx.copy(origsrc, dst)
723 723
724 724 def readrequires(opener, supported):
725 725 '''Reads and parses .hg/requires and checks if all entries found
726 726 are in the list of supported features.'''
727 727 requirements = set(opener.read("requires").splitlines())
728 728 missings = []
729 729 for r in requirements:
730 730 if r not in supported:
731 731 if not r or not r[0].isalnum():
732 732 raise error.RequirementError(_(".hg/requires file is corrupt"))
733 733 missings.append(r)
734 734 missings.sort()
735 735 if missings:
736 736 raise error.RequirementError(
737 737 _("unknown repository format: requires features '%s' (upgrade "
738 738 "Mercurial)") % "', '".join(missings),
739 739 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
740 740 " for details"))
741 741 return requirements
742 742
743 743 class filecachesubentry(object):
744 744 def __init__(self, path, stat):
745 745 self.path = path
746 746 self.cachestat = None
747 747 self._cacheable = None
748 748
749 749 if stat:
750 750 self.cachestat = filecachesubentry.stat(self.path)
751 751
752 752 if self.cachestat:
753 753 self._cacheable = self.cachestat.cacheable()
754 754 else:
755 755 # None means we don't know yet
756 756 self._cacheable = None
757 757
758 758 def refresh(self):
759 759 if self.cacheable():
760 760 self.cachestat = filecachesubentry.stat(self.path)
761 761
762 762 def cacheable(self):
763 763 if self._cacheable is not None:
764 764 return self._cacheable
765 765
766 766 # we don't know yet, assume it is for now
767 767 return True
768 768
769 769 def changed(self):
770 770 # no point in going further if we can't cache it
771 771 if not self.cacheable():
772 772 return True
773 773
774 774 newstat = filecachesubentry.stat(self.path)
775 775
776 776 # we may not know if it's cacheable yet, check again now
777 777 if newstat and self._cacheable is None:
778 778 self._cacheable = newstat.cacheable()
779 779
780 780 # check again
781 781 if not self._cacheable:
782 782 return True
783 783
784 784 if self.cachestat != newstat:
785 785 self.cachestat = newstat
786 786 return True
787 787 else:
788 788 return False
789 789
790 790 @staticmethod
791 791 def stat(path):
792 792 try:
793 793 return util.cachestat(path)
794 794 except OSError, e:
795 795 if e.errno != errno.ENOENT:
796 796 raise
797 797
798 798 class filecacheentry(object):
799 799 def __init__(self, paths, stat=True):
800 800 self._entries = []
801 801 for path in paths:
802 802 self._entries.append(filecachesubentry(path, stat))
803 803
804 804 def changed(self):
805 805 '''true if any entry has changed'''
806 806 for entry in self._entries:
807 807 if entry.changed():
808 808 return True
809 809 return False
810 810
811 811 def refresh(self):
812 812 for entry in self._entries:
813 813 entry.refresh()
814 814
815 815 class filecache(object):
816 816 '''A property like decorator that tracks files under .hg/ for updates.
817 817
818 818 Records stat info when called in _filecache.
819 819
820 820 On subsequent calls, compares old stat info with new info, and recreates the
821 821 object when any of the files changes, updating the new stat info in
822 822 _filecache.
823 823
824 824 Mercurial either atomic renames or appends for files under .hg,
825 825 so to ensure the cache is reliable we need the filesystem to be able
826 826 to tell us if a file has been replaced. If it can't, we fallback to
827 827 recreating the object on every call (essentially the same behaviour as
828 828 propertycache).
829 829
830 830 '''
831 831 def __init__(self, *paths):
832 832 self.paths = paths
833 833
834 834 def join(self, obj, fname):
835 835 """Used to compute the runtime path of a cached file.
836 836
837 837 Users should subclass filecache and provide their own version of this
838 838 function to call the appropriate join function on 'obj' (an instance
839 839 of the class that its member function was decorated).
840 840 """
841 841 return obj.join(fname)
842 842
843 843 def __call__(self, func):
844 844 self.func = func
845 845 self.name = func.__name__
846 846 return self
847 847
848 848 def __get__(self, obj, type=None):
849 849 # do we need to check if the file changed?
850 850 if self.name in obj.__dict__:
851 851 assert self.name in obj._filecache, self.name
852 852 return obj.__dict__[self.name]
853 853
854 854 entry = obj._filecache.get(self.name)
855 855
856 856 if entry:
857 857 if entry.changed():
858 858 entry.obj = self.func(obj)
859 859 else:
860 860 paths = [self.join(obj, path) for path in self.paths]
861 861
862 862 # We stat -before- creating the object so our cache doesn't lie if
863 863 # a writer modified between the time we read and stat
864 864 entry = filecacheentry(paths, True)
865 865 entry.obj = self.func(obj)
866 866
867 867 obj._filecache[self.name] = entry
868 868
869 869 obj.__dict__[self.name] = entry.obj
870 870 return entry.obj
871 871
872 872 def __set__(self, obj, value):
873 873 if self.name not in obj._filecache:
874 874 # we add an entry for the missing value because X in __dict__
875 875 # implies X in _filecache
876 876 paths = [self.join(obj, path) for path in self.paths]
877 877 ce = filecacheentry(paths, False)
878 878 obj._filecache[self.name] = ce
879 879 else:
880 880 ce = obj._filecache[self.name]
881 881
882 882 ce.obj = value # update cached copy
883 883 obj.__dict__[self.name] = value # update copy returned by obj.x
884 884
885 885 def __delete__(self, obj):
886 886 try:
887 887 del obj.__dict__[self.name]
888 888 except KeyError:
889 889 raise AttributeError(self.name)
890 890
891 891 class dirs(object):
892 892 '''a multiset of directory names from a dirstate or manifest'''
893 893
894 894 def __init__(self, map, skip=None):
895 895 self._dirs = {}
896 896 addpath = self.addpath
897 897 if util.safehasattr(map, 'iteritems') and skip is not None:
898 898 for f, s in map.iteritems():
899 899 if s[0] != skip:
900 900 addpath(f)
901 901 else:
902 902 for f in map:
903 903 addpath(f)
904 904
905 905 def addpath(self, path):
906 906 dirs = self._dirs
907 907 for base in finddirs(path):
908 908 if base in dirs:
909 909 dirs[base] += 1
910 910 return
911 911 dirs[base] = 1
912 912
913 913 def delpath(self, path):
914 914 dirs = self._dirs
915 915 for base in finddirs(path):
916 916 if dirs[base] > 1:
917 917 dirs[base] -= 1
918 918 return
919 919 del dirs[base]
920 920
921 921 def __iter__(self):
922 922 return self._dirs.iterkeys()
923 923
924 924 def __contains__(self, d):
925 925 return d in self._dirs
926 926
927 927 if util.safehasattr(parsers, 'dirs'):
928 928 dirs = parsers.dirs
929 929
930 930 def finddirs(path):
931 931 pos = path.rfind('/')
932 932 while pos != -1:
933 933 yield path[:pos]
934 934 pos = path.rfind('/', 0, pos)
@@ -1,1043 +1,1051
1 1 $ HGENCODING=utf-8
2 2 $ export HGENCODING
3 3
4 4 $ try() {
5 5 > hg debugrevspec --debug "$@"
6 6 > }
7 7
8 8 $ log() {
9 9 > hg log --template '{rev}\n' -r "$1"
10 10 > }
11 11
12 12 $ hg init repo
13 13 $ cd repo
14 14
15 15 $ echo a > a
16 16 $ hg branch a
17 17 marked working directory as branch a
18 18 (branches are permanent and global, did you want a bookmark?)
19 19 $ hg ci -Aqm0
20 20
21 21 $ echo b > b
22 22 $ hg branch b
23 23 marked working directory as branch b
24 24 (branches are permanent and global, did you want a bookmark?)
25 25 $ hg ci -Aqm1
26 26
27 27 $ rm a
28 28 $ hg branch a-b-c-
29 29 marked working directory as branch a-b-c-
30 30 (branches are permanent and global, did you want a bookmark?)
31 31 $ hg ci -Aqm2 -u Bob
32 32
33 33 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
34 34 2
35 35 $ hg log -r "extra('branch')" --template '{rev}\n'
36 36 0
37 37 1
38 38 2
39 39 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
40 40 0 a
41 41 2 a-b-c-
42 42
43 43 $ hg co 1
44 44 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 45 $ hg branch +a+b+c+
46 46 marked working directory as branch +a+b+c+
47 47 (branches are permanent and global, did you want a bookmark?)
48 48 $ hg ci -Aqm3
49 49
50 50 $ hg co 2 # interleave
51 51 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
52 52 $ echo bb > b
53 53 $ hg branch -- -a-b-c-
54 54 marked working directory as branch -a-b-c-
55 55 (branches are permanent and global, did you want a bookmark?)
56 56 $ hg ci -Aqm4 -d "May 12 2005"
57 57
58 58 $ hg co 3
59 59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 60 $ hg branch !a/b/c/
61 61 marked working directory as branch !a/b/c/
62 62 (branches are permanent and global, did you want a bookmark?)
63 63 $ hg ci -Aqm"5 bug"
64 64
65 65 $ hg merge 4
66 66 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
67 67 (branch merge, don't forget to commit)
68 68 $ hg branch _a_b_c_
69 69 marked working directory as branch _a_b_c_
70 70 (branches are permanent and global, did you want a bookmark?)
71 71 $ hg ci -Aqm"6 issue619"
72 72
73 73 $ hg branch .a.b.c.
74 74 marked working directory as branch .a.b.c.
75 75 (branches are permanent and global, did you want a bookmark?)
76 76 $ hg ci -Aqm7
77 77
78 78 $ hg branch all
79 79 marked working directory as branch all
80 80 (branches are permanent and global, did you want a bookmark?)
81 81
82 82 $ hg co 4
83 83 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 84 $ hg branch Γ©
85 85 marked working directory as branch \xc3\xa9 (esc)
86 86 (branches are permanent and global, did you want a bookmark?)
87 87 $ hg ci -Aqm9
88 88
89 89 $ hg tag -r6 1.0
90 90
91 91 $ hg clone --quiet -U -r 7 . ../remote1
92 92 $ hg clone --quiet -U -r 8 . ../remote2
93 93 $ echo "[paths]" >> .hg/hgrc
94 94 $ echo "default = ../remote1" >> .hg/hgrc
95 95
96 96 names that should work without quoting
97 97
98 98 $ try a
99 99 ('symbol', 'a')
100 100 0
101 101 $ try b-a
102 102 (minus
103 103 ('symbol', 'b')
104 104 ('symbol', 'a'))
105 105 1
106 106 $ try _a_b_c_
107 107 ('symbol', '_a_b_c_')
108 108 6
109 109 $ try _a_b_c_-a
110 110 (minus
111 111 ('symbol', '_a_b_c_')
112 112 ('symbol', 'a'))
113 113 6
114 114 $ try .a.b.c.
115 115 ('symbol', '.a.b.c.')
116 116 7
117 117 $ try .a.b.c.-a
118 118 (minus
119 119 ('symbol', '.a.b.c.')
120 120 ('symbol', 'a'))
121 121 7
122 122 $ try -- '-a-b-c-' # complains
123 123 hg: parse error at 7: not a prefix: end
124 124 [255]
125 125 $ log -a-b-c- # succeeds with fallback
126 126 4
127
127 128 $ try -- -a-b-c--a # complains
128 129 (minus
129 130 (minus
130 131 (minus
131 132 (negate
132 133 ('symbol', 'a'))
133 134 ('symbol', 'b'))
134 135 ('symbol', 'c'))
135 136 (negate
136 137 ('symbol', 'a')))
137 138 abort: unknown revision '-a'!
138 139 [255]
139 140 $ try Γ©
140 141 ('symbol', '\xc3\xa9')
141 142 9
142 143
144 no quoting needed
145
146 $ log ::a-b-c-
147 0
148 1
149 2
150
143 151 quoting needed
144 152
145 153 $ try '"-a-b-c-"-a'
146 154 (minus
147 155 ('string', '-a-b-c-')
148 156 ('symbol', 'a'))
149 157 4
150 158
151 159 $ log '1 or 2'
152 160 1
153 161 2
154 162 $ log '1|2'
155 163 1
156 164 2
157 165 $ log '1 and 2'
158 166 $ log '1&2'
159 167 $ try '1&2|3' # precedence - and is higher
160 168 (or
161 169 (and
162 170 ('symbol', '1')
163 171 ('symbol', '2'))
164 172 ('symbol', '3'))
165 173 3
166 174 $ try '1|2&3'
167 175 (or
168 176 ('symbol', '1')
169 177 (and
170 178 ('symbol', '2')
171 179 ('symbol', '3')))
172 180 1
173 181 $ try '1&2&3' # associativity
174 182 (and
175 183 (and
176 184 ('symbol', '1')
177 185 ('symbol', '2'))
178 186 ('symbol', '3'))
179 187 $ try '1|(2|3)'
180 188 (or
181 189 ('symbol', '1')
182 190 (group
183 191 (or
184 192 ('symbol', '2')
185 193 ('symbol', '3'))))
186 194 1
187 195 2
188 196 3
189 197 $ log '1.0' # tag
190 198 6
191 199 $ log 'a' # branch
192 200 0
193 201 $ log '2785f51ee'
194 202 0
195 203 $ log 'date(2005)'
196 204 4
197 205 $ log 'date(this is a test)'
198 206 hg: parse error at 10: unexpected token: symbol
199 207 [255]
200 208 $ log 'date()'
201 209 hg: parse error: date requires a string
202 210 [255]
203 211 $ log 'date'
204 212 hg: parse error: can't use date here
205 213 [255]
206 214 $ log 'date('
207 215 hg: parse error at 5: not a prefix: end
208 216 [255]
209 217 $ log 'date(tip)'
210 218 abort: invalid date: 'tip'
211 219 [255]
212 220 $ log '"date"'
213 221 abort: unknown revision 'date'!
214 222 [255]
215 223 $ log 'date(2005) and 1::'
216 224 4
217 225
218 226 ancestor can accept 0 or more arguments
219 227
220 228 $ log 'ancestor()'
221 229 $ log 'ancestor(1)'
222 230 1
223 231 $ log 'ancestor(4,5)'
224 232 1
225 233 $ log 'ancestor(4,5) and 4'
226 234 $ log 'ancestor(0,0,1,3)'
227 235 0
228 236 $ log 'ancestor(3,1,5,3,5,1)'
229 237 1
230 238 $ log 'ancestor(0,1,3,5)'
231 239 0
232 240 $ log 'ancestor(1,2,3,4,5)'
233 241 1
234 242 $ log 'ancestors(5)'
235 243 0
236 244 1
237 245 3
238 246 5
239 247 $ log 'ancestor(ancestors(5))'
240 248 0
241 249 $ log 'author(bob)'
242 250 2
243 251 $ log 'author("re:bob|test")'
244 252 0
245 253 1
246 254 2
247 255 3
248 256 4
249 257 5
250 258 6
251 259 7
252 260 8
253 261 9
254 262 $ log 'branch(Γ©)'
255 263 8
256 264 9
257 265 $ log 'branch(a)'
258 266 0
259 267 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
260 268 0 a
261 269 2 a-b-c-
262 270 3 +a+b+c+
263 271 4 -a-b-c-
264 272 5 !a/b/c/
265 273 6 _a_b_c_
266 274 7 .a.b.c.
267 275 $ log 'children(ancestor(4,5))'
268 276 2
269 277 3
270 278 $ log 'closed()'
271 279 $ log 'contains(a)'
272 280 0
273 281 1
274 282 3
275 283 5
276 284 $ log 'contains("../repo/a")'
277 285 0
278 286 1
279 287 3
280 288 5
281 289 $ log 'desc(B)'
282 290 5
283 291 $ log 'descendants(2 or 3)'
284 292 2
285 293 3
286 294 4
287 295 5
288 296 6
289 297 7
290 298 8
291 299 9
292 300 $ log 'file("b*")'
293 301 1
294 302 4
295 303 $ log 'filelog("b")'
296 304 1
297 305 4
298 306 $ log 'filelog("../repo/b")'
299 307 1
300 308 4
301 309 $ log 'follow()'
302 310 0
303 311 1
304 312 2
305 313 4
306 314 8
307 315 9
308 316 $ log 'grep("issue\d+")'
309 317 6
310 318 $ try 'grep("(")' # invalid regular expression
311 319 (func
312 320 ('symbol', 'grep')
313 321 ('string', '('))
314 322 hg: parse error: invalid match pattern: unbalanced parenthesis
315 323 [255]
316 324 $ try 'grep("\bissue\d+")'
317 325 (func
318 326 ('symbol', 'grep')
319 327 ('string', '\x08issue\\d+'))
320 328 $ try 'grep(r"\bissue\d+")'
321 329 (func
322 330 ('symbol', 'grep')
323 331 ('string', '\\bissue\\d+'))
324 332 6
325 333 $ try 'grep(r"\")'
326 334 hg: parse error at 7: unterminated string
327 335 [255]
328 336 $ log 'head()'
329 337 0
330 338 1
331 339 2
332 340 3
333 341 4
334 342 5
335 343 6
336 344 7
337 345 9
338 346 $ log 'heads(6::)'
339 347 7
340 348 $ log 'keyword(issue)'
341 349 6
342 350 $ log 'keyword("test a")'
343 351 $ log 'limit(head(), 1)'
344 352 0
345 353 $ log 'matching(6)'
346 354 6
347 355 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
348 356 6
349 357 7
350 358 $ log 'max(contains(a))'
351 359 5
352 360 $ log 'min(contains(a))'
353 361 0
354 362 $ log 'merge()'
355 363 6
356 364 $ log 'branchpoint()'
357 365 1
358 366 4
359 367 $ log 'modifies(b)'
360 368 4
361 369 $ log 'modifies("path:b")'
362 370 4
363 371 $ log 'modifies("*")'
364 372 4
365 373 6
366 374 $ log 'modifies("set:modified()")'
367 375 4
368 376 $ log 'id(5)'
369 377 2
370 378 $ log 'only(9)'
371 379 8
372 380 9
373 381 $ log 'only(8)'
374 382 8
375 383 $ log 'only(9, 5)'
376 384 2
377 385 4
378 386 8
379 387 9
380 388 $ log 'only(7 + 9, 5 + 2)'
381 389 4
382 390 6
383 391 7
384 392 8
385 393 9
386 394 $ log 'outgoing()'
387 395 8
388 396 9
389 397 $ log 'outgoing("../remote1")'
390 398 8
391 399 9
392 400 $ log 'outgoing("../remote2")'
393 401 3
394 402 5
395 403 6
396 404 7
397 405 9
398 406 $ log 'p1(merge())'
399 407 5
400 408 $ log 'p2(merge())'
401 409 4
402 410 $ log 'parents(merge())'
403 411 4
404 412 5
405 413 $ log 'p1(branchpoint())'
406 414 0
407 415 2
408 416 $ log 'p2(branchpoint())'
409 417 $ log 'parents(branchpoint())'
410 418 0
411 419 2
412 420 $ log 'removes(a)'
413 421 2
414 422 6
415 423 $ log 'roots(all())'
416 424 0
417 425 $ log 'reverse(2 or 3 or 4 or 5)'
418 426 5
419 427 4
420 428 3
421 429 2
422 430 $ log 'reverse(all())'
423 431 9
424 432 8
425 433 7
426 434 6
427 435 5
428 436 4
429 437 3
430 438 2
431 439 1
432 440 0
433 441 $ log '1:: and reverse(all())'
434 442 9
435 443 8
436 444 7
437 445 6
438 446 5
439 447 4
440 448 3
441 449 2
442 450 1
443 451 $ log 'rev(5)'
444 452 5
445 453 $ log 'sort(limit(reverse(all()), 3))'
446 454 7
447 455 8
448 456 9
449 457 $ log 'sort(2 or 3 or 4 or 5, date)'
450 458 2
451 459 3
452 460 5
453 461 4
454 462 $ log 'tagged()'
455 463 6
456 464 $ log 'tag()'
457 465 6
458 466 $ log 'tag(1.0)'
459 467 6
460 468 $ log 'tag(tip)'
461 469 9
462 470
463 471 test sort revset
464 472 --------------------------------------------
465 473
466 474 test when adding two unordered revsets
467 475
468 476 $ log 'sort(keyword(issue) or modifies(b))'
469 477 4
470 478 6
471 479
472 480 test when sorting a reversed collection in the same way it is
473 481
474 482 $ log 'sort(reverse(all()), -rev)'
475 483 9
476 484 8
477 485 7
478 486 6
479 487 5
480 488 4
481 489 3
482 490 2
483 491 1
484 492 0
485 493
486 494 test when sorting a reversed collection
487 495
488 496 $ log 'sort(reverse(all()), rev)'
489 497 0
490 498 1
491 499 2
492 500 3
493 501 4
494 502 5
495 503 6
496 504 7
497 505 8
498 506 9
499 507
500 508
501 509 test sorting two sorted collections in different orders
502 510
503 511 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
504 512 2
505 513 6
506 514 8
507 515 9
508 516
509 517 test sorting two sorted collections in different orders backwards
510 518
511 519 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
512 520 9
513 521 8
514 522 6
515 523 2
516 524
517 525 test substracting something from an addset
518 526
519 527 $ log '(outgoing() or removes(a)) - removes(a)'
520 528 8
521 529 9
522 530
523 531 test intersecting something with an addset
524 532
525 533 $ log 'parents(outgoing() or removes(a))'
526 534 1
527 535 4
528 536 5
529 537 8
530 538
531 539 check that conversion to _missingancestors works
532 540 $ try --optimize '::3 - ::1'
533 541 (minus
534 542 (dagrangepre
535 543 ('symbol', '3'))
536 544 (dagrangepre
537 545 ('symbol', '1')))
538 546 * optimized:
539 547 (func
540 548 ('symbol', '_missingancestors')
541 549 (list
542 550 ('symbol', '3')
543 551 ('symbol', '1')))
544 552 3
545 553 $ try --optimize 'ancestors(1) - ancestors(3)'
546 554 (minus
547 555 (func
548 556 ('symbol', 'ancestors')
549 557 ('symbol', '1'))
550 558 (func
551 559 ('symbol', 'ancestors')
552 560 ('symbol', '3')))
553 561 * optimized:
554 562 (func
555 563 ('symbol', '_missingancestors')
556 564 (list
557 565 ('symbol', '1')
558 566 ('symbol', '3')))
559 567 $ try --optimize 'not ::2 and ::6'
560 568 (and
561 569 (not
562 570 (dagrangepre
563 571 ('symbol', '2')))
564 572 (dagrangepre
565 573 ('symbol', '6')))
566 574 * optimized:
567 575 (func
568 576 ('symbol', '_missingancestors')
569 577 (list
570 578 ('symbol', '6')
571 579 ('symbol', '2')))
572 580 3
573 581 4
574 582 5
575 583 6
576 584 $ try --optimize 'ancestors(6) and not ancestors(4)'
577 585 (and
578 586 (func
579 587 ('symbol', 'ancestors')
580 588 ('symbol', '6'))
581 589 (not
582 590 (func
583 591 ('symbol', 'ancestors')
584 592 ('symbol', '4'))))
585 593 * optimized:
586 594 (func
587 595 ('symbol', '_missingancestors')
588 596 (list
589 597 ('symbol', '6')
590 598 ('symbol', '4')))
591 599 3
592 600 5
593 601 6
594 602
595 603 we can use patterns when searching for tags
596 604
597 605 $ log 'tag("1..*")'
598 606 abort: tag '1..*' does not exist
599 607 [255]
600 608 $ log 'tag("re:1..*")'
601 609 6
602 610 $ log 'tag("re:[0-9].[0-9]")'
603 611 6
604 612 $ log 'tag("literal:1.0")'
605 613 6
606 614 $ log 'tag("re:0..*")'
607 615
608 616 $ log 'tag(unknown)'
609 617 abort: tag 'unknown' does not exist
610 618 [255]
611 619 $ log 'branch(unknown)'
612 620 abort: unknown revision 'unknown'!
613 621 [255]
614 622 $ log 'user(bob)'
615 623 2
616 624
617 625 $ log '4::8'
618 626 4
619 627 8
620 628 $ log '4:8'
621 629 4
622 630 5
623 631 6
624 632 7
625 633 8
626 634
627 635 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
628 636 4
629 637 2
630 638 5
631 639
632 640 $ log 'not 0 and 0:2'
633 641 1
634 642 2
635 643 $ log 'not 1 and 0:2'
636 644 0
637 645 2
638 646 $ log 'not 2 and 0:2'
639 647 0
640 648 1
641 649 $ log '(1 and 2)::'
642 650 $ log '(1 and 2):'
643 651 $ log '(1 and 2):3'
644 652 $ log 'sort(head(), -rev)'
645 653 9
646 654 7
647 655 6
648 656 5
649 657 4
650 658 3
651 659 2
652 660 1
653 661 0
654 662 $ log '4::8 - 8'
655 663 4
656 664 $ log 'matching(1 or 2 or 3) and (2 or 3 or 1)'
657 665 2
658 666 3
659 667 1
660 668
661 669 issue2437
662 670
663 671 $ log '3 and p1(5)'
664 672 3
665 673 $ log '4 and p2(6)'
666 674 4
667 675 $ log '1 and parents(:2)'
668 676 1
669 677 $ log '2 and children(1:)'
670 678 2
671 679 $ log 'roots(all()) or roots(all())'
672 680 0
673 681 $ hg debugrevspec 'roots(all()) or roots(all())'
674 682 0
675 683 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
676 684 9
677 685 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
678 686 4
679 687
680 688 issue2654: report a parse error if the revset was not completely parsed
681 689
682 690 $ log '1 OR 2'
683 691 hg: parse error at 2: invalid token
684 692 [255]
685 693
686 694 or operator should preserve ordering:
687 695 $ log 'reverse(2::4) or tip'
688 696 4
689 697 2
690 698 9
691 699
692 700 parentrevspec
693 701
694 702 $ log 'merge()^0'
695 703 6
696 704 $ log 'merge()^'
697 705 5
698 706 $ log 'merge()^1'
699 707 5
700 708 $ log 'merge()^2'
701 709 4
702 710 $ log 'merge()^^'
703 711 3
704 712 $ log 'merge()^1^'
705 713 3
706 714 $ log 'merge()^^^'
707 715 1
708 716
709 717 $ log 'merge()~0'
710 718 6
711 719 $ log 'merge()~1'
712 720 5
713 721 $ log 'merge()~2'
714 722 3
715 723 $ log 'merge()~2^1'
716 724 1
717 725 $ log 'merge()~3'
718 726 1
719 727
720 728 $ log '(-3:tip)^'
721 729 4
722 730 6
723 731 8
724 732
725 733 $ log 'tip^foo'
726 734 hg: parse error: ^ expects a number 0, 1, or 2
727 735 [255]
728 736
729 737 aliases:
730 738
731 739 $ echo '[revsetalias]' >> .hg/hgrc
732 740 $ echo 'm = merge()' >> .hg/hgrc
733 741 $ echo 'sincem = descendants(m)' >> .hg/hgrc
734 742 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
735 743 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
736 744 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
737 745
738 746 $ try m
739 747 ('symbol', 'm')
740 748 (func
741 749 ('symbol', 'merge')
742 750 None)
743 751 6
744 752
745 753 test alias recursion
746 754
747 755 $ try sincem
748 756 ('symbol', 'sincem')
749 757 (func
750 758 ('symbol', 'descendants')
751 759 (func
752 760 ('symbol', 'merge')
753 761 None))
754 762 6
755 763 7
756 764
757 765 test infinite recursion
758 766
759 767 $ echo 'recurse1 = recurse2' >> .hg/hgrc
760 768 $ echo 'recurse2 = recurse1' >> .hg/hgrc
761 769 $ try recurse1
762 770 ('symbol', 'recurse1')
763 771 hg: parse error: infinite expansion of revset alias "recurse1" detected
764 772 [255]
765 773
766 774 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
767 775 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
768 776 $ try "level2(level1(1, 2), 3)"
769 777 (func
770 778 ('symbol', 'level2')
771 779 (list
772 780 (func
773 781 ('symbol', 'level1')
774 782 (list
775 783 ('symbol', '1')
776 784 ('symbol', '2')))
777 785 ('symbol', '3')))
778 786 (or
779 787 ('symbol', '3')
780 788 (or
781 789 ('symbol', '1')
782 790 ('symbol', '2')))
783 791 3
784 792 1
785 793 2
786 794
787 795 test nesting and variable passing
788 796
789 797 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
790 798 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
791 799 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
792 800 $ try 'nested(2:5)'
793 801 (func
794 802 ('symbol', 'nested')
795 803 (range
796 804 ('symbol', '2')
797 805 ('symbol', '5')))
798 806 (func
799 807 ('symbol', 'max')
800 808 (range
801 809 ('symbol', '2')
802 810 ('symbol', '5')))
803 811 5
804 812
805 813 test variable isolation, variable placeholders are rewritten as string
806 814 then parsed and matched again as string. Check they do not leak too
807 815 far away.
808 816
809 817 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
810 818 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
811 819 $ try 'callinjection(2:5)'
812 820 (func
813 821 ('symbol', 'callinjection')
814 822 (range
815 823 ('symbol', '2')
816 824 ('symbol', '5')))
817 825 (func
818 826 ('symbol', 'descendants')
819 827 (func
820 828 ('symbol', 'max')
821 829 ('string', '$1')))
822 830 abort: unknown revision '$1'!
823 831 [255]
824 832
825 833 $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
826 834 $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
827 835 $ try 'callinjection2(2:5)'
828 836 (func
829 837 ('symbol', 'callinjection2')
830 838 (range
831 839 ('symbol', '2')
832 840 ('symbol', '5')))
833 841 hg: parse error: not a function: _aliasarg
834 842 [255]
835 843 >>> data = file('.hg/hgrc', 'rb').read()
836 844 >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
837 845
838 846 $ try 'd(2:5)'
839 847 (func
840 848 ('symbol', 'd')
841 849 (range
842 850 ('symbol', '2')
843 851 ('symbol', '5')))
844 852 (func
845 853 ('symbol', 'reverse')
846 854 (func
847 855 ('symbol', 'sort')
848 856 (list
849 857 (range
850 858 ('symbol', '2')
851 859 ('symbol', '5'))
852 860 ('symbol', 'date'))))
853 861 4
854 862 5
855 863 3
856 864 2
857 865 $ try 'rs(2 or 3, date)'
858 866 (func
859 867 ('symbol', 'rs')
860 868 (list
861 869 (or
862 870 ('symbol', '2')
863 871 ('symbol', '3'))
864 872 ('symbol', 'date')))
865 873 (func
866 874 ('symbol', 'reverse')
867 875 (func
868 876 ('symbol', 'sort')
869 877 (list
870 878 (or
871 879 ('symbol', '2')
872 880 ('symbol', '3'))
873 881 ('symbol', 'date'))))
874 882 3
875 883 2
876 884 $ try 'rs()'
877 885 (func
878 886 ('symbol', 'rs')
879 887 None)
880 888 hg: parse error: invalid number of arguments: 0
881 889 [255]
882 890 $ try 'rs(2)'
883 891 (func
884 892 ('symbol', 'rs')
885 893 ('symbol', '2'))
886 894 hg: parse error: invalid number of arguments: 1
887 895 [255]
888 896 $ try 'rs(2, data, 7)'
889 897 (func
890 898 ('symbol', 'rs')
891 899 (list
892 900 (list
893 901 ('symbol', '2')
894 902 ('symbol', 'data'))
895 903 ('symbol', '7')))
896 904 hg: parse error: invalid number of arguments: 3
897 905 [255]
898 906 $ try 'rs4(2 or 3, x, x, date)'
899 907 (func
900 908 ('symbol', 'rs4')
901 909 (list
902 910 (list
903 911 (list
904 912 (or
905 913 ('symbol', '2')
906 914 ('symbol', '3'))
907 915 ('symbol', 'x'))
908 916 ('symbol', 'x'))
909 917 ('symbol', 'date')))
910 918 (func
911 919 ('symbol', 'reverse')
912 920 (func
913 921 ('symbol', 'sort')
914 922 (list
915 923 (or
916 924 ('symbol', '2')
917 925 ('symbol', '3'))
918 926 ('symbol', 'date'))))
919 927 3
920 928 2
921 929
922 930 issue2549 - correct optimizations
923 931
924 932 $ log 'limit(1 or 2 or 3, 2) and not 2'
925 933 1
926 934 $ log 'max(1 or 2) and not 2'
927 935 $ log 'min(1 or 2) and not 1'
928 936 $ log 'last(1 or 2, 1) and not 2'
929 937
930 938 test revsets started with 40-chars hash (issue3669)
931 939
932 940 $ ISSUE3669_TIP=`hg tip --template '{node}'`
933 941 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
934 942 9
935 943 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
936 944 8
937 945
938 946 test or-ed indirect predicates (issue3775)
939 947
940 948 $ log '6 or 6^1' | sort
941 949 5
942 950 6
943 951 $ log '6^1 or 6' | sort
944 952 5
945 953 6
946 954 $ log '4 or 4~1' | sort
947 955 2
948 956 4
949 957 $ log '4~1 or 4' | sort
950 958 2
951 959 4
952 960 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
953 961 0
954 962 1
955 963 2
956 964 3
957 965 4
958 966 5
959 967 6
960 968 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
961 969 0
962 970 1
963 971 2
964 972 3
965 973 4
966 974 5
967 975 6
968 976
969 977 tests for 'remote()' predicate:
970 978 #. (csets in remote) (id) (remote)
971 979 1. less than local current branch "default"
972 980 2. same with local specified "default"
973 981 3. more than local specified specified
974 982
975 983 $ hg clone --quiet -U . ../remote3
976 984 $ cd ../remote3
977 985 $ hg update -q 7
978 986 $ echo r > r
979 987 $ hg ci -Aqm 10
980 988 $ log 'remote()'
981 989 7
982 990 $ log 'remote("a-b-c-")'
983 991 2
984 992 $ cd ../repo
985 993 $ log 'remote(".a.b.c.", "../remote3")'
986 994
987 995 $ cd ..
988 996
989 997 test author/desc/keyword in problematic encoding
990 998 # unicode: cp932:
991 999 # u30A2 0x83 0x41(= 'A')
992 1000 # u30C2 0x83 0x61(= 'a')
993 1001
994 1002 $ hg init problematicencoding
995 1003 $ cd problematicencoding
996 1004
997 1005 $ python > setup.sh <<EOF
998 1006 > print u'''
999 1007 > echo a > text
1000 1008 > hg add text
1001 1009 > hg --encoding utf-8 commit -u '\u30A2' -m none
1002 1010 > echo b > text
1003 1011 > hg --encoding utf-8 commit -u '\u30C2' -m none
1004 1012 > echo c > text
1005 1013 > hg --encoding utf-8 commit -u none -m '\u30A2'
1006 1014 > echo d > text
1007 1015 > hg --encoding utf-8 commit -u none -m '\u30C2'
1008 1016 > '''.encode('utf-8')
1009 1017 > EOF
1010 1018 $ sh < setup.sh
1011 1019
1012 1020 test in problematic encoding
1013 1021 $ python > test.sh <<EOF
1014 1022 > print u'''
1015 1023 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
1016 1024 > echo ====
1017 1025 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
1018 1026 > echo ====
1019 1027 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
1020 1028 > echo ====
1021 1029 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
1022 1030 > echo ====
1023 1031 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
1024 1032 > echo ====
1025 1033 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
1026 1034 > '''.encode('cp932')
1027 1035 > EOF
1028 1036 $ sh < test.sh
1029 1037 0
1030 1038 ====
1031 1039 1
1032 1040 ====
1033 1041 2
1034 1042 ====
1035 1043 3
1036 1044 ====
1037 1045 0
1038 1046 2
1039 1047 ====
1040 1048 1
1041 1049 3
1042 1050
1043 1051 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now