##// END OF EJS Templates
revpair: smartset compatibility...
Pierre-Yves David -
r20862:97b2f26d default
parent child Browse files
Show More
@@ -1,932 +1,945
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 if len(l) == 0:
473 if not l:
474 first = second = None
475 elif l.isascending():
476 first = l.min()
477 second = l.max()
478 elif l.isdescending():
479 first = l.max()
480 second = l.min()
481 else:
482 l = list(l)
483 first = l[0]
484 second = l[-1]
485
486 if first is None:
474 487 raise util.Abort(_('empty revision range'))
475 488
476 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
477 return repo.lookup(l[0]), None
489 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
490 return repo.lookup(first), None
478 491
479 return repo.lookup(l[0]), repo.lookup(l[-1])
492 return repo.lookup(first), repo.lookup(second)
480 493
481 494 _revrangesep = ':'
482 495
483 496 def revrange(repo, revs):
484 497 """Yield revision as strings from a list of revision specifications."""
485 498
486 499 def revfix(repo, val, defval):
487 500 if not val and val != 0 and defval is not None:
488 501 return defval
489 502 return repo[val].rev()
490 503
491 504 seen, l = set(), revset.baseset([])
492 505 for spec in revs:
493 506 if l and not seen:
494 507 seen = set(l)
495 508 # attempt to parse old-style ranges first to deal with
496 509 # things like old-tag which contain query metacharacters
497 510 try:
498 511 if isinstance(spec, int):
499 512 seen.add(spec)
500 513 l = l + revset.baseset([spec])
501 514 continue
502 515
503 516 if _revrangesep in spec:
504 517 start, end = spec.split(_revrangesep, 1)
505 518 start = revfix(repo, start, 0)
506 519 end = revfix(repo, end, len(repo) - 1)
507 520 if end == nullrev and start < 0:
508 521 start = nullrev
509 522 rangeiter = repo.changelog.revs(start, end)
510 523 if not seen and not l:
511 524 # by far the most common case: revs = ["-1:0"]
512 525 l = revset.baseset(rangeiter)
513 526 # defer syncing seen until next iteration
514 527 continue
515 528 newrevs = set(rangeiter)
516 529 if seen:
517 530 newrevs.difference_update(seen)
518 531 seen.update(newrevs)
519 532 else:
520 533 seen = newrevs
521 534 l = l + revset.baseset(sorted(newrevs, reverse=start > end))
522 535 continue
523 536 elif spec and spec in repo: # single unquoted rev
524 537 rev = revfix(repo, spec, None)
525 538 if rev in seen:
526 539 continue
527 540 seen.add(rev)
528 541 l = l + revset.baseset([rev])
529 542 continue
530 543 except error.RepoLookupError:
531 544 pass
532 545
533 546 # fall through to new-style queries if old-style fails
534 547 m = revset.match(repo.ui, spec, repo)
535 548 if seen or l:
536 549 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
537 550 l = l + revset.baseset(dl)
538 551 seen.update(dl)
539 552 else:
540 553 l = m(repo, revset.spanset(repo))
541 554
542 555 return l
543 556
544 557 def expandpats(pats):
545 558 if not util.expandglobs:
546 559 return list(pats)
547 560 ret = []
548 561 for p in pats:
549 562 kind, name = matchmod._patsplit(p, None)
550 563 if kind is None:
551 564 try:
552 565 globbed = glob.glob(name)
553 566 except re.error:
554 567 globbed = [name]
555 568 if globbed:
556 569 ret.extend(globbed)
557 570 continue
558 571 ret.append(p)
559 572 return ret
560 573
561 574 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
562 575 if pats == ("",):
563 576 pats = []
564 577 if not globbed and default == 'relpath':
565 578 pats = expandpats(pats or [])
566 579
567 580 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
568 581 default)
569 582 def badfn(f, msg):
570 583 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
571 584 m.bad = badfn
572 585 return m, pats
573 586
574 587 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
575 588 return matchandpats(ctx, pats, opts, globbed, default)[0]
576 589
577 590 def matchall(repo):
578 591 return matchmod.always(repo.root, repo.getcwd())
579 592
580 593 def matchfiles(repo, files):
581 594 return matchmod.exact(repo.root, repo.getcwd(), files)
582 595
583 596 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
584 597 if dry_run is None:
585 598 dry_run = opts.get('dry_run')
586 599 if similarity is None:
587 600 similarity = float(opts.get('similarity') or 0)
588 601 # we'd use status here, except handling of symlinks and ignore is tricky
589 602 m = match(repo[None], pats, opts)
590 603 rejected = []
591 604 m.bad = lambda x, y: rejected.append(x)
592 605
593 606 added, unknown, deleted, removed = _interestingfiles(repo, m)
594 607
595 608 unknownset = set(unknown)
596 609 toprint = unknownset.copy()
597 610 toprint.update(deleted)
598 611 for abs in sorted(toprint):
599 612 if repo.ui.verbose or not m.exact(abs):
600 613 rel = m.rel(abs)
601 614 if abs in unknownset:
602 615 status = _('adding %s\n') % ((pats and rel) or abs)
603 616 else:
604 617 status = _('removing %s\n') % ((pats and rel) or abs)
605 618 repo.ui.status(status)
606 619
607 620 renames = _findrenames(repo, m, added + unknown, removed + deleted,
608 621 similarity)
609 622
610 623 if not dry_run:
611 624 _markchanges(repo, unknown, deleted, renames)
612 625
613 626 for f in rejected:
614 627 if f in m.files():
615 628 return 1
616 629 return 0
617 630
618 631 def marktouched(repo, files, similarity=0.0):
619 632 '''Assert that files have somehow been operated upon. files are relative to
620 633 the repo root.'''
621 634 m = matchfiles(repo, files)
622 635 rejected = []
623 636 m.bad = lambda x, y: rejected.append(x)
624 637
625 638 added, unknown, deleted, removed = _interestingfiles(repo, m)
626 639
627 640 if repo.ui.verbose:
628 641 unknownset = set(unknown)
629 642 toprint = unknownset.copy()
630 643 toprint.update(deleted)
631 644 for abs in sorted(toprint):
632 645 if abs in unknownset:
633 646 status = _('adding %s\n') % abs
634 647 else:
635 648 status = _('removing %s\n') % abs
636 649 repo.ui.status(status)
637 650
638 651 renames = _findrenames(repo, m, added + unknown, removed + deleted,
639 652 similarity)
640 653
641 654 _markchanges(repo, unknown, deleted, renames)
642 655
643 656 for f in rejected:
644 657 if f in m.files():
645 658 return 1
646 659 return 0
647 660
648 661 def _interestingfiles(repo, matcher):
649 662 '''Walk dirstate with matcher, looking for files that addremove would care
650 663 about.
651 664
652 665 This is different from dirstate.status because it doesn't care about
653 666 whether files are modified or clean.'''
654 667 added, unknown, deleted, removed = [], [], [], []
655 668 audit_path = pathutil.pathauditor(repo.root)
656 669
657 670 ctx = repo[None]
658 671 dirstate = repo.dirstate
659 672 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
660 673 full=False)
661 674 for abs, st in walkresults.iteritems():
662 675 dstate = dirstate[abs]
663 676 if dstate == '?' and audit_path.check(abs):
664 677 unknown.append(abs)
665 678 elif dstate != 'r' and not st:
666 679 deleted.append(abs)
667 680 # for finding renames
668 681 elif dstate == 'r':
669 682 removed.append(abs)
670 683 elif dstate == 'a':
671 684 added.append(abs)
672 685
673 686 return added, unknown, deleted, removed
674 687
675 688 def _findrenames(repo, matcher, added, removed, similarity):
676 689 '''Find renames from removed files to added ones.'''
677 690 renames = {}
678 691 if similarity > 0:
679 692 for old, new, score in similar.findrenames(repo, added, removed,
680 693 similarity):
681 694 if (repo.ui.verbose or not matcher.exact(old)
682 695 or not matcher.exact(new)):
683 696 repo.ui.status(_('recording removal of %s as rename to %s '
684 697 '(%d%% similar)\n') %
685 698 (matcher.rel(old), matcher.rel(new),
686 699 score * 100))
687 700 renames[new] = old
688 701 return renames
689 702
690 703 def _markchanges(repo, unknown, deleted, renames):
691 704 '''Marks the files in unknown as added, the files in deleted as removed,
692 705 and the files in renames as copied.'''
693 706 wctx = repo[None]
694 707 wlock = repo.wlock()
695 708 try:
696 709 wctx.forget(deleted)
697 710 wctx.add(unknown)
698 711 for new, old in renames.iteritems():
699 712 wctx.copy(old, new)
700 713 finally:
701 714 wlock.release()
702 715
703 716 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
704 717 """Update the dirstate to reflect the intent of copying src to dst. For
705 718 different reasons it might not end with dst being marked as copied from src.
706 719 """
707 720 origsrc = repo.dirstate.copied(src) or src
708 721 if dst == origsrc: # copying back a copy?
709 722 if repo.dirstate[dst] not in 'mn' and not dryrun:
710 723 repo.dirstate.normallookup(dst)
711 724 else:
712 725 if repo.dirstate[origsrc] == 'a' and origsrc == src:
713 726 if not ui.quiet:
714 727 ui.warn(_("%s has not been committed yet, so no copy "
715 728 "data will be stored for %s.\n")
716 729 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
717 730 if repo.dirstate[dst] in '?r' and not dryrun:
718 731 wctx.add([dst])
719 732 elif not dryrun:
720 733 wctx.copy(origsrc, dst)
721 734
722 735 def readrequires(opener, supported):
723 736 '''Reads and parses .hg/requires and checks if all entries found
724 737 are in the list of supported features.'''
725 738 requirements = set(opener.read("requires").splitlines())
726 739 missings = []
727 740 for r in requirements:
728 741 if r not in supported:
729 742 if not r or not r[0].isalnum():
730 743 raise error.RequirementError(_(".hg/requires file is corrupt"))
731 744 missings.append(r)
732 745 missings.sort()
733 746 if missings:
734 747 raise error.RequirementError(
735 748 _("repository requires features unknown to this Mercurial: %s")
736 749 % " ".join(missings),
737 750 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
738 751 " for more information"))
739 752 return requirements
740 753
741 754 class filecachesubentry(object):
742 755 def __init__(self, path, stat):
743 756 self.path = path
744 757 self.cachestat = None
745 758 self._cacheable = None
746 759
747 760 if stat:
748 761 self.cachestat = filecachesubentry.stat(self.path)
749 762
750 763 if self.cachestat:
751 764 self._cacheable = self.cachestat.cacheable()
752 765 else:
753 766 # None means we don't know yet
754 767 self._cacheable = None
755 768
756 769 def refresh(self):
757 770 if self.cacheable():
758 771 self.cachestat = filecachesubentry.stat(self.path)
759 772
760 773 def cacheable(self):
761 774 if self._cacheable is not None:
762 775 return self._cacheable
763 776
764 777 # we don't know yet, assume it is for now
765 778 return True
766 779
767 780 def changed(self):
768 781 # no point in going further if we can't cache it
769 782 if not self.cacheable():
770 783 return True
771 784
772 785 newstat = filecachesubentry.stat(self.path)
773 786
774 787 # we may not know if it's cacheable yet, check again now
775 788 if newstat and self._cacheable is None:
776 789 self._cacheable = newstat.cacheable()
777 790
778 791 # check again
779 792 if not self._cacheable:
780 793 return True
781 794
782 795 if self.cachestat != newstat:
783 796 self.cachestat = newstat
784 797 return True
785 798 else:
786 799 return False
787 800
788 801 @staticmethod
789 802 def stat(path):
790 803 try:
791 804 return util.cachestat(path)
792 805 except OSError, e:
793 806 if e.errno != errno.ENOENT:
794 807 raise
795 808
796 809 class filecacheentry(object):
797 810 def __init__(self, paths, stat=True):
798 811 self._entries = []
799 812 for path in paths:
800 813 self._entries.append(filecachesubentry(path, stat))
801 814
802 815 def changed(self):
803 816 '''true if any entry has changed'''
804 817 for entry in self._entries:
805 818 if entry.changed():
806 819 return True
807 820 return False
808 821
809 822 def refresh(self):
810 823 for entry in self._entries:
811 824 entry.refresh()
812 825
813 826 class filecache(object):
814 827 '''A property like decorator that tracks files under .hg/ for updates.
815 828
816 829 Records stat info when called in _filecache.
817 830
818 831 On subsequent calls, compares old stat info with new info, and recreates the
819 832 object when any of the files changes, updating the new stat info in
820 833 _filecache.
821 834
822 835 Mercurial either atomic renames or appends for files under .hg,
823 836 so to ensure the cache is reliable we need the filesystem to be able
824 837 to tell us if a file has been replaced. If it can't, we fallback to
825 838 recreating the object on every call (essentially the same behaviour as
826 839 propertycache).
827 840
828 841 '''
829 842 def __init__(self, *paths):
830 843 self.paths = paths
831 844
832 845 def join(self, obj, fname):
833 846 """Used to compute the runtime path of a cached file.
834 847
835 848 Users should subclass filecache and provide their own version of this
836 849 function to call the appropriate join function on 'obj' (an instance
837 850 of the class that its member function was decorated).
838 851 """
839 852 return obj.join(fname)
840 853
841 854 def __call__(self, func):
842 855 self.func = func
843 856 self.name = func.__name__
844 857 return self
845 858
846 859 def __get__(self, obj, type=None):
847 860 # do we need to check if the file changed?
848 861 if self.name in obj.__dict__:
849 862 assert self.name in obj._filecache, self.name
850 863 return obj.__dict__[self.name]
851 864
852 865 entry = obj._filecache.get(self.name)
853 866
854 867 if entry:
855 868 if entry.changed():
856 869 entry.obj = self.func(obj)
857 870 else:
858 871 paths = [self.join(obj, path) for path in self.paths]
859 872
860 873 # We stat -before- creating the object so our cache doesn't lie if
861 874 # a writer modified between the time we read and stat
862 875 entry = filecacheentry(paths, True)
863 876 entry.obj = self.func(obj)
864 877
865 878 obj._filecache[self.name] = entry
866 879
867 880 obj.__dict__[self.name] = entry.obj
868 881 return entry.obj
869 882
870 883 def __set__(self, obj, value):
871 884 if self.name not in obj._filecache:
872 885 # we add an entry for the missing value because X in __dict__
873 886 # implies X in _filecache
874 887 paths = [self.join(obj, path) for path in self.paths]
875 888 ce = filecacheentry(paths, False)
876 889 obj._filecache[self.name] = ce
877 890 else:
878 891 ce = obj._filecache[self.name]
879 892
880 893 ce.obj = value # update cached copy
881 894 obj.__dict__[self.name] = value # update copy returned by obj.x
882 895
883 896 def __delete__(self, obj):
884 897 try:
885 898 del obj.__dict__[self.name]
886 899 except KeyError:
887 900 raise AttributeError(self.name)
888 901
889 902 class dirs(object):
890 903 '''a multiset of directory names from a dirstate or manifest'''
891 904
892 905 def __init__(self, map, skip=None):
893 906 self._dirs = {}
894 907 addpath = self.addpath
895 908 if util.safehasattr(map, 'iteritems') and skip is not None:
896 909 for f, s in map.iteritems():
897 910 if s[0] != skip:
898 911 addpath(f)
899 912 else:
900 913 for f in map:
901 914 addpath(f)
902 915
903 916 def addpath(self, path):
904 917 dirs = self._dirs
905 918 for base in finddirs(path):
906 919 if base in dirs:
907 920 dirs[base] += 1
908 921 return
909 922 dirs[base] = 1
910 923
911 924 def delpath(self, path):
912 925 dirs = self._dirs
913 926 for base in finddirs(path):
914 927 if dirs[base] > 1:
915 928 dirs[base] -= 1
916 929 return
917 930 del dirs[base]
918 931
919 932 def __iter__(self):
920 933 return self._dirs.iterkeys()
921 934
922 935 def __contains__(self, d):
923 936 return d in self._dirs
924 937
925 938 if util.safehasattr(parsers, 'dirs'):
926 939 dirs = parsers.dirs
927 940
928 941 def finddirs(path):
929 942 pos = path.rfind('/')
930 943 while pos != -1:
931 944 yield path[:pos]
932 945 pos = path.rfind('/', 0, pos)
@@ -1,1061 +1,1098
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 128 $ try -- -a-b-c--a # complains
129 129 (minus
130 130 (minus
131 131 (minus
132 132 (negate
133 133 ('symbol', 'a'))
134 134 ('symbol', 'b'))
135 135 ('symbol', 'c'))
136 136 (negate
137 137 ('symbol', 'a')))
138 138 abort: unknown revision '-a'!
139 139 [255]
140 140 $ try Γ©
141 141 ('symbol', '\xc3\xa9')
142 142 9
143 143
144 144 no quoting needed
145 145
146 146 $ log ::a-b-c-
147 147 0
148 148 1
149 149 2
150 150
151 151 quoting needed
152 152
153 153 $ try '"-a-b-c-"-a'
154 154 (minus
155 155 ('string', '-a-b-c-')
156 156 ('symbol', 'a'))
157 157 4
158 158
159 159 $ log '1 or 2'
160 160 1
161 161 2
162 162 $ log '1|2'
163 163 1
164 164 2
165 165 $ log '1 and 2'
166 166 $ log '1&2'
167 167 $ try '1&2|3' # precedence - and is higher
168 168 (or
169 169 (and
170 170 ('symbol', '1')
171 171 ('symbol', '2'))
172 172 ('symbol', '3'))
173 173 3
174 174 $ try '1|2&3'
175 175 (or
176 176 ('symbol', '1')
177 177 (and
178 178 ('symbol', '2')
179 179 ('symbol', '3')))
180 180 1
181 181 $ try '1&2&3' # associativity
182 182 (and
183 183 (and
184 184 ('symbol', '1')
185 185 ('symbol', '2'))
186 186 ('symbol', '3'))
187 187 $ try '1|(2|3)'
188 188 (or
189 189 ('symbol', '1')
190 190 (group
191 191 (or
192 192 ('symbol', '2')
193 193 ('symbol', '3'))))
194 194 1
195 195 2
196 196 3
197 197 $ log '1.0' # tag
198 198 6
199 199 $ log 'a' # branch
200 200 0
201 201 $ log '2785f51ee'
202 202 0
203 203 $ log 'date(2005)'
204 204 4
205 205 $ log 'date(this is a test)'
206 206 hg: parse error at 10: unexpected token: symbol
207 207 [255]
208 208 $ log 'date()'
209 209 hg: parse error: date requires a string
210 210 [255]
211 211 $ log 'date'
212 212 hg: parse error: can't use date here
213 213 [255]
214 214 $ log 'date('
215 215 hg: parse error at 5: not a prefix: end
216 216 [255]
217 217 $ log 'date(tip)'
218 218 abort: invalid date: 'tip'
219 219 [255]
220 220 $ log '"date"'
221 221 abort: unknown revision 'date'!
222 222 [255]
223 223 $ log 'date(2005) and 1::'
224 224 4
225 225
226 226 ancestor can accept 0 or more arguments
227 227
228 228 $ log 'ancestor()'
229 229 $ log 'ancestor(1)'
230 230 1
231 231 $ log 'ancestor(4,5)'
232 232 1
233 233 $ log 'ancestor(4,5) and 4'
234 234 $ log 'ancestor(0,0,1,3)'
235 235 0
236 236 $ log 'ancestor(3,1,5,3,5,1)'
237 237 1
238 238 $ log 'ancestor(0,1,3,5)'
239 239 0
240 240 $ log 'ancestor(1,2,3,4,5)'
241 241 1
242 242 $ log 'ancestors(5)'
243 243 0
244 244 1
245 245 3
246 246 5
247 247 $ log 'ancestor(ancestors(5))'
248 248 0
249 249 $ log 'author(bob)'
250 250 2
251 251 $ log 'author("re:bob|test")'
252 252 0
253 253 1
254 254 2
255 255 3
256 256 4
257 257 5
258 258 6
259 259 7
260 260 8
261 261 9
262 262 $ log 'branch(Γ©)'
263 263 8
264 264 9
265 265 $ log 'branch(a)'
266 266 0
267 267 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
268 268 0 a
269 269 2 a-b-c-
270 270 3 +a+b+c+
271 271 4 -a-b-c-
272 272 5 !a/b/c/
273 273 6 _a_b_c_
274 274 7 .a.b.c.
275 275 $ log 'children(ancestor(4,5))'
276 276 2
277 277 3
278 278 $ log 'closed()'
279 279 $ log 'contains(a)'
280 280 0
281 281 1
282 282 3
283 283 5
284 284 $ log 'contains("../repo/a")'
285 285 0
286 286 1
287 287 3
288 288 5
289 289 $ log 'desc(B)'
290 290 5
291 291 $ log 'descendants(2 or 3)'
292 292 2
293 293 3
294 294 4
295 295 5
296 296 6
297 297 7
298 298 8
299 299 9
300 300 $ log 'file("b*")'
301 301 1
302 302 4
303 303 $ log 'filelog("b")'
304 304 1
305 305 4
306 306 $ log 'filelog("../repo/b")'
307 307 1
308 308 4
309 309 $ log 'follow()'
310 310 0
311 311 1
312 312 2
313 313 4
314 314 8
315 315 9
316 316 $ log 'grep("issue\d+")'
317 317 6
318 318 $ try 'grep("(")' # invalid regular expression
319 319 (func
320 320 ('symbol', 'grep')
321 321 ('string', '('))
322 322 hg: parse error: invalid match pattern: unbalanced parenthesis
323 323 [255]
324 324 $ try 'grep("\bissue\d+")'
325 325 (func
326 326 ('symbol', 'grep')
327 327 ('string', '\x08issue\\d+'))
328 328 $ try 'grep(r"\bissue\d+")'
329 329 (func
330 330 ('symbol', 'grep')
331 331 ('string', '\\bissue\\d+'))
332 332 6
333 333 $ try 'grep(r"\")'
334 334 hg: parse error at 7: unterminated string
335 335 [255]
336 336 $ log 'head()'
337 337 0
338 338 1
339 339 2
340 340 3
341 341 4
342 342 5
343 343 6
344 344 7
345 345 9
346 346 $ log 'heads(6::)'
347 347 7
348 348 $ log 'keyword(issue)'
349 349 6
350 350 $ log 'keyword("test a")'
351 351 $ log 'limit(head(), 1)'
352 352 0
353 353 $ log 'matching(6)'
354 354 6
355 355 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
356 356 6
357 357 7
358 358 $ log 'max(contains(a))'
359 359 5
360 360 $ log 'min(contains(a))'
361 361 0
362 362 $ log 'merge()'
363 363 6
364 364 $ log 'branchpoint()'
365 365 1
366 366 4
367 367 $ log 'modifies(b)'
368 368 4
369 369 $ log 'modifies("path:b")'
370 370 4
371 371 $ log 'modifies("*")'
372 372 4
373 373 6
374 374 $ log 'modifies("set:modified()")'
375 375 4
376 376 $ log 'id(5)'
377 377 2
378 378 $ log 'only(9)'
379 379 8
380 380 9
381 381 $ log 'only(8)'
382 382 8
383 383 $ log 'only(9, 5)'
384 384 2
385 385 4
386 386 8
387 387 9
388 388 $ log 'only(7 + 9, 5 + 2)'
389 389 4
390 390 6
391 391 7
392 392 8
393 393 9
394 394 $ log 'outgoing()'
395 395 8
396 396 9
397 397 $ log 'outgoing("../remote1")'
398 398 8
399 399 9
400 400 $ log 'outgoing("../remote2")'
401 401 3
402 402 5
403 403 6
404 404 7
405 405 9
406 406 $ log 'p1(merge())'
407 407 5
408 408 $ log 'p2(merge())'
409 409 4
410 410 $ log 'parents(merge())'
411 411 4
412 412 5
413 413 $ log 'p1(branchpoint())'
414 414 0
415 415 2
416 416 $ log 'p2(branchpoint())'
417 417 $ log 'parents(branchpoint())'
418 418 0
419 419 2
420 420 $ log 'removes(a)'
421 421 2
422 422 6
423 423 $ log 'roots(all())'
424 424 0
425 425 $ log 'reverse(2 or 3 or 4 or 5)'
426 426 5
427 427 4
428 428 3
429 429 2
430 430 $ log 'reverse(all())'
431 431 9
432 432 8
433 433 7
434 434 6
435 435 5
436 436 4
437 437 3
438 438 2
439 439 1
440 440 0
441 441 $ log '1:: and reverse(all())'
442 442 9
443 443 8
444 444 7
445 445 6
446 446 5
447 447 4
448 448 3
449 449 2
450 450 1
451 451 $ log 'rev(5)'
452 452 5
453 453 $ log 'sort(limit(reverse(all()), 3))'
454 454 7
455 455 8
456 456 9
457 457 $ log 'sort(2 or 3 or 4 or 5, date)'
458 458 2
459 459 3
460 460 5
461 461 4
462 462 $ log 'tagged()'
463 463 6
464 464 $ log 'tag()'
465 465 6
466 466 $ log 'tag(1.0)'
467 467 6
468 468 $ log 'tag(tip)'
469 469 9
470 470
471 471 test sort revset
472 472 --------------------------------------------
473 473
474 474 test when adding two unordered revsets
475 475
476 476 $ log 'sort(keyword(issue) or modifies(b))'
477 477 4
478 478 6
479 479
480 480 test when sorting a reversed collection in the same way it is
481 481
482 482 $ log 'sort(reverse(all()), -rev)'
483 483 9
484 484 8
485 485 7
486 486 6
487 487 5
488 488 4
489 489 3
490 490 2
491 491 1
492 492 0
493 493
494 494 test when sorting a reversed collection
495 495
496 496 $ log 'sort(reverse(all()), rev)'
497 497 0
498 498 1
499 499 2
500 500 3
501 501 4
502 502 5
503 503 6
504 504 7
505 505 8
506 506 9
507 507
508 508
509 509 test sorting two sorted collections in different orders
510 510
511 511 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
512 512 2
513 513 6
514 514 8
515 515 9
516 516
517 517 test sorting two sorted collections in different orders backwards
518 518
519 519 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
520 520 9
521 521 8
522 522 6
523 523 2
524 524
525 525 test substracting something from an addset
526 526
527 527 $ log '(outgoing() or removes(a)) - removes(a)'
528 528 8
529 529 9
530 530
531 531 test intersecting something with an addset
532 532
533 533 $ log 'parents(outgoing() or removes(a))'
534 534 1
535 535 4
536 536 5
537 537 8
538 538
539 539 check that conversion to _missingancestors works
540 540 $ try --optimize '::3 - ::1'
541 541 (minus
542 542 (dagrangepre
543 543 ('symbol', '3'))
544 544 (dagrangepre
545 545 ('symbol', '1')))
546 546 * optimized:
547 547 (func
548 548 ('symbol', '_missingancestors')
549 549 (list
550 550 ('symbol', '3')
551 551 ('symbol', '1')))
552 552 3
553 553 $ try --optimize 'ancestors(1) - ancestors(3)'
554 554 (minus
555 555 (func
556 556 ('symbol', 'ancestors')
557 557 ('symbol', '1'))
558 558 (func
559 559 ('symbol', 'ancestors')
560 560 ('symbol', '3')))
561 561 * optimized:
562 562 (func
563 563 ('symbol', '_missingancestors')
564 564 (list
565 565 ('symbol', '1')
566 566 ('symbol', '3')))
567 567 $ try --optimize 'not ::2 and ::6'
568 568 (and
569 569 (not
570 570 (dagrangepre
571 571 ('symbol', '2')))
572 572 (dagrangepre
573 573 ('symbol', '6')))
574 574 * optimized:
575 575 (func
576 576 ('symbol', '_missingancestors')
577 577 (list
578 578 ('symbol', '6')
579 579 ('symbol', '2')))
580 580 3
581 581 4
582 582 5
583 583 6
584 584 $ try --optimize 'ancestors(6) and not ancestors(4)'
585 585 (and
586 586 (func
587 587 ('symbol', 'ancestors')
588 588 ('symbol', '6'))
589 589 (not
590 590 (func
591 591 ('symbol', 'ancestors')
592 592 ('symbol', '4'))))
593 593 * optimized:
594 594 (func
595 595 ('symbol', '_missingancestors')
596 596 (list
597 597 ('symbol', '6')
598 598 ('symbol', '4')))
599 599 3
600 600 5
601 601 6
602 602
603 603 we can use patterns when searching for tags
604 604
605 605 $ log 'tag("1..*")'
606 606 abort: tag '1..*' does not exist
607 607 [255]
608 608 $ log 'tag("re:1..*")'
609 609 6
610 610 $ log 'tag("re:[0-9].[0-9]")'
611 611 6
612 612 $ log 'tag("literal:1.0")'
613 613 6
614 614 $ log 'tag("re:0..*")'
615 615
616 616 $ log 'tag(unknown)'
617 617 abort: tag 'unknown' does not exist
618 618 [255]
619 619 $ log 'branch(unknown)'
620 620 abort: unknown revision 'unknown'!
621 621 [255]
622 622 $ log 'user(bob)'
623 623 2
624 624
625 625 $ log '4::8'
626 626 4
627 627 8
628 628 $ log '4:8'
629 629 4
630 630 5
631 631 6
632 632 7
633 633 8
634 634
635 635 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
636 636 4
637 637 2
638 638 5
639 639
640 640 $ log 'not 0 and 0:2'
641 641 1
642 642 2
643 643 $ log 'not 1 and 0:2'
644 644 0
645 645 2
646 646 $ log 'not 2 and 0:2'
647 647 0
648 648 1
649 649 $ log '(1 and 2)::'
650 650 $ log '(1 and 2):'
651 651 $ log '(1 and 2):3'
652 652 $ log 'sort(head(), -rev)'
653 653 9
654 654 7
655 655 6
656 656 5
657 657 4
658 658 3
659 659 2
660 660 1
661 661 0
662 662 $ log '4::8 - 8'
663 663 4
664 664 $ log 'matching(1 or 2 or 3) and (2 or 3 or 1)'
665 665 2
666 666 3
667 667 1
668 668
669 669 issue2437
670 670
671 671 $ log '3 and p1(5)'
672 672 3
673 673 $ log '4 and p2(6)'
674 674 4
675 675 $ log '1 and parents(:2)'
676 676 1
677 677 $ log '2 and children(1:)'
678 678 2
679 679 $ log 'roots(all()) or roots(all())'
680 680 0
681 681 $ hg debugrevspec 'roots(all()) or roots(all())'
682 682 0
683 683 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
684 684 9
685 685 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
686 686 4
687 687
688 688 issue2654: report a parse error if the revset was not completely parsed
689 689
690 690 $ log '1 OR 2'
691 691 hg: parse error at 2: invalid token
692 692 [255]
693 693
694 694 or operator should preserve ordering:
695 695 $ log 'reverse(2::4) or tip'
696 696 4
697 697 2
698 698 9
699 699
700 700 parentrevspec
701 701
702 702 $ log 'merge()^0'
703 703 6
704 704 $ log 'merge()^'
705 705 5
706 706 $ log 'merge()^1'
707 707 5
708 708 $ log 'merge()^2'
709 709 4
710 710 $ log 'merge()^^'
711 711 3
712 712 $ log 'merge()^1^'
713 713 3
714 714 $ log 'merge()^^^'
715 715 1
716 716
717 717 $ log 'merge()~0'
718 718 6
719 719 $ log 'merge()~1'
720 720 5
721 721 $ log 'merge()~2'
722 722 3
723 723 $ log 'merge()~2^1'
724 724 1
725 725 $ log 'merge()~3'
726 726 1
727 727
728 728 $ log '(-3:tip)^'
729 729 4
730 730 6
731 731 8
732 732
733 733 $ log 'tip^foo'
734 734 hg: parse error: ^ expects a number 0, 1, or 2
735 735 [255]
736 736
737 737 multiple revspecs
738 738
739 739 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
740 740 8
741 741 9
742 742 4
743 743 5
744 744 6
745 745 7
746 746
747 test usage in revpair (with "+")
748
749 (real pair)
750
751 $ hg diff -r 'tip^^' -r 'tip'
752 diff -r 2326846efdab -r 24286f4ae135 .hgtags
753 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
754 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
755 @@ -0,0 +1,1 @@
756 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
757 $ hg diff -r 'tip^^::tip'
758 diff -r 2326846efdab -r 24286f4ae135 .hgtags
759 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
760 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
761 @@ -0,0 +1,1 @@
762 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
763
764 (single rev)
765
766 $ hg diff -r 'tip^' -r 'tip^'
767 $ hg diff -r 'tip^::tip^ or tip^'
768
769 (single rev that does not looks like a range)
770
771 $ hg diff -r 'tip^ or tip^'
772 diff -r d5d0dcbdc4d9 .hgtags
773 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
774 +++ b/.hgtags * (glob)
775 @@ -0,0 +1,1 @@
776 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
777
778 (no rev)
779
780 $ hg diff -r 'author("babar") or author("celeste")'
781 abort: empty revision range
782 [255]
783
747 784 aliases:
748 785
749 786 $ echo '[revsetalias]' >> .hg/hgrc
750 787 $ echo 'm = merge()' >> .hg/hgrc
751 788 $ echo 'sincem = descendants(m)' >> .hg/hgrc
752 789 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
753 790 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
754 791 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
755 792
756 793 $ try m
757 794 ('symbol', 'm')
758 795 (func
759 796 ('symbol', 'merge')
760 797 None)
761 798 6
762 799
763 800 test alias recursion
764 801
765 802 $ try sincem
766 803 ('symbol', 'sincem')
767 804 (func
768 805 ('symbol', 'descendants')
769 806 (func
770 807 ('symbol', 'merge')
771 808 None))
772 809 6
773 810 7
774 811
775 812 test infinite recursion
776 813
777 814 $ echo 'recurse1 = recurse2' >> .hg/hgrc
778 815 $ echo 'recurse2 = recurse1' >> .hg/hgrc
779 816 $ try recurse1
780 817 ('symbol', 'recurse1')
781 818 hg: parse error: infinite expansion of revset alias "recurse1" detected
782 819 [255]
783 820
784 821 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
785 822 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
786 823 $ try "level2(level1(1, 2), 3)"
787 824 (func
788 825 ('symbol', 'level2')
789 826 (list
790 827 (func
791 828 ('symbol', 'level1')
792 829 (list
793 830 ('symbol', '1')
794 831 ('symbol', '2')))
795 832 ('symbol', '3')))
796 833 (or
797 834 ('symbol', '3')
798 835 (or
799 836 ('symbol', '1')
800 837 ('symbol', '2')))
801 838 3
802 839 1
803 840 2
804 841
805 842 test nesting and variable passing
806 843
807 844 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
808 845 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
809 846 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
810 847 $ try 'nested(2:5)'
811 848 (func
812 849 ('symbol', 'nested')
813 850 (range
814 851 ('symbol', '2')
815 852 ('symbol', '5')))
816 853 (func
817 854 ('symbol', 'max')
818 855 (range
819 856 ('symbol', '2')
820 857 ('symbol', '5')))
821 858 5
822 859
823 860 test variable isolation, variable placeholders are rewritten as string
824 861 then parsed and matched again as string. Check they do not leak too
825 862 far away.
826 863
827 864 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
828 865 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
829 866 $ try 'callinjection(2:5)'
830 867 (func
831 868 ('symbol', 'callinjection')
832 869 (range
833 870 ('symbol', '2')
834 871 ('symbol', '5')))
835 872 (func
836 873 ('symbol', 'descendants')
837 874 (func
838 875 ('symbol', 'max')
839 876 ('string', '$1')))
840 877 abort: unknown revision '$1'!
841 878 [255]
842 879
843 880 $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
844 881 $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
845 882 $ try 'callinjection2(2:5)'
846 883 (func
847 884 ('symbol', 'callinjection2')
848 885 (range
849 886 ('symbol', '2')
850 887 ('symbol', '5')))
851 888 hg: parse error: not a function: _aliasarg
852 889 [255]
853 890 >>> data = file('.hg/hgrc', 'rb').read()
854 891 >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
855 892
856 893 $ try 'd(2:5)'
857 894 (func
858 895 ('symbol', 'd')
859 896 (range
860 897 ('symbol', '2')
861 898 ('symbol', '5')))
862 899 (func
863 900 ('symbol', 'reverse')
864 901 (func
865 902 ('symbol', 'sort')
866 903 (list
867 904 (range
868 905 ('symbol', '2')
869 906 ('symbol', '5'))
870 907 ('symbol', 'date'))))
871 908 4
872 909 5
873 910 3
874 911 2
875 912 $ try 'rs(2 or 3, date)'
876 913 (func
877 914 ('symbol', 'rs')
878 915 (list
879 916 (or
880 917 ('symbol', '2')
881 918 ('symbol', '3'))
882 919 ('symbol', 'date')))
883 920 (func
884 921 ('symbol', 'reverse')
885 922 (func
886 923 ('symbol', 'sort')
887 924 (list
888 925 (or
889 926 ('symbol', '2')
890 927 ('symbol', '3'))
891 928 ('symbol', 'date'))))
892 929 3
893 930 2
894 931 $ try 'rs()'
895 932 (func
896 933 ('symbol', 'rs')
897 934 None)
898 935 hg: parse error: invalid number of arguments: 0
899 936 [255]
900 937 $ try 'rs(2)'
901 938 (func
902 939 ('symbol', 'rs')
903 940 ('symbol', '2'))
904 941 hg: parse error: invalid number of arguments: 1
905 942 [255]
906 943 $ try 'rs(2, data, 7)'
907 944 (func
908 945 ('symbol', 'rs')
909 946 (list
910 947 (list
911 948 ('symbol', '2')
912 949 ('symbol', 'data'))
913 950 ('symbol', '7')))
914 951 hg: parse error: invalid number of arguments: 3
915 952 [255]
916 953 $ try 'rs4(2 or 3, x, x, date)'
917 954 (func
918 955 ('symbol', 'rs4')
919 956 (list
920 957 (list
921 958 (list
922 959 (or
923 960 ('symbol', '2')
924 961 ('symbol', '3'))
925 962 ('symbol', 'x'))
926 963 ('symbol', 'x'))
927 964 ('symbol', 'date')))
928 965 (func
929 966 ('symbol', 'reverse')
930 967 (func
931 968 ('symbol', 'sort')
932 969 (list
933 970 (or
934 971 ('symbol', '2')
935 972 ('symbol', '3'))
936 973 ('symbol', 'date'))))
937 974 3
938 975 2
939 976
940 977 issue2549 - correct optimizations
941 978
942 979 $ log 'limit(1 or 2 or 3, 2) and not 2'
943 980 1
944 981 $ log 'max(1 or 2) and not 2'
945 982 $ log 'min(1 or 2) and not 1'
946 983 $ log 'last(1 or 2, 1) and not 2'
947 984
948 985 test revsets started with 40-chars hash (issue3669)
949 986
950 987 $ ISSUE3669_TIP=`hg tip --template '{node}'`
951 988 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
952 989 9
953 990 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
954 991 8
955 992
956 993 test or-ed indirect predicates (issue3775)
957 994
958 995 $ log '6 or 6^1' | sort
959 996 5
960 997 6
961 998 $ log '6^1 or 6' | sort
962 999 5
963 1000 6
964 1001 $ log '4 or 4~1' | sort
965 1002 2
966 1003 4
967 1004 $ log '4~1 or 4' | sort
968 1005 2
969 1006 4
970 1007 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
971 1008 0
972 1009 1
973 1010 2
974 1011 3
975 1012 4
976 1013 5
977 1014 6
978 1015 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
979 1016 0
980 1017 1
981 1018 2
982 1019 3
983 1020 4
984 1021 5
985 1022 6
986 1023
987 1024 tests for 'remote()' predicate:
988 1025 #. (csets in remote) (id) (remote)
989 1026 1. less than local current branch "default"
990 1027 2. same with local specified "default"
991 1028 3. more than local specified specified
992 1029
993 1030 $ hg clone --quiet -U . ../remote3
994 1031 $ cd ../remote3
995 1032 $ hg update -q 7
996 1033 $ echo r > r
997 1034 $ hg ci -Aqm 10
998 1035 $ log 'remote()'
999 1036 7
1000 1037 $ log 'remote("a-b-c-")'
1001 1038 2
1002 1039 $ cd ../repo
1003 1040 $ log 'remote(".a.b.c.", "../remote3")'
1004 1041
1005 1042 $ cd ..
1006 1043
1007 1044 test author/desc/keyword in problematic encoding
1008 1045 # unicode: cp932:
1009 1046 # u30A2 0x83 0x41(= 'A')
1010 1047 # u30C2 0x83 0x61(= 'a')
1011 1048
1012 1049 $ hg init problematicencoding
1013 1050 $ cd problematicencoding
1014 1051
1015 1052 $ python > setup.sh <<EOF
1016 1053 > print u'''
1017 1054 > echo a > text
1018 1055 > hg add text
1019 1056 > hg --encoding utf-8 commit -u '\u30A2' -m none
1020 1057 > echo b > text
1021 1058 > hg --encoding utf-8 commit -u '\u30C2' -m none
1022 1059 > echo c > text
1023 1060 > hg --encoding utf-8 commit -u none -m '\u30A2'
1024 1061 > echo d > text
1025 1062 > hg --encoding utf-8 commit -u none -m '\u30C2'
1026 1063 > '''.encode('utf-8')
1027 1064 > EOF
1028 1065 $ sh < setup.sh
1029 1066
1030 1067 test in problematic encoding
1031 1068 $ python > test.sh <<EOF
1032 1069 > print u'''
1033 1070 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
1034 1071 > echo ====
1035 1072 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
1036 1073 > echo ====
1037 1074 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
1038 1075 > echo ====
1039 1076 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
1040 1077 > echo ====
1041 1078 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
1042 1079 > echo ====
1043 1080 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
1044 1081 > '''.encode('cp932')
1045 1082 > EOF
1046 1083 $ sh < test.sh
1047 1084 0
1048 1085 ====
1049 1086 1
1050 1087 ====
1051 1088 2
1052 1089 ====
1053 1090 3
1054 1091 ====
1055 1092 0
1056 1093 2
1057 1094 ====
1058 1095 1
1059 1096 3
1060 1097
1061 1098 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now