##// END OF EJS Templates
util: raise ParseError when parsing dates (BC)...
Boris Feld -
r32462:bb18728e default
parent child Browse files
Show More
@@ -1,1699 +1,1699 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 __future__ import absolute_import
9 9
10 10 import collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import tempfile
22 22 import traceback
23 23
24 24 from .i18n import _
25 25 from .node import hex
26 26
27 27 from . import (
28 28 color,
29 29 config,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 progress,
34 34 pycompat,
35 35 rcutil,
36 36 scmutil,
37 37 util,
38 38 )
39 39
40 40 urlreq = util.urlreq
41 41
42 42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 44 if not c.isalnum())
45 45
46 46 samplehgrcs = {
47 47 'user':
48 48 """# example user config (see 'hg help config' for more info)
49 49 [ui]
50 50 # name and email, e.g.
51 51 # username = Jane Doe <jdoe@example.com>
52 52 username =
53 53
54 54 # uncomment to disable color in command output
55 55 # (see 'hg help color' for details)
56 56 # color = never
57 57
58 58 # uncomment to disable command output pagination
59 59 # (see 'hg help pager' for details)
60 60 # paginate = never
61 61
62 62 [extensions]
63 63 # uncomment these lines to enable some popular extensions
64 64 # (see 'hg help extensions' for more info)
65 65 #
66 66 # churn =
67 67 """,
68 68
69 69 'cloned':
70 70 """# example repository config (see 'hg help config' for more info)
71 71 [paths]
72 72 default = %s
73 73
74 74 # path aliases to other clones of this repo in URLs or filesystem paths
75 75 # (see 'hg help config.paths' for more info)
76 76 #
77 77 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
78 78 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
79 79 # my-clone = /home/jdoe/jdoes-clone
80 80
81 81 [ui]
82 82 # name and email (local to this repository, optional), e.g.
83 83 # username = Jane Doe <jdoe@example.com>
84 84 """,
85 85
86 86 'local':
87 87 """# example repository config (see 'hg help config' for more info)
88 88 [paths]
89 89 # path aliases to other clones of this repo in URLs or filesystem paths
90 90 # (see 'hg help config.paths' for more info)
91 91 #
92 92 # default = http://example.com/hg/example-repo
93 93 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
94 94 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
95 95 # my-clone = /home/jdoe/jdoes-clone
96 96
97 97 [ui]
98 98 # name and email (local to this repository, optional), e.g.
99 99 # username = Jane Doe <jdoe@example.com>
100 100 """,
101 101
102 102 'global':
103 103 """# example system-wide hg config (see 'hg help config' for more info)
104 104
105 105 [ui]
106 106 # uncomment to disable color in command output
107 107 # (see 'hg help color' for details)
108 108 # color = never
109 109
110 110 # uncomment to disable command output pagination
111 111 # (see 'hg help pager' for details)
112 112 # paginate = never
113 113
114 114 [extensions]
115 115 # uncomment these lines to enable some popular extensions
116 116 # (see 'hg help extensions' for more info)
117 117 #
118 118 # blackbox =
119 119 # churn =
120 120 """,
121 121 }
122 122
123 123
124 124 class httppasswordmgrdbproxy(object):
125 125 """Delays loading urllib2 until it's needed."""
126 126 def __init__(self):
127 127 self._mgr = None
128 128
129 129 def _get_mgr(self):
130 130 if self._mgr is None:
131 131 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
132 132 return self._mgr
133 133
134 134 def add_password(self, *args, **kwargs):
135 135 return self._get_mgr().add_password(*args, **kwargs)
136 136
137 137 def find_user_password(self, *args, **kwargs):
138 138 return self._get_mgr().find_user_password(*args, **kwargs)
139 139
140 140 def _catchterm(*args):
141 141 raise error.SignalInterrupt
142 142
143 143 class ui(object):
144 144 def __init__(self, src=None):
145 145 """Create a fresh new ui object if no src given
146 146
147 147 Use uimod.ui.load() to create a ui which knows global and user configs.
148 148 In most cases, you should use ui.copy() to create a copy of an existing
149 149 ui object.
150 150 """
151 151 # _buffers: used for temporary capture of output
152 152 self._buffers = []
153 153 # _exithandlers: callbacks run at the end of a request
154 154 self._exithandlers = []
155 155 # 3-tuple describing how each buffer in the stack behaves.
156 156 # Values are (capture stderr, capture subprocesses, apply labels).
157 157 self._bufferstates = []
158 158 # When a buffer is active, defines whether we are expanding labels.
159 159 # This exists to prevent an extra list lookup.
160 160 self._bufferapplylabels = None
161 161 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
162 162 self._reportuntrusted = True
163 163 self._ocfg = config.config() # overlay
164 164 self._tcfg = config.config() # trusted
165 165 self._ucfg = config.config() # untrusted
166 166 self._trustusers = set()
167 167 self._trustgroups = set()
168 168 self.callhooks = True
169 169 # Insecure server connections requested.
170 170 self.insecureconnections = False
171 171 # Blocked time
172 172 self.logblockedtimes = False
173 173 # color mode: see mercurial/color.py for possible value
174 174 self._colormode = None
175 175 self._terminfoparams = {}
176 176 self._styles = {}
177 177
178 178 if src:
179 179 self._exithandlers = src._exithandlers
180 180 self.fout = src.fout
181 181 self.ferr = src.ferr
182 182 self.fin = src.fin
183 183 self.pageractive = src.pageractive
184 184 self._disablepager = src._disablepager
185 185
186 186 self._tcfg = src._tcfg.copy()
187 187 self._ucfg = src._ucfg.copy()
188 188 self._ocfg = src._ocfg.copy()
189 189 self._trustusers = src._trustusers.copy()
190 190 self._trustgroups = src._trustgroups.copy()
191 191 self.environ = src.environ
192 192 self.callhooks = src.callhooks
193 193 self.insecureconnections = src.insecureconnections
194 194 self._colormode = src._colormode
195 195 self._terminfoparams = src._terminfoparams.copy()
196 196 self._styles = src._styles.copy()
197 197
198 198 self.fixconfig()
199 199
200 200 self.httppasswordmgrdb = src.httppasswordmgrdb
201 201 self._blockedtimes = src._blockedtimes
202 202 else:
203 203 self.fout = util.stdout
204 204 self.ferr = util.stderr
205 205 self.fin = util.stdin
206 206 self.pageractive = False
207 207 self._disablepager = False
208 208
209 209 # shared read-only environment
210 210 self.environ = encoding.environ
211 211
212 212 self.httppasswordmgrdb = httppasswordmgrdbproxy()
213 213 self._blockedtimes = collections.defaultdict(int)
214 214
215 215 allowed = self.configlist('experimental', 'exportableenviron')
216 216 if '*' in allowed:
217 217 self._exportableenviron = self.environ
218 218 else:
219 219 self._exportableenviron = {}
220 220 for k in allowed:
221 221 if k in self.environ:
222 222 self._exportableenviron[k] = self.environ[k]
223 223
224 224 @classmethod
225 225 def load(cls):
226 226 """Create a ui and load global and user configs"""
227 227 u = cls()
228 228 # we always trust global config files and environment variables
229 229 for t, f in rcutil.rccomponents():
230 230 if t == 'path':
231 231 u.readconfig(f, trust=True)
232 232 elif t == 'items':
233 233 sections = set()
234 234 for section, name, value, source in f:
235 235 # do not set u._ocfg
236 236 # XXX clean this up once immutable config object is a thing
237 237 u._tcfg.set(section, name, value, source)
238 238 u._ucfg.set(section, name, value, source)
239 239 sections.add(section)
240 240 for section in sections:
241 241 u.fixconfig(section=section)
242 242 else:
243 243 raise error.ProgrammingError('unknown rctype: %s' % t)
244 244 return u
245 245
246 246 def copy(self):
247 247 return self.__class__(self)
248 248
249 249 def resetstate(self):
250 250 """Clear internal state that shouldn't persist across commands"""
251 251 if self._progbar:
252 252 self._progbar.resetstate() # reset last-print time of progress bar
253 253 self.httppasswordmgrdb = httppasswordmgrdbproxy()
254 254
255 255 @contextlib.contextmanager
256 256 def timeblockedsection(self, key):
257 257 # this is open-coded below - search for timeblockedsection to find them
258 258 starttime = util.timer()
259 259 try:
260 260 yield
261 261 finally:
262 262 self._blockedtimes[key + '_blocked'] += \
263 263 (util.timer() - starttime) * 1000
264 264
265 265 def formatter(self, topic, opts):
266 266 return formatter.formatter(self, topic, opts)
267 267
268 268 def _trusted(self, fp, f):
269 269 st = util.fstat(fp)
270 270 if util.isowner(st):
271 271 return True
272 272
273 273 tusers, tgroups = self._trustusers, self._trustgroups
274 274 if '*' in tusers or '*' in tgroups:
275 275 return True
276 276
277 277 user = util.username(st.st_uid)
278 278 group = util.groupname(st.st_gid)
279 279 if user in tusers or group in tgroups or user == util.username():
280 280 return True
281 281
282 282 if self._reportuntrusted:
283 283 self.warn(_('not trusting file %s from untrusted '
284 284 'user %s, group %s\n') % (f, user, group))
285 285 return False
286 286
287 287 def readconfig(self, filename, root=None, trust=False,
288 288 sections=None, remap=None):
289 289 try:
290 290 fp = open(filename, u'rb')
291 291 except IOError:
292 292 if not sections: # ignore unless we were looking for something
293 293 return
294 294 raise
295 295
296 296 cfg = config.config()
297 297 trusted = sections or trust or self._trusted(fp, filename)
298 298
299 299 try:
300 300 cfg.read(filename, fp, sections=sections, remap=remap)
301 301 fp.close()
302 302 except error.ConfigError as inst:
303 303 if trusted:
304 304 raise
305 305 self.warn(_("ignored: %s\n") % str(inst))
306 306
307 307 if self.plain():
308 308 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
309 309 'logtemplate', 'statuscopies', 'style',
310 310 'traceback', 'verbose'):
311 311 if k in cfg['ui']:
312 312 del cfg['ui'][k]
313 313 for k, v in cfg.items('defaults'):
314 314 del cfg['defaults'][k]
315 315 for k, v in cfg.items('commands'):
316 316 del cfg['commands'][k]
317 317 # Don't remove aliases from the configuration if in the exceptionlist
318 318 if self.plain('alias'):
319 319 for k, v in cfg.items('alias'):
320 320 del cfg['alias'][k]
321 321 if self.plain('revsetalias'):
322 322 for k, v in cfg.items('revsetalias'):
323 323 del cfg['revsetalias'][k]
324 324 if self.plain('templatealias'):
325 325 for k, v in cfg.items('templatealias'):
326 326 del cfg['templatealias'][k]
327 327
328 328 if trusted:
329 329 self._tcfg.update(cfg)
330 330 self._tcfg.update(self._ocfg)
331 331 self._ucfg.update(cfg)
332 332 self._ucfg.update(self._ocfg)
333 333
334 334 if root is None:
335 335 root = os.path.expanduser('~')
336 336 self.fixconfig(root=root)
337 337
338 338 def fixconfig(self, root=None, section=None):
339 339 if section in (None, 'paths'):
340 340 # expand vars and ~
341 341 # translate paths relative to root (or home) into absolute paths
342 342 root = root or pycompat.getcwd()
343 343 for c in self._tcfg, self._ucfg, self._ocfg:
344 344 for n, p in c.items('paths'):
345 345 # Ignore sub-options.
346 346 if ':' in n:
347 347 continue
348 348 if not p:
349 349 continue
350 350 if '%%' in p:
351 351 s = self.configsource('paths', n) or 'none'
352 352 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
353 353 % (n, p, s))
354 354 p = p.replace('%%', '%')
355 355 p = util.expandpath(p)
356 356 if not util.hasscheme(p) and not os.path.isabs(p):
357 357 p = os.path.normpath(os.path.join(root, p))
358 358 c.set("paths", n, p)
359 359
360 360 if section in (None, 'ui'):
361 361 # update ui options
362 362 self.debugflag = self.configbool('ui', 'debug')
363 363 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
364 364 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
365 365 if self.verbose and self.quiet:
366 366 self.quiet = self.verbose = False
367 367 self._reportuntrusted = self.debugflag or self.configbool("ui",
368 368 "report_untrusted", True)
369 369 self.tracebackflag = self.configbool('ui', 'traceback', False)
370 370 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
371 371
372 372 if section in (None, 'trusted'):
373 373 # update trust information
374 374 self._trustusers.update(self.configlist('trusted', 'users'))
375 375 self._trustgroups.update(self.configlist('trusted', 'groups'))
376 376
377 377 def backupconfig(self, section, item):
378 378 return (self._ocfg.backup(section, item),
379 379 self._tcfg.backup(section, item),
380 380 self._ucfg.backup(section, item),)
381 381 def restoreconfig(self, data):
382 382 self._ocfg.restore(data[0])
383 383 self._tcfg.restore(data[1])
384 384 self._ucfg.restore(data[2])
385 385
386 386 def setconfig(self, section, name, value, source=''):
387 387 for cfg in (self._ocfg, self._tcfg, self._ucfg):
388 388 cfg.set(section, name, value, source)
389 389 self.fixconfig(section=section)
390 390
391 391 def _data(self, untrusted):
392 392 return untrusted and self._ucfg or self._tcfg
393 393
394 394 def configsource(self, section, name, untrusted=False):
395 395 return self._data(untrusted).source(section, name)
396 396
397 397 def config(self, section, name, default=None, untrusted=False):
398 398 if isinstance(name, list):
399 399 alternates = name
400 400 else:
401 401 alternates = [name]
402 402
403 403 for n in alternates:
404 404 value = self._data(untrusted).get(section, n, None)
405 405 if value is not None:
406 406 name = n
407 407 break
408 408 else:
409 409 value = default
410 410
411 411 if self.debugflag and not untrusted and self._reportuntrusted:
412 412 for n in alternates:
413 413 uvalue = self._ucfg.get(section, n)
414 414 if uvalue is not None and uvalue != value:
415 415 self.debug("ignoring untrusted configuration option "
416 416 "%s.%s = %s\n" % (section, n, uvalue))
417 417 return value
418 418
419 419 def configsuboptions(self, section, name, default=None, untrusted=False):
420 420 """Get a config option and all sub-options.
421 421
422 422 Some config options have sub-options that are declared with the
423 423 format "key:opt = value". This method is used to return the main
424 424 option and all its declared sub-options.
425 425
426 426 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
427 427 is a dict of defined sub-options where keys and values are strings.
428 428 """
429 429 data = self._data(untrusted)
430 430 main = data.get(section, name, default)
431 431 if self.debugflag and not untrusted and self._reportuntrusted:
432 432 uvalue = self._ucfg.get(section, name)
433 433 if uvalue is not None and uvalue != main:
434 434 self.debug('ignoring untrusted configuration option '
435 435 '%s.%s = %s\n' % (section, name, uvalue))
436 436
437 437 sub = {}
438 438 prefix = '%s:' % name
439 439 for k, v in data.items(section):
440 440 if k.startswith(prefix):
441 441 sub[k[len(prefix):]] = v
442 442
443 443 if self.debugflag and not untrusted and self._reportuntrusted:
444 444 for k, v in sub.items():
445 445 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
446 446 if uvalue is not None and uvalue != v:
447 447 self.debug('ignoring untrusted configuration option '
448 448 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
449 449
450 450 return main, sub
451 451
452 452 def configpath(self, section, name, default=None, untrusted=False):
453 453 'get a path config item, expanded relative to repo root or config file'
454 454 v = self.config(section, name, default, untrusted)
455 455 if v is None:
456 456 return None
457 457 if not os.path.isabs(v) or "://" not in v:
458 458 src = self.configsource(section, name, untrusted)
459 459 if ':' in src:
460 460 base = os.path.dirname(src.rsplit(':')[0])
461 461 v = os.path.join(base, os.path.expanduser(v))
462 462 return v
463 463
464 464 def configbool(self, section, name, default=False, untrusted=False):
465 465 """parse a configuration element as a boolean
466 466
467 467 >>> u = ui(); s = 'foo'
468 468 >>> u.setconfig(s, 'true', 'yes')
469 469 >>> u.configbool(s, 'true')
470 470 True
471 471 >>> u.setconfig(s, 'false', 'no')
472 472 >>> u.configbool(s, 'false')
473 473 False
474 474 >>> u.configbool(s, 'unknown')
475 475 False
476 476 >>> u.configbool(s, 'unknown', True)
477 477 True
478 478 >>> u.setconfig(s, 'invalid', 'somevalue')
479 479 >>> u.configbool(s, 'invalid')
480 480 Traceback (most recent call last):
481 481 ...
482 482 ConfigError: foo.invalid is not a boolean ('somevalue')
483 483 """
484 484
485 485 v = self.config(section, name, None, untrusted)
486 486 if v is None:
487 487 return default
488 488 if isinstance(v, bool):
489 489 return v
490 490 b = util.parsebool(v)
491 491 if b is None:
492 492 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
493 493 % (section, name, v))
494 494 return b
495 495
496 496 def configwith(self, convert, section, name, default=None,
497 497 desc=None, untrusted=False):
498 498 """parse a configuration element with a conversion function
499 499
500 500 >>> u = ui(); s = 'foo'
501 501 >>> u.setconfig(s, 'float1', '42')
502 502 >>> u.configwith(float, s, 'float1')
503 503 42.0
504 504 >>> u.setconfig(s, 'float2', '-4.25')
505 505 >>> u.configwith(float, s, 'float2')
506 506 -4.25
507 507 >>> u.configwith(float, s, 'unknown', 7)
508 508 7
509 509 >>> u.setconfig(s, 'invalid', 'somevalue')
510 510 >>> u.configwith(float, s, 'invalid')
511 511 Traceback (most recent call last):
512 512 ...
513 513 ConfigError: foo.invalid is not a valid float ('somevalue')
514 514 >>> u.configwith(float, s, 'invalid', desc='womble')
515 515 Traceback (most recent call last):
516 516 ...
517 517 ConfigError: foo.invalid is not a valid womble ('somevalue')
518 518 """
519 519
520 520 v = self.config(section, name, None, untrusted)
521 521 if v is None:
522 522 return default
523 523 try:
524 524 return convert(v)
525 except ValueError:
525 except (ValueError, error.ParseError):
526 526 if desc is None:
527 527 desc = convert.__name__
528 528 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
529 529 % (section, name, desc, v))
530 530
531 531 def configint(self, section, name, default=None, untrusted=False):
532 532 """parse a configuration element as an integer
533 533
534 534 >>> u = ui(); s = 'foo'
535 535 >>> u.setconfig(s, 'int1', '42')
536 536 >>> u.configint(s, 'int1')
537 537 42
538 538 >>> u.setconfig(s, 'int2', '-42')
539 539 >>> u.configint(s, 'int2')
540 540 -42
541 541 >>> u.configint(s, 'unknown', 7)
542 542 7
543 543 >>> u.setconfig(s, 'invalid', 'somevalue')
544 544 >>> u.configint(s, 'invalid')
545 545 Traceback (most recent call last):
546 546 ...
547 547 ConfigError: foo.invalid is not a valid integer ('somevalue')
548 548 """
549 549
550 550 return self.configwith(int, section, name, default, 'integer',
551 551 untrusted)
552 552
553 553 def configbytes(self, section, name, default=0, untrusted=False):
554 554 """parse a configuration element as a quantity in bytes
555 555
556 556 Units can be specified as b (bytes), k or kb (kilobytes), m or
557 557 mb (megabytes), g or gb (gigabytes).
558 558
559 559 >>> u = ui(); s = 'foo'
560 560 >>> u.setconfig(s, 'val1', '42')
561 561 >>> u.configbytes(s, 'val1')
562 562 42
563 563 >>> u.setconfig(s, 'val2', '42.5 kb')
564 564 >>> u.configbytes(s, 'val2')
565 565 43520
566 566 >>> u.configbytes(s, 'unknown', '7 MB')
567 567 7340032
568 568 >>> u.setconfig(s, 'invalid', 'somevalue')
569 569 >>> u.configbytes(s, 'invalid')
570 570 Traceback (most recent call last):
571 571 ...
572 572 ConfigError: foo.invalid is not a byte quantity ('somevalue')
573 573 """
574 574
575 575 value = self.config(section, name, None, untrusted)
576 576 if value is None:
577 577 if not isinstance(default, str):
578 578 return default
579 579 value = default
580 580 try:
581 581 return util.sizetoint(value)
582 582 except error.ParseError:
583 583 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
584 584 % (section, name, value))
585 585
586 586 def configlist(self, section, name, default=None, untrusted=False):
587 587 """parse a configuration element as a list of comma/space separated
588 588 strings
589 589
590 590 >>> u = ui(); s = 'foo'
591 591 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
592 592 >>> u.configlist(s, 'list1')
593 593 ['this', 'is', 'a small', 'test']
594 594 """
595 595 # default is not always a list
596 596 if isinstance(default, bytes):
597 597 default = config.parselist(default)
598 598 return self.configwith(config.parselist, section, name, default or [],
599 599 'list', untrusted)
600 600
601 601 def configdate(self, section, name, default=None, untrusted=False):
602 602 """parse a configuration element as a tuple of ints
603 603
604 604 >>> u = ui(); s = 'foo'
605 605 >>> u.setconfig(s, 'date', '0 0')
606 606 >>> u.configdate(s, 'date')
607 607 (0, 0)
608 608 """
609 609 if self.config(section, name, default, untrusted):
610 return self.configwith(util.rawparsedate, section, name, default,
610 return self.configwith(util.parsedate, section, name, default,
611 611 'date', untrusted)
612 612 return default
613 613
614 614 def hasconfig(self, section, name, untrusted=False):
615 615 return self._data(untrusted).hasitem(section, name)
616 616
617 617 def has_section(self, section, untrusted=False):
618 618 '''tell whether section exists in config.'''
619 619 return section in self._data(untrusted)
620 620
621 621 def configitems(self, section, untrusted=False, ignoresub=False):
622 622 items = self._data(untrusted).items(section)
623 623 if ignoresub:
624 624 newitems = {}
625 625 for k, v in items:
626 626 if ':' not in k:
627 627 newitems[k] = v
628 628 items = newitems.items()
629 629 if self.debugflag and not untrusted and self._reportuntrusted:
630 630 for k, v in self._ucfg.items(section):
631 631 if self._tcfg.get(section, k) != v:
632 632 self.debug("ignoring untrusted configuration option "
633 633 "%s.%s = %s\n" % (section, k, v))
634 634 return items
635 635
636 636 def walkconfig(self, untrusted=False):
637 637 cfg = self._data(untrusted)
638 638 for section in cfg.sections():
639 639 for name, value in self.configitems(section, untrusted):
640 640 yield section, name, value
641 641
642 642 def plain(self, feature=None):
643 643 '''is plain mode active?
644 644
645 645 Plain mode means that all configuration variables which affect
646 646 the behavior and output of Mercurial should be
647 647 ignored. Additionally, the output should be stable,
648 648 reproducible and suitable for use in scripts or applications.
649 649
650 650 The only way to trigger plain mode is by setting either the
651 651 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
652 652
653 653 The return value can either be
654 654 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
655 655 - True otherwise
656 656 '''
657 657 if ('HGPLAIN' not in encoding.environ and
658 658 'HGPLAINEXCEPT' not in encoding.environ):
659 659 return False
660 660 exceptions = encoding.environ.get('HGPLAINEXCEPT',
661 661 '').strip().split(',')
662 662 if feature and exceptions:
663 663 return feature not in exceptions
664 664 return True
665 665
666 666 def username(self):
667 667 """Return default username to be used in commits.
668 668
669 669 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
670 670 and stop searching if one of these is set.
671 671 If not found and ui.askusername is True, ask the user, else use
672 672 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
673 673 """
674 674 user = encoding.environ.get("HGUSER")
675 675 if user is None:
676 676 user = self.config("ui", ["username", "user"])
677 677 if user is not None:
678 678 user = os.path.expandvars(user)
679 679 if user is None:
680 680 user = encoding.environ.get("EMAIL")
681 681 if user is None and self.configbool("ui", "askusername"):
682 682 user = self.prompt(_("enter a commit username:"), default=None)
683 683 if user is None and not self.interactive():
684 684 try:
685 685 user = '%s@%s' % (util.getuser(), socket.getfqdn())
686 686 self.warn(_("no username found, using '%s' instead\n") % user)
687 687 except KeyError:
688 688 pass
689 689 if not user:
690 690 raise error.Abort(_('no username supplied'),
691 691 hint=_("use 'hg config --edit' "
692 692 'to set your username'))
693 693 if "\n" in user:
694 694 raise error.Abort(_("username %s contains a newline\n")
695 695 % repr(user))
696 696 return user
697 697
698 698 def shortuser(self, user):
699 699 """Return a short representation of a user name or email address."""
700 700 if not self.verbose:
701 701 user = util.shortuser(user)
702 702 return user
703 703
704 704 def expandpath(self, loc, default=None):
705 705 """Return repository location relative to cwd or from [paths]"""
706 706 try:
707 707 p = self.paths.getpath(loc)
708 708 if p:
709 709 return p.rawloc
710 710 except error.RepoError:
711 711 pass
712 712
713 713 if default:
714 714 try:
715 715 p = self.paths.getpath(default)
716 716 if p:
717 717 return p.rawloc
718 718 except error.RepoError:
719 719 pass
720 720
721 721 return loc
722 722
723 723 @util.propertycache
724 724 def paths(self):
725 725 return paths(self)
726 726
727 727 def pushbuffer(self, error=False, subproc=False, labeled=False):
728 728 """install a buffer to capture standard output of the ui object
729 729
730 730 If error is True, the error output will be captured too.
731 731
732 732 If subproc is True, output from subprocesses (typically hooks) will be
733 733 captured too.
734 734
735 735 If labeled is True, any labels associated with buffered
736 736 output will be handled. By default, this has no effect
737 737 on the output returned, but extensions and GUI tools may
738 738 handle this argument and returned styled output. If output
739 739 is being buffered so it can be captured and parsed or
740 740 processed, labeled should not be set to True.
741 741 """
742 742 self._buffers.append([])
743 743 self._bufferstates.append((error, subproc, labeled))
744 744 self._bufferapplylabels = labeled
745 745
746 746 def popbuffer(self):
747 747 '''pop the last buffer and return the buffered output'''
748 748 self._bufferstates.pop()
749 749 if self._bufferstates:
750 750 self._bufferapplylabels = self._bufferstates[-1][2]
751 751 else:
752 752 self._bufferapplylabels = None
753 753
754 754 return "".join(self._buffers.pop())
755 755
756 756 def write(self, *args, **opts):
757 757 '''write args to output
758 758
759 759 By default, this method simply writes to the buffer or stdout.
760 760 Color mode can be set on the UI class to have the output decorated
761 761 with color modifier before being written to stdout.
762 762
763 763 The color used is controlled by an optional keyword argument, "label".
764 764 This should be a string containing label names separated by space.
765 765 Label names take the form of "topic.type". For example, ui.debug()
766 766 issues a label of "ui.debug".
767 767
768 768 When labeling output for a specific command, a label of
769 769 "cmdname.type" is recommended. For example, status issues
770 770 a label of "status.modified" for modified files.
771 771 '''
772 772 if self._buffers and not opts.get('prompt', False):
773 773 if self._bufferapplylabels:
774 774 label = opts.get('label', '')
775 775 self._buffers[-1].extend(self.label(a, label) for a in args)
776 776 else:
777 777 self._buffers[-1].extend(args)
778 778 elif self._colormode == 'win32':
779 779 # windows color printing is its own can of crab, defer to
780 780 # the color module and that is it.
781 781 color.win32print(self, self._write, *args, **opts)
782 782 else:
783 783 msgs = args
784 784 if self._colormode is not None:
785 785 label = opts.get('label', '')
786 786 msgs = [self.label(a, label) for a in args]
787 787 self._write(*msgs, **opts)
788 788
789 789 def _write(self, *msgs, **opts):
790 790 self._progclear()
791 791 # opencode timeblockedsection because this is a critical path
792 792 starttime = util.timer()
793 793 try:
794 794 for a in msgs:
795 795 self.fout.write(a)
796 796 except IOError as err:
797 797 raise error.StdioError(err)
798 798 finally:
799 799 self._blockedtimes['stdio_blocked'] += \
800 800 (util.timer() - starttime) * 1000
801 801
802 802 def write_err(self, *args, **opts):
803 803 self._progclear()
804 804 if self._bufferstates and self._bufferstates[-1][0]:
805 805 self.write(*args, **opts)
806 806 elif self._colormode == 'win32':
807 807 # windows color printing is its own can of crab, defer to
808 808 # the color module and that is it.
809 809 color.win32print(self, self._write_err, *args, **opts)
810 810 else:
811 811 msgs = args
812 812 if self._colormode is not None:
813 813 label = opts.get('label', '')
814 814 msgs = [self.label(a, label) for a in args]
815 815 self._write_err(*msgs, **opts)
816 816
817 817 def _write_err(self, *msgs, **opts):
818 818 try:
819 819 with self.timeblockedsection('stdio'):
820 820 if not getattr(self.fout, 'closed', False):
821 821 self.fout.flush()
822 822 for a in msgs:
823 823 self.ferr.write(a)
824 824 # stderr may be buffered under win32 when redirected to files,
825 825 # including stdout.
826 826 if not getattr(self.ferr, 'closed', False):
827 827 self.ferr.flush()
828 828 except IOError as inst:
829 829 raise error.StdioError(inst)
830 830
831 831 def flush(self):
832 832 # opencode timeblockedsection because this is a critical path
833 833 starttime = util.timer()
834 834 try:
835 835 try:
836 836 self.fout.flush()
837 837 except IOError as err:
838 838 raise error.StdioError(err)
839 839 finally:
840 840 try:
841 841 self.ferr.flush()
842 842 except IOError as err:
843 843 raise error.StdioError(err)
844 844 finally:
845 845 self._blockedtimes['stdio_blocked'] += \
846 846 (util.timer() - starttime) * 1000
847 847
848 848 def _isatty(self, fh):
849 849 if self.configbool('ui', 'nontty', False):
850 850 return False
851 851 return util.isatty(fh)
852 852
853 853 def disablepager(self):
854 854 self._disablepager = True
855 855
856 856 def pager(self, command):
857 857 """Start a pager for subsequent command output.
858 858
859 859 Commands which produce a long stream of output should call
860 860 this function to activate the user's preferred pagination
861 861 mechanism (which may be no pager). Calling this function
862 862 precludes any future use of interactive functionality, such as
863 863 prompting the user or activating curses.
864 864
865 865 Args:
866 866 command: The full, non-aliased name of the command. That is, "log"
867 867 not "history, "summary" not "summ", etc.
868 868 """
869 869 if (self._disablepager
870 870 or self.pageractive
871 871 or command in self.configlist('pager', 'ignore')
872 872 or not self.configbool('ui', 'paginate', True)
873 873 or not self.configbool('pager', 'attend-' + command, True)
874 874 # TODO: if we want to allow HGPLAINEXCEPT=pager,
875 875 # formatted() will need some adjustment.
876 876 or not self.formatted()
877 877 or self.plain()
878 878 # TODO: expose debugger-enabled on the UI object
879 879 or '--debugger' in pycompat.sysargv):
880 880 # We only want to paginate if the ui appears to be
881 881 # interactive, the user didn't say HGPLAIN or
882 882 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
883 883 return
884 884
885 885 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
886 886 if not pagercmd:
887 887 return
888 888
889 889 pagerenv = {}
890 890 for name, value in rcutil.defaultpagerenv().items():
891 891 if name not in encoding.environ:
892 892 pagerenv[name] = value
893 893
894 894 self.debug('starting pager for command %r\n' % command)
895 895 self.flush()
896 896
897 897 wasformatted = self.formatted()
898 898 if util.safehasattr(signal, "SIGPIPE"):
899 899 signal.signal(signal.SIGPIPE, _catchterm)
900 900 if self._runpager(pagercmd, pagerenv):
901 901 self.pageractive = True
902 902 # Preserve the formatted-ness of the UI. This is important
903 903 # because we mess with stdout, which might confuse
904 904 # auto-detection of things being formatted.
905 905 self.setconfig('ui', 'formatted', wasformatted, 'pager')
906 906 self.setconfig('ui', 'interactive', False, 'pager')
907 907
908 908 # If pagermode differs from color.mode, reconfigure color now that
909 909 # pageractive is set.
910 910 cm = self._colormode
911 911 if cm != self.config('color', 'pagermode', cm):
912 912 color.setup(self)
913 913 else:
914 914 # If the pager can't be spawned in dispatch when --pager=on is
915 915 # given, don't try again when the command runs, to avoid a duplicate
916 916 # warning about a missing pager command.
917 917 self.disablepager()
918 918
919 919 def _runpager(self, command, env=None):
920 920 """Actually start the pager and set up file descriptors.
921 921
922 922 This is separate in part so that extensions (like chg) can
923 923 override how a pager is invoked.
924 924 """
925 925 if command == 'cat':
926 926 # Save ourselves some work.
927 927 return False
928 928 # If the command doesn't contain any of these characters, we
929 929 # assume it's a binary and exec it directly. This means for
930 930 # simple pager command configurations, we can degrade
931 931 # gracefully and tell the user about their broken pager.
932 932 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
933 933
934 934 if pycompat.osname == 'nt' and not shell:
935 935 # Window's built-in `more` cannot be invoked with shell=False, but
936 936 # its `more.com` can. Hide this implementation detail from the
937 937 # user so we can also get sane bad PAGER behavior. MSYS has
938 938 # `more.exe`, so do a cmd.exe style resolution of the executable to
939 939 # determine which one to use.
940 940 fullcmd = util.findexe(command)
941 941 if not fullcmd:
942 942 self.warn(_("missing pager command '%s', skipping pager\n")
943 943 % command)
944 944 return False
945 945
946 946 command = fullcmd
947 947
948 948 try:
949 949 pager = subprocess.Popen(
950 950 command, shell=shell, bufsize=-1,
951 951 close_fds=util.closefds, stdin=subprocess.PIPE,
952 952 stdout=util.stdout, stderr=util.stderr,
953 953 env=util.shellenviron(env))
954 954 except OSError as e:
955 955 if e.errno == errno.ENOENT and not shell:
956 956 self.warn(_("missing pager command '%s', skipping pager\n")
957 957 % command)
958 958 return False
959 959 raise
960 960
961 961 # back up original file descriptors
962 962 stdoutfd = os.dup(util.stdout.fileno())
963 963 stderrfd = os.dup(util.stderr.fileno())
964 964
965 965 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
966 966 if self._isatty(util.stderr):
967 967 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
968 968
969 969 @self.atexit
970 970 def killpager():
971 971 if util.safehasattr(signal, "SIGINT"):
972 972 signal.signal(signal.SIGINT, signal.SIG_IGN)
973 973 # restore original fds, closing pager.stdin copies in the process
974 974 os.dup2(stdoutfd, util.stdout.fileno())
975 975 os.dup2(stderrfd, util.stderr.fileno())
976 976 pager.stdin.close()
977 977 pager.wait()
978 978
979 979 return True
980 980
981 981 def atexit(self, func, *args, **kwargs):
982 982 '''register a function to run after dispatching a request
983 983
984 984 Handlers do not stay registered across request boundaries.'''
985 985 self._exithandlers.append((func, args, kwargs))
986 986 return func
987 987
988 988 def interface(self, feature):
989 989 """what interface to use for interactive console features?
990 990
991 991 The interface is controlled by the value of `ui.interface` but also by
992 992 the value of feature-specific configuration. For example:
993 993
994 994 ui.interface.histedit = text
995 995 ui.interface.chunkselector = curses
996 996
997 997 Here the features are "histedit" and "chunkselector".
998 998
999 999 The configuration above means that the default interfaces for commands
1000 1000 is curses, the interface for histedit is text and the interface for
1001 1001 selecting chunk is crecord (the best curses interface available).
1002 1002
1003 1003 Consider the following example:
1004 1004 ui.interface = curses
1005 1005 ui.interface.histedit = text
1006 1006
1007 1007 Then histedit will use the text interface and chunkselector will use
1008 1008 the default curses interface (crecord at the moment).
1009 1009 """
1010 1010 alldefaults = frozenset(["text", "curses"])
1011 1011
1012 1012 featureinterfaces = {
1013 1013 "chunkselector": [
1014 1014 "text",
1015 1015 "curses",
1016 1016 ]
1017 1017 }
1018 1018
1019 1019 # Feature-specific interface
1020 1020 if feature not in featureinterfaces.keys():
1021 1021 # Programming error, not user error
1022 1022 raise ValueError("Unknown feature requested %s" % feature)
1023 1023
1024 1024 availableinterfaces = frozenset(featureinterfaces[feature])
1025 1025 if alldefaults > availableinterfaces:
1026 1026 # Programming error, not user error. We need a use case to
1027 1027 # define the right thing to do here.
1028 1028 raise ValueError(
1029 1029 "Feature %s does not handle all default interfaces" %
1030 1030 feature)
1031 1031
1032 1032 if self.plain():
1033 1033 return "text"
1034 1034
1035 1035 # Default interface for all the features
1036 1036 defaultinterface = "text"
1037 1037 i = self.config("ui", "interface", None)
1038 1038 if i in alldefaults:
1039 1039 defaultinterface = i
1040 1040
1041 1041 choseninterface = defaultinterface
1042 1042 f = self.config("ui", "interface.%s" % feature, None)
1043 1043 if f in availableinterfaces:
1044 1044 choseninterface = f
1045 1045
1046 1046 if i is not None and defaultinterface != i:
1047 1047 if f is not None:
1048 1048 self.warn(_("invalid value for ui.interface: %s\n") %
1049 1049 (i,))
1050 1050 else:
1051 1051 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1052 1052 (i, choseninterface))
1053 1053 if f is not None and choseninterface != f:
1054 1054 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1055 1055 (feature, f, choseninterface))
1056 1056
1057 1057 return choseninterface
1058 1058
1059 1059 def interactive(self):
1060 1060 '''is interactive input allowed?
1061 1061
1062 1062 An interactive session is a session where input can be reasonably read
1063 1063 from `sys.stdin'. If this function returns false, any attempt to read
1064 1064 from stdin should fail with an error, unless a sensible default has been
1065 1065 specified.
1066 1066
1067 1067 Interactiveness is triggered by the value of the `ui.interactive'
1068 1068 configuration variable or - if it is unset - when `sys.stdin' points
1069 1069 to a terminal device.
1070 1070
1071 1071 This function refers to input only; for output, see `ui.formatted()'.
1072 1072 '''
1073 1073 i = self.configbool("ui", "interactive", None)
1074 1074 if i is None:
1075 1075 # some environments replace stdin without implementing isatty
1076 1076 # usually those are non-interactive
1077 1077 return self._isatty(self.fin)
1078 1078
1079 1079 return i
1080 1080
1081 1081 def termwidth(self):
1082 1082 '''how wide is the terminal in columns?
1083 1083 '''
1084 1084 if 'COLUMNS' in encoding.environ:
1085 1085 try:
1086 1086 return int(encoding.environ['COLUMNS'])
1087 1087 except ValueError:
1088 1088 pass
1089 1089 return scmutil.termsize(self)[0]
1090 1090
1091 1091 def formatted(self):
1092 1092 '''should formatted output be used?
1093 1093
1094 1094 It is often desirable to format the output to suite the output medium.
1095 1095 Examples of this are truncating long lines or colorizing messages.
1096 1096 However, this is not often not desirable when piping output into other
1097 1097 utilities, e.g. `grep'.
1098 1098
1099 1099 Formatted output is triggered by the value of the `ui.formatted'
1100 1100 configuration variable or - if it is unset - when `sys.stdout' points
1101 1101 to a terminal device. Please note that `ui.formatted' should be
1102 1102 considered an implementation detail; it is not intended for use outside
1103 1103 Mercurial or its extensions.
1104 1104
1105 1105 This function refers to output only; for input, see `ui.interactive()'.
1106 1106 This function always returns false when in plain mode, see `ui.plain()'.
1107 1107 '''
1108 1108 if self.plain():
1109 1109 return False
1110 1110
1111 1111 i = self.configbool("ui", "formatted", None)
1112 1112 if i is None:
1113 1113 # some environments replace stdout without implementing isatty
1114 1114 # usually those are non-interactive
1115 1115 return self._isatty(self.fout)
1116 1116
1117 1117 return i
1118 1118
1119 1119 def _readline(self, prompt=''):
1120 1120 if self._isatty(self.fin):
1121 1121 try:
1122 1122 # magically add command line editing support, where
1123 1123 # available
1124 1124 import readline
1125 1125 # force demandimport to really load the module
1126 1126 readline.read_history_file
1127 1127 # windows sometimes raises something other than ImportError
1128 1128 except Exception:
1129 1129 pass
1130 1130
1131 1131 # call write() so output goes through subclassed implementation
1132 1132 # e.g. color extension on Windows
1133 1133 self.write(prompt, prompt=True)
1134 1134
1135 1135 # instead of trying to emulate raw_input, swap (self.fin,
1136 1136 # self.fout) with (sys.stdin, sys.stdout)
1137 1137 oldin = sys.stdin
1138 1138 oldout = sys.stdout
1139 1139 sys.stdin = self.fin
1140 1140 sys.stdout = self.fout
1141 1141 # prompt ' ' must exist; otherwise readline may delete entire line
1142 1142 # - http://bugs.python.org/issue12833
1143 1143 with self.timeblockedsection('stdio'):
1144 1144 line = raw_input(' ')
1145 1145 sys.stdin = oldin
1146 1146 sys.stdout = oldout
1147 1147
1148 1148 # When stdin is in binary mode on Windows, it can cause
1149 1149 # raw_input() to emit an extra trailing carriage return
1150 1150 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1151 1151 line = line[:-1]
1152 1152 return line
1153 1153
1154 1154 def prompt(self, msg, default="y"):
1155 1155 """Prompt user with msg, read response.
1156 1156 If ui is not interactive, the default is returned.
1157 1157 """
1158 1158 if not self.interactive():
1159 1159 self.write(msg, ' ', default or '', "\n")
1160 1160 return default
1161 1161 try:
1162 1162 r = self._readline(self.label(msg, 'ui.prompt'))
1163 1163 if not r:
1164 1164 r = default
1165 1165 if self.configbool('ui', 'promptecho'):
1166 1166 self.write(r, "\n")
1167 1167 return r
1168 1168 except EOFError:
1169 1169 raise error.ResponseExpected()
1170 1170
1171 1171 @staticmethod
1172 1172 def extractchoices(prompt):
1173 1173 """Extract prompt message and list of choices from specified prompt.
1174 1174
1175 1175 This returns tuple "(message, choices)", and "choices" is the
1176 1176 list of tuple "(response character, text without &)".
1177 1177
1178 1178 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1179 1179 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1180 1180 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1181 1181 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1182 1182 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1183 1183 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1184 1184 """
1185 1185
1186 1186 # Sadly, the prompt string may have been built with a filename
1187 1187 # containing "$$" so let's try to find the first valid-looking
1188 1188 # prompt to start parsing. Sadly, we also can't rely on
1189 1189 # choices containing spaces, ASCII, or basically anything
1190 1190 # except an ampersand followed by a character.
1191 1191 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1192 1192 msg = m.group(1)
1193 1193 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1194 1194 return (msg,
1195 1195 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1196 1196 for s in choices])
1197 1197
1198 1198 def promptchoice(self, prompt, default=0):
1199 1199 """Prompt user with a message, read response, and ensure it matches
1200 1200 one of the provided choices. The prompt is formatted as follows:
1201 1201
1202 1202 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1203 1203
1204 1204 The index of the choice is returned. Responses are case
1205 1205 insensitive. If ui is not interactive, the default is
1206 1206 returned.
1207 1207 """
1208 1208
1209 1209 msg, choices = self.extractchoices(prompt)
1210 1210 resps = [r for r, t in choices]
1211 1211 while True:
1212 1212 r = self.prompt(msg, resps[default])
1213 1213 if r.lower() in resps:
1214 1214 return resps.index(r.lower())
1215 1215 self.write(_("unrecognized response\n"))
1216 1216
1217 1217 def getpass(self, prompt=None, default=None):
1218 1218 if not self.interactive():
1219 1219 return default
1220 1220 try:
1221 1221 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1222 1222 # disable getpass() only if explicitly specified. it's still valid
1223 1223 # to interact with tty even if fin is not a tty.
1224 1224 with self.timeblockedsection('stdio'):
1225 1225 if self.configbool('ui', 'nontty'):
1226 1226 l = self.fin.readline()
1227 1227 if not l:
1228 1228 raise EOFError
1229 1229 return l.rstrip('\n')
1230 1230 else:
1231 1231 return getpass.getpass('')
1232 1232 except EOFError:
1233 1233 raise error.ResponseExpected()
1234 1234 def status(self, *msg, **opts):
1235 1235 '''write status message to output (if ui.quiet is False)
1236 1236
1237 1237 This adds an output label of "ui.status".
1238 1238 '''
1239 1239 if not self.quiet:
1240 1240 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1241 1241 self.write(*msg, **opts)
1242 1242 def warn(self, *msg, **opts):
1243 1243 '''write warning message to output (stderr)
1244 1244
1245 1245 This adds an output label of "ui.warning".
1246 1246 '''
1247 1247 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1248 1248 self.write_err(*msg, **opts)
1249 1249 def note(self, *msg, **opts):
1250 1250 '''write note to output (if ui.verbose is True)
1251 1251
1252 1252 This adds an output label of "ui.note".
1253 1253 '''
1254 1254 if self.verbose:
1255 1255 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1256 1256 self.write(*msg, **opts)
1257 1257 def debug(self, *msg, **opts):
1258 1258 '''write debug message to output (if ui.debugflag is True)
1259 1259
1260 1260 This adds an output label of "ui.debug".
1261 1261 '''
1262 1262 if self.debugflag:
1263 1263 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1264 1264 self.write(*msg, **opts)
1265 1265
1266 1266 def edit(self, text, user, extra=None, editform=None, pending=None,
1267 1267 repopath=None):
1268 1268 extra_defaults = {
1269 1269 'prefix': 'editor',
1270 1270 'suffix': '.txt',
1271 1271 }
1272 1272 if extra is not None:
1273 1273 extra_defaults.update(extra)
1274 1274 extra = extra_defaults
1275 1275
1276 1276 rdir = None
1277 1277 if self.configbool('experimental', 'editortmpinhg'):
1278 1278 rdir = repopath
1279 1279 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1280 1280 suffix=extra['suffix'],
1281 1281 dir=rdir)
1282 1282 try:
1283 1283 f = os.fdopen(fd, r'wb')
1284 1284 f.write(util.tonativeeol(text))
1285 1285 f.close()
1286 1286
1287 1287 environ = {'HGUSER': user}
1288 1288 if 'transplant_source' in extra:
1289 1289 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1290 1290 for label in ('intermediate-source', 'source', 'rebase_source'):
1291 1291 if label in extra:
1292 1292 environ.update({'HGREVISION': extra[label]})
1293 1293 break
1294 1294 if editform:
1295 1295 environ.update({'HGEDITFORM': editform})
1296 1296 if pending:
1297 1297 environ.update({'HG_PENDING': pending})
1298 1298
1299 1299 editor = self.geteditor()
1300 1300
1301 1301 self.system("%s \"%s\"" % (editor, name),
1302 1302 environ=environ,
1303 1303 onerr=error.Abort, errprefix=_("edit failed"),
1304 1304 blockedtag='editor')
1305 1305
1306 1306 f = open(name, r'rb')
1307 1307 t = util.fromnativeeol(f.read())
1308 1308 f.close()
1309 1309 finally:
1310 1310 os.unlink(name)
1311 1311
1312 1312 return t
1313 1313
1314 1314 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1315 1315 blockedtag=None):
1316 1316 '''execute shell command with appropriate output stream. command
1317 1317 output will be redirected if fout is not stdout.
1318 1318
1319 1319 if command fails and onerr is None, return status, else raise onerr
1320 1320 object as exception.
1321 1321 '''
1322 1322 if blockedtag is None:
1323 1323 # Long cmds tend to be because of an absolute path on cmd. Keep
1324 1324 # the tail end instead
1325 1325 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1326 1326 blockedtag = 'unknown_system_' + cmdsuffix
1327 1327 out = self.fout
1328 1328 if any(s[1] for s in self._bufferstates):
1329 1329 out = self
1330 1330 with self.timeblockedsection(blockedtag):
1331 1331 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1332 1332 if rc and onerr:
1333 1333 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1334 1334 util.explainexit(rc)[0])
1335 1335 if errprefix:
1336 1336 errmsg = '%s: %s' % (errprefix, errmsg)
1337 1337 raise onerr(errmsg)
1338 1338 return rc
1339 1339
1340 1340 def _runsystem(self, cmd, environ, cwd, out):
1341 1341 """actually execute the given shell command (can be overridden by
1342 1342 extensions like chg)"""
1343 1343 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1344 1344
1345 1345 def traceback(self, exc=None, force=False):
1346 1346 '''print exception traceback if traceback printing enabled or forced.
1347 1347 only to call in exception handler. returns true if traceback
1348 1348 printed.'''
1349 1349 if self.tracebackflag or force:
1350 1350 if exc is None:
1351 1351 exc = sys.exc_info()
1352 1352 cause = getattr(exc[1], 'cause', None)
1353 1353
1354 1354 if cause is not None:
1355 1355 causetb = traceback.format_tb(cause[2])
1356 1356 exctb = traceback.format_tb(exc[2])
1357 1357 exconly = traceback.format_exception_only(cause[0], cause[1])
1358 1358
1359 1359 # exclude frame where 'exc' was chained and rethrown from exctb
1360 1360 self.write_err('Traceback (most recent call last):\n',
1361 1361 ''.join(exctb[:-1]),
1362 1362 ''.join(causetb),
1363 1363 ''.join(exconly))
1364 1364 else:
1365 1365 output = traceback.format_exception(exc[0], exc[1], exc[2])
1366 1366 data = r''.join(output)
1367 1367 if pycompat.ispy3:
1368 1368 enc = pycompat.sysstr(encoding.encoding)
1369 1369 data = data.encode(enc, errors=r'replace')
1370 1370 self.write_err(data)
1371 1371 return self.tracebackflag or force
1372 1372
1373 1373 def geteditor(self):
1374 1374 '''return editor to use'''
1375 1375 if pycompat.sysplatform == 'plan9':
1376 1376 # vi is the MIPS instruction simulator on Plan 9. We
1377 1377 # instead default to E to plumb commit messages to
1378 1378 # avoid confusion.
1379 1379 editor = 'E'
1380 1380 else:
1381 1381 editor = 'vi'
1382 1382 return (encoding.environ.get("HGEDITOR") or
1383 1383 self.config("ui", "editor", editor))
1384 1384
1385 1385 @util.propertycache
1386 1386 def _progbar(self):
1387 1387 """setup the progbar singleton to the ui object"""
1388 1388 if (self.quiet or self.debugflag
1389 1389 or self.configbool('progress', 'disable', False)
1390 1390 or not progress.shouldprint(self)):
1391 1391 return None
1392 1392 return getprogbar(self)
1393 1393
1394 1394 def _progclear(self):
1395 1395 """clear progress bar output if any. use it before any output"""
1396 1396 if '_progbar' not in vars(self): # nothing loaded yet
1397 1397 return
1398 1398 if self._progbar is not None and self._progbar.printed:
1399 1399 self._progbar.clear()
1400 1400
1401 1401 def progress(self, topic, pos, item="", unit="", total=None):
1402 1402 '''show a progress message
1403 1403
1404 1404 By default a textual progress bar will be displayed if an operation
1405 1405 takes too long. 'topic' is the current operation, 'item' is a
1406 1406 non-numeric marker of the current position (i.e. the currently
1407 1407 in-process file), 'pos' is the current numeric position (i.e.
1408 1408 revision, bytes, etc.), unit is a corresponding unit label,
1409 1409 and total is the highest expected pos.
1410 1410
1411 1411 Multiple nested topics may be active at a time.
1412 1412
1413 1413 All topics should be marked closed by setting pos to None at
1414 1414 termination.
1415 1415 '''
1416 1416 if self._progbar is not None:
1417 1417 self._progbar.progress(topic, pos, item=item, unit=unit,
1418 1418 total=total)
1419 1419 if pos is None or not self.configbool('progress', 'debug'):
1420 1420 return
1421 1421
1422 1422 if unit:
1423 1423 unit = ' ' + unit
1424 1424 if item:
1425 1425 item = ' ' + item
1426 1426
1427 1427 if total:
1428 1428 pct = 100.0 * pos / total
1429 1429 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1430 1430 % (topic, item, pos, total, unit, pct))
1431 1431 else:
1432 1432 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1433 1433
1434 1434 def log(self, service, *msg, **opts):
1435 1435 '''hook for logging facility extensions
1436 1436
1437 1437 service should be a readily-identifiable subsystem, which will
1438 1438 allow filtering.
1439 1439
1440 1440 *msg should be a newline-terminated format string to log, and
1441 1441 then any values to %-format into that format string.
1442 1442
1443 1443 **opts currently has no defined meanings.
1444 1444 '''
1445 1445
1446 1446 def label(self, msg, label):
1447 1447 '''style msg based on supplied label
1448 1448
1449 1449 If some color mode is enabled, this will add the necessary control
1450 1450 characters to apply such color. In addition, 'debug' color mode adds
1451 1451 markup showing which label affects a piece of text.
1452 1452
1453 1453 ui.write(s, 'label') is equivalent to
1454 1454 ui.write(ui.label(s, 'label')).
1455 1455 '''
1456 1456 if self._colormode is not None:
1457 1457 return color.colorlabel(self, msg, label)
1458 1458 return msg
1459 1459
1460 1460 def develwarn(self, msg, stacklevel=1, config=None):
1461 1461 """issue a developer warning message
1462 1462
1463 1463 Use 'stacklevel' to report the offender some layers further up in the
1464 1464 stack.
1465 1465 """
1466 1466 if not self.configbool('devel', 'all-warnings'):
1467 1467 if config is not None and not self.configbool('devel', config):
1468 1468 return
1469 1469 msg = 'devel-warn: ' + msg
1470 1470 stacklevel += 1 # get in develwarn
1471 1471 if self.tracebackflag:
1472 1472 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1473 1473 self.log('develwarn', '%s at:\n%s' %
1474 1474 (msg, ''.join(util.getstackframes(stacklevel))))
1475 1475 else:
1476 1476 curframe = inspect.currentframe()
1477 1477 calframe = inspect.getouterframes(curframe, 2)
1478 1478 self.write_err('%s at: %s:%s (%s)\n'
1479 1479 % ((msg,) + calframe[stacklevel][1:4]))
1480 1480 self.log('develwarn', '%s at: %s:%s (%s)\n',
1481 1481 msg, *calframe[stacklevel][1:4])
1482 1482 curframe = calframe = None # avoid cycles
1483 1483
1484 1484 def deprecwarn(self, msg, version):
1485 1485 """issue a deprecation warning
1486 1486
1487 1487 - msg: message explaining what is deprecated and how to upgrade,
1488 1488 - version: last version where the API will be supported,
1489 1489 """
1490 1490 if not (self.configbool('devel', 'all-warnings')
1491 1491 or self.configbool('devel', 'deprec-warn')):
1492 1492 return
1493 1493 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1494 1494 " update your code.)") % version
1495 1495 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1496 1496
1497 1497 def exportableenviron(self):
1498 1498 """The environment variables that are safe to export, e.g. through
1499 1499 hgweb.
1500 1500 """
1501 1501 return self._exportableenviron
1502 1502
1503 1503 @contextlib.contextmanager
1504 1504 def configoverride(self, overrides, source=""):
1505 1505 """Context manager for temporary config overrides
1506 1506 `overrides` must be a dict of the following structure:
1507 1507 {(section, name) : value}"""
1508 1508 backups = {}
1509 1509 try:
1510 1510 for (section, name), value in overrides.items():
1511 1511 backups[(section, name)] = self.backupconfig(section, name)
1512 1512 self.setconfig(section, name, value, source)
1513 1513 yield
1514 1514 finally:
1515 1515 for __, backup in backups.items():
1516 1516 self.restoreconfig(backup)
1517 1517 # just restoring ui.quiet config to the previous value is not enough
1518 1518 # as it does not update ui.quiet class member
1519 1519 if ('ui', 'quiet') in overrides:
1520 1520 self.fixconfig(section='ui')
1521 1521
1522 1522 class paths(dict):
1523 1523 """Represents a collection of paths and their configs.
1524 1524
1525 1525 Data is initially derived from ui instances and the config files they have
1526 1526 loaded.
1527 1527 """
1528 1528 def __init__(self, ui):
1529 1529 dict.__init__(self)
1530 1530
1531 1531 for name, loc in ui.configitems('paths', ignoresub=True):
1532 1532 # No location is the same as not existing.
1533 1533 if not loc:
1534 1534 continue
1535 1535 loc, sub = ui.configsuboptions('paths', name)
1536 1536 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1537 1537
1538 1538 def getpath(self, name, default=None):
1539 1539 """Return a ``path`` from a string, falling back to default.
1540 1540
1541 1541 ``name`` can be a named path or locations. Locations are filesystem
1542 1542 paths or URIs.
1543 1543
1544 1544 Returns None if ``name`` is not a registered path, a URI, or a local
1545 1545 path to a repo.
1546 1546 """
1547 1547 # Only fall back to default if no path was requested.
1548 1548 if name is None:
1549 1549 if not default:
1550 1550 default = ()
1551 1551 elif not isinstance(default, (tuple, list)):
1552 1552 default = (default,)
1553 1553 for k in default:
1554 1554 try:
1555 1555 return self[k]
1556 1556 except KeyError:
1557 1557 continue
1558 1558 return None
1559 1559
1560 1560 # Most likely empty string.
1561 1561 # This may need to raise in the future.
1562 1562 if not name:
1563 1563 return None
1564 1564
1565 1565 try:
1566 1566 return self[name]
1567 1567 except KeyError:
1568 1568 # Try to resolve as a local path or URI.
1569 1569 try:
1570 1570 # We don't pass sub-options in, so no need to pass ui instance.
1571 1571 return path(None, None, rawloc=name)
1572 1572 except ValueError:
1573 1573 raise error.RepoError(_('repository %s does not exist') %
1574 1574 name)
1575 1575
1576 1576 _pathsuboptions = {}
1577 1577
1578 1578 def pathsuboption(option, attr):
1579 1579 """Decorator used to declare a path sub-option.
1580 1580
1581 1581 Arguments are the sub-option name and the attribute it should set on
1582 1582 ``path`` instances.
1583 1583
1584 1584 The decorated function will receive as arguments a ``ui`` instance,
1585 1585 ``path`` instance, and the string value of this option from the config.
1586 1586 The function should return the value that will be set on the ``path``
1587 1587 instance.
1588 1588
1589 1589 This decorator can be used to perform additional verification of
1590 1590 sub-options and to change the type of sub-options.
1591 1591 """
1592 1592 def register(func):
1593 1593 _pathsuboptions[option] = (attr, func)
1594 1594 return func
1595 1595 return register
1596 1596
1597 1597 @pathsuboption('pushurl', 'pushloc')
1598 1598 def pushurlpathoption(ui, path, value):
1599 1599 u = util.url(value)
1600 1600 # Actually require a URL.
1601 1601 if not u.scheme:
1602 1602 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1603 1603 return None
1604 1604
1605 1605 # Don't support the #foo syntax in the push URL to declare branch to
1606 1606 # push.
1607 1607 if u.fragment:
1608 1608 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1609 1609 'ignoring)\n') % path.name)
1610 1610 u.fragment = None
1611 1611
1612 1612 return str(u)
1613 1613
1614 1614 @pathsuboption('pushrev', 'pushrev')
1615 1615 def pushrevpathoption(ui, path, value):
1616 1616 return value
1617 1617
1618 1618 class path(object):
1619 1619 """Represents an individual path and its configuration."""
1620 1620
1621 1621 def __init__(self, ui, name, rawloc=None, suboptions=None):
1622 1622 """Construct a path from its config options.
1623 1623
1624 1624 ``ui`` is the ``ui`` instance the path is coming from.
1625 1625 ``name`` is the symbolic name of the path.
1626 1626 ``rawloc`` is the raw location, as defined in the config.
1627 1627 ``pushloc`` is the raw locations pushes should be made to.
1628 1628
1629 1629 If ``name`` is not defined, we require that the location be a) a local
1630 1630 filesystem path with a .hg directory or b) a URL. If not,
1631 1631 ``ValueError`` is raised.
1632 1632 """
1633 1633 if not rawloc:
1634 1634 raise ValueError('rawloc must be defined')
1635 1635
1636 1636 # Locations may define branches via syntax <base>#<branch>.
1637 1637 u = util.url(rawloc)
1638 1638 branch = None
1639 1639 if u.fragment:
1640 1640 branch = u.fragment
1641 1641 u.fragment = None
1642 1642
1643 1643 self.url = u
1644 1644 self.branch = branch
1645 1645
1646 1646 self.name = name
1647 1647 self.rawloc = rawloc
1648 1648 self.loc = '%s' % u
1649 1649
1650 1650 # When given a raw location but not a symbolic name, validate the
1651 1651 # location is valid.
1652 1652 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1653 1653 raise ValueError('location is not a URL or path to a local '
1654 1654 'repo: %s' % rawloc)
1655 1655
1656 1656 suboptions = suboptions or {}
1657 1657
1658 1658 # Now process the sub-options. If a sub-option is registered, its
1659 1659 # attribute will always be present. The value will be None if there
1660 1660 # was no valid sub-option.
1661 1661 for suboption, (attr, func) in _pathsuboptions.iteritems():
1662 1662 if suboption not in suboptions:
1663 1663 setattr(self, attr, None)
1664 1664 continue
1665 1665
1666 1666 value = func(ui, self, suboptions[suboption])
1667 1667 setattr(self, attr, value)
1668 1668
1669 1669 def _isvalidlocalpath(self, path):
1670 1670 """Returns True if the given path is a potentially valid repository.
1671 1671 This is its own function so that extensions can change the definition of
1672 1672 'valid' in this case (like when pulling from a git repo into a hg
1673 1673 one)."""
1674 1674 return os.path.isdir(os.path.join(path, '.hg'))
1675 1675
1676 1676 @property
1677 1677 def suboptions(self):
1678 1678 """Return sub-options and their values for this path.
1679 1679
1680 1680 This is intended to be used for presentation purposes.
1681 1681 """
1682 1682 d = {}
1683 1683 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1684 1684 value = getattr(self, attr)
1685 1685 if value is not None:
1686 1686 d[subopt] = value
1687 1687 return d
1688 1688
1689 1689 # we instantiate one globally shared progress bar to avoid
1690 1690 # competing progress bars when multiple UI objects get created
1691 1691 _progresssingleton = None
1692 1692
1693 1693 def getprogbar(ui):
1694 1694 global _progresssingleton
1695 1695 if _progresssingleton is None:
1696 1696 # passing 'ui' object to the singleton is fishy,
1697 1697 # this is how the extension used to work but feel free to rework it.
1698 1698 _progresssingleton = progress.progbar(ui)
1699 1699 return _progresssingleton
@@ -1,3748 +1,3731 b''
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from __future__ import absolute_import
17 17
18 18 import bz2
19 19 import calendar
20 20 import codecs
21 21 import collections
22 22 import datetime
23 23 import errno
24 24 import gc
25 25 import hashlib
26 26 import imp
27 27 import os
28 28 import platform as pyplatform
29 29 import re as remod
30 30 import shutil
31 31 import signal
32 32 import socket
33 33 import stat
34 34 import string
35 35 import subprocess
36 36 import sys
37 37 import tempfile
38 38 import textwrap
39 39 import time
40 40 import traceback
41 41 import warnings
42 42 import zlib
43 43
44 44 from . import (
45 45 encoding,
46 46 error,
47 47 i18n,
48 48 policy,
49 49 pycompat,
50 50 )
51 51
52 52 base85 = policy.importmod(r'base85')
53 53 osutil = policy.importmod(r'osutil')
54 54 parsers = policy.importmod(r'parsers')
55 55
56 56 b85decode = base85.b85decode
57 57 b85encode = base85.b85encode
58 58
59 59 cookielib = pycompat.cookielib
60 60 empty = pycompat.empty
61 61 httplib = pycompat.httplib
62 62 httpserver = pycompat.httpserver
63 63 pickle = pycompat.pickle
64 64 queue = pycompat.queue
65 65 socketserver = pycompat.socketserver
66 66 stderr = pycompat.stderr
67 67 stdin = pycompat.stdin
68 68 stdout = pycompat.stdout
69 69 stringio = pycompat.stringio
70 70 urlerr = pycompat.urlerr
71 71 urlreq = pycompat.urlreq
72 72 xmlrpclib = pycompat.xmlrpclib
73 73
74 74 def isatty(fp):
75 75 try:
76 76 return fp.isatty()
77 77 except AttributeError:
78 78 return False
79 79
80 80 # glibc determines buffering on first write to stdout - if we replace a TTY
81 81 # destined stdout with a pipe destined stdout (e.g. pager), we want line
82 82 # buffering
83 83 if isatty(stdout):
84 84 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
85 85
86 86 if pycompat.osname == 'nt':
87 87 from . import windows as platform
88 88 stdout = platform.winstdout(stdout)
89 89 else:
90 90 from . import posix as platform
91 91
92 92 _ = i18n._
93 93
94 94 bindunixsocket = platform.bindunixsocket
95 95 cachestat = platform.cachestat
96 96 checkexec = platform.checkexec
97 97 checklink = platform.checklink
98 98 copymode = platform.copymode
99 99 executablepath = platform.executablepath
100 100 expandglobs = platform.expandglobs
101 101 explainexit = platform.explainexit
102 102 findexe = platform.findexe
103 103 gethgcmd = platform.gethgcmd
104 104 getuser = platform.getuser
105 105 getpid = os.getpid
106 106 groupmembers = platform.groupmembers
107 107 groupname = platform.groupname
108 108 hidewindow = platform.hidewindow
109 109 isexec = platform.isexec
110 110 isowner = platform.isowner
111 111 listdir = osutil.listdir
112 112 localpath = platform.localpath
113 113 lookupreg = platform.lookupreg
114 114 makedir = platform.makedir
115 115 nlinks = platform.nlinks
116 116 normpath = platform.normpath
117 117 normcase = platform.normcase
118 118 normcasespec = platform.normcasespec
119 119 normcasefallback = platform.normcasefallback
120 120 openhardlinks = platform.openhardlinks
121 121 oslink = platform.oslink
122 122 parsepatchoutput = platform.parsepatchoutput
123 123 pconvert = platform.pconvert
124 124 poll = platform.poll
125 125 popen = platform.popen
126 126 posixfile = platform.posixfile
127 127 quotecommand = platform.quotecommand
128 128 readpipe = platform.readpipe
129 129 rename = platform.rename
130 130 removedirs = platform.removedirs
131 131 samedevice = platform.samedevice
132 132 samefile = platform.samefile
133 133 samestat = platform.samestat
134 134 setbinary = platform.setbinary
135 135 setflags = platform.setflags
136 136 setsignalhandler = platform.setsignalhandler
137 137 shellquote = platform.shellquote
138 138 spawndetached = platform.spawndetached
139 139 split = platform.split
140 140 sshargs = platform.sshargs
141 141 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
142 142 statisexec = platform.statisexec
143 143 statislink = platform.statislink
144 144 testpid = platform.testpid
145 145 umask = platform.umask
146 146 unlink = platform.unlink
147 147 username = platform.username
148 148
149 149 try:
150 150 recvfds = osutil.recvfds
151 151 except AttributeError:
152 152 pass
153 153 try:
154 154 setprocname = osutil.setprocname
155 155 except AttributeError:
156 156 pass
157 157
158 158 # Python compatibility
159 159
160 160 _notset = object()
161 161
162 162 # disable Python's problematic floating point timestamps (issue4836)
163 163 # (Python hypocritically says you shouldn't change this behavior in
164 164 # libraries, and sure enough Mercurial is not a library.)
165 165 os.stat_float_times(False)
166 166
167 167 def safehasattr(thing, attr):
168 168 return getattr(thing, attr, _notset) is not _notset
169 169
170 170 def bitsfrom(container):
171 171 bits = 0
172 172 for bit in container:
173 173 bits |= bit
174 174 return bits
175 175
176 176 # python 2.6 still have deprecation warning enabled by default. We do not want
177 177 # to display anything to standard user so detect if we are running test and
178 178 # only use python deprecation warning in this case.
179 179 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
180 180 if _dowarn:
181 181 # explicitly unfilter our warning for python 2.7
182 182 #
183 183 # The option of setting PYTHONWARNINGS in the test runner was investigated.
184 184 # However, module name set through PYTHONWARNINGS was exactly matched, so
185 185 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
186 186 # makes the whole PYTHONWARNINGS thing useless for our usecase.
187 187 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
188 188 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
189 189 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
190 190
191 191 def nouideprecwarn(msg, version, stacklevel=1):
192 192 """Issue an python native deprecation warning
193 193
194 194 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
195 195 """
196 196 if _dowarn:
197 197 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
198 198 " update your code.)") % version
199 199 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
200 200
201 201 DIGESTS = {
202 202 'md5': hashlib.md5,
203 203 'sha1': hashlib.sha1,
204 204 'sha512': hashlib.sha512,
205 205 }
206 206 # List of digest types from strongest to weakest
207 207 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
208 208
209 209 for k in DIGESTS_BY_STRENGTH:
210 210 assert k in DIGESTS
211 211
212 212 class digester(object):
213 213 """helper to compute digests.
214 214
215 215 This helper can be used to compute one or more digests given their name.
216 216
217 217 >>> d = digester(['md5', 'sha1'])
218 218 >>> d.update('foo')
219 219 >>> [k for k in sorted(d)]
220 220 ['md5', 'sha1']
221 221 >>> d['md5']
222 222 'acbd18db4cc2f85cedef654fccc4a4d8'
223 223 >>> d['sha1']
224 224 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
225 225 >>> digester.preferred(['md5', 'sha1'])
226 226 'sha1'
227 227 """
228 228
229 229 def __init__(self, digests, s=''):
230 230 self._hashes = {}
231 231 for k in digests:
232 232 if k not in DIGESTS:
233 233 raise Abort(_('unknown digest type: %s') % k)
234 234 self._hashes[k] = DIGESTS[k]()
235 235 if s:
236 236 self.update(s)
237 237
238 238 def update(self, data):
239 239 for h in self._hashes.values():
240 240 h.update(data)
241 241
242 242 def __getitem__(self, key):
243 243 if key not in DIGESTS:
244 244 raise Abort(_('unknown digest type: %s') % k)
245 245 return self._hashes[key].hexdigest()
246 246
247 247 def __iter__(self):
248 248 return iter(self._hashes)
249 249
250 250 @staticmethod
251 251 def preferred(supported):
252 252 """returns the strongest digest type in both supported and DIGESTS."""
253 253
254 254 for k in DIGESTS_BY_STRENGTH:
255 255 if k in supported:
256 256 return k
257 257 return None
258 258
259 259 class digestchecker(object):
260 260 """file handle wrapper that additionally checks content against a given
261 261 size and digests.
262 262
263 263 d = digestchecker(fh, size, {'md5': '...'})
264 264
265 265 When multiple digests are given, all of them are validated.
266 266 """
267 267
268 268 def __init__(self, fh, size, digests):
269 269 self._fh = fh
270 270 self._size = size
271 271 self._got = 0
272 272 self._digests = dict(digests)
273 273 self._digester = digester(self._digests.keys())
274 274
275 275 def read(self, length=-1):
276 276 content = self._fh.read(length)
277 277 self._digester.update(content)
278 278 self._got += len(content)
279 279 return content
280 280
281 281 def validate(self):
282 282 if self._size != self._got:
283 283 raise Abort(_('size mismatch: expected %d, got %d') %
284 284 (self._size, self._got))
285 285 for k, v in self._digests.items():
286 286 if v != self._digester[k]:
287 287 # i18n: first parameter is a digest name
288 288 raise Abort(_('%s mismatch: expected %s, got %s') %
289 289 (k, v, self._digester[k]))
290 290
291 291 try:
292 292 buffer = buffer
293 293 except NameError:
294 294 if not pycompat.ispy3:
295 295 def buffer(sliceable, offset=0, length=None):
296 296 if length is not None:
297 297 return sliceable[offset:offset + length]
298 298 return sliceable[offset:]
299 299 else:
300 300 def buffer(sliceable, offset=0, length=None):
301 301 if length is not None:
302 302 return memoryview(sliceable)[offset:offset + length]
303 303 return memoryview(sliceable)[offset:]
304 304
305 305 closefds = pycompat.osname == 'posix'
306 306
307 307 _chunksize = 4096
308 308
309 309 class bufferedinputpipe(object):
310 310 """a manually buffered input pipe
311 311
312 312 Python will not let us use buffered IO and lazy reading with 'polling' at
313 313 the same time. We cannot probe the buffer state and select will not detect
314 314 that data are ready to read if they are already buffered.
315 315
316 316 This class let us work around that by implementing its own buffering
317 317 (allowing efficient readline) while offering a way to know if the buffer is
318 318 empty from the output (allowing collaboration of the buffer with polling).
319 319
320 320 This class lives in the 'util' module because it makes use of the 'os'
321 321 module from the python stdlib.
322 322 """
323 323
324 324 def __init__(self, input):
325 325 self._input = input
326 326 self._buffer = []
327 327 self._eof = False
328 328 self._lenbuf = 0
329 329
330 330 @property
331 331 def hasbuffer(self):
332 332 """True is any data is currently buffered
333 333
334 334 This will be used externally a pre-step for polling IO. If there is
335 335 already data then no polling should be set in place."""
336 336 return bool(self._buffer)
337 337
338 338 @property
339 339 def closed(self):
340 340 return self._input.closed
341 341
342 342 def fileno(self):
343 343 return self._input.fileno()
344 344
345 345 def close(self):
346 346 return self._input.close()
347 347
348 348 def read(self, size):
349 349 while (not self._eof) and (self._lenbuf < size):
350 350 self._fillbuffer()
351 351 return self._frombuffer(size)
352 352
353 353 def readline(self, *args, **kwargs):
354 354 if 1 < len(self._buffer):
355 355 # this should not happen because both read and readline end with a
356 356 # _frombuffer call that collapse it.
357 357 self._buffer = [''.join(self._buffer)]
358 358 self._lenbuf = len(self._buffer[0])
359 359 lfi = -1
360 360 if self._buffer:
361 361 lfi = self._buffer[-1].find('\n')
362 362 while (not self._eof) and lfi < 0:
363 363 self._fillbuffer()
364 364 if self._buffer:
365 365 lfi = self._buffer[-1].find('\n')
366 366 size = lfi + 1
367 367 if lfi < 0: # end of file
368 368 size = self._lenbuf
369 369 elif 1 < len(self._buffer):
370 370 # we need to take previous chunks into account
371 371 size += self._lenbuf - len(self._buffer[-1])
372 372 return self._frombuffer(size)
373 373
374 374 def _frombuffer(self, size):
375 375 """return at most 'size' data from the buffer
376 376
377 377 The data are removed from the buffer."""
378 378 if size == 0 or not self._buffer:
379 379 return ''
380 380 buf = self._buffer[0]
381 381 if 1 < len(self._buffer):
382 382 buf = ''.join(self._buffer)
383 383
384 384 data = buf[:size]
385 385 buf = buf[len(data):]
386 386 if buf:
387 387 self._buffer = [buf]
388 388 self._lenbuf = len(buf)
389 389 else:
390 390 self._buffer = []
391 391 self._lenbuf = 0
392 392 return data
393 393
394 394 def _fillbuffer(self):
395 395 """read data to the buffer"""
396 396 data = os.read(self._input.fileno(), _chunksize)
397 397 if not data:
398 398 self._eof = True
399 399 else:
400 400 self._lenbuf += len(data)
401 401 self._buffer.append(data)
402 402
403 403 def popen2(cmd, env=None, newlines=False):
404 404 # Setting bufsize to -1 lets the system decide the buffer size.
405 405 # The default for bufsize is 0, meaning unbuffered. This leads to
406 406 # poor performance on Mac OS X: http://bugs.python.org/issue4194
407 407 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
408 408 close_fds=closefds,
409 409 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
410 410 universal_newlines=newlines,
411 411 env=env)
412 412 return p.stdin, p.stdout
413 413
414 414 def popen3(cmd, env=None, newlines=False):
415 415 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
416 416 return stdin, stdout, stderr
417 417
418 418 def popen4(cmd, env=None, newlines=False, bufsize=-1):
419 419 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
420 420 close_fds=closefds,
421 421 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
422 422 stderr=subprocess.PIPE,
423 423 universal_newlines=newlines,
424 424 env=env)
425 425 return p.stdin, p.stdout, p.stderr, p
426 426
427 427 def version():
428 428 """Return version information if available."""
429 429 try:
430 430 from . import __version__
431 431 return __version__.version
432 432 except ImportError:
433 433 return 'unknown'
434 434
435 435 def versiontuple(v=None, n=4):
436 436 """Parses a Mercurial version string into an N-tuple.
437 437
438 438 The version string to be parsed is specified with the ``v`` argument.
439 439 If it isn't defined, the current Mercurial version string will be parsed.
440 440
441 441 ``n`` can be 2, 3, or 4. Here is how some version strings map to
442 442 returned values:
443 443
444 444 >>> v = '3.6.1+190-df9b73d2d444'
445 445 >>> versiontuple(v, 2)
446 446 (3, 6)
447 447 >>> versiontuple(v, 3)
448 448 (3, 6, 1)
449 449 >>> versiontuple(v, 4)
450 450 (3, 6, 1, '190-df9b73d2d444')
451 451
452 452 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
453 453 (3, 6, 1, '190-df9b73d2d444+20151118')
454 454
455 455 >>> v = '3.6'
456 456 >>> versiontuple(v, 2)
457 457 (3, 6)
458 458 >>> versiontuple(v, 3)
459 459 (3, 6, None)
460 460 >>> versiontuple(v, 4)
461 461 (3, 6, None, None)
462 462
463 463 >>> v = '3.9-rc'
464 464 >>> versiontuple(v, 2)
465 465 (3, 9)
466 466 >>> versiontuple(v, 3)
467 467 (3, 9, None)
468 468 >>> versiontuple(v, 4)
469 469 (3, 9, None, 'rc')
470 470
471 471 >>> v = '3.9-rc+2-02a8fea4289b'
472 472 >>> versiontuple(v, 2)
473 473 (3, 9)
474 474 >>> versiontuple(v, 3)
475 475 (3, 9, None)
476 476 >>> versiontuple(v, 4)
477 477 (3, 9, None, 'rc+2-02a8fea4289b')
478 478 """
479 479 if not v:
480 480 v = version()
481 481 parts = remod.split('[\+-]', v, 1)
482 482 if len(parts) == 1:
483 483 vparts, extra = parts[0], None
484 484 else:
485 485 vparts, extra = parts
486 486
487 487 vints = []
488 488 for i in vparts.split('.'):
489 489 try:
490 490 vints.append(int(i))
491 491 except ValueError:
492 492 break
493 493 # (3, 6) -> (3, 6, None)
494 494 while len(vints) < 3:
495 495 vints.append(None)
496 496
497 497 if n == 2:
498 498 return (vints[0], vints[1])
499 499 if n == 3:
500 500 return (vints[0], vints[1], vints[2])
501 501 if n == 4:
502 502 return (vints[0], vints[1], vints[2], extra)
503 503
504 504 # used by parsedate
505 505 defaultdateformats = (
506 506 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
507 507 '%Y-%m-%dT%H:%M', # without seconds
508 508 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
509 509 '%Y-%m-%dT%H%M', # without seconds
510 510 '%Y-%m-%d %H:%M:%S', # our common legal variant
511 511 '%Y-%m-%d %H:%M', # without seconds
512 512 '%Y-%m-%d %H%M%S', # without :
513 513 '%Y-%m-%d %H%M', # without seconds
514 514 '%Y-%m-%d %I:%M:%S%p',
515 515 '%Y-%m-%d %H:%M',
516 516 '%Y-%m-%d %I:%M%p',
517 517 '%Y-%m-%d',
518 518 '%m-%d',
519 519 '%m/%d',
520 520 '%m/%d/%y',
521 521 '%m/%d/%Y',
522 522 '%a %b %d %H:%M:%S %Y',
523 523 '%a %b %d %I:%M:%S%p %Y',
524 524 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
525 525 '%b %d %H:%M:%S %Y',
526 526 '%b %d %I:%M:%S%p %Y',
527 527 '%b %d %H:%M:%S',
528 528 '%b %d %I:%M:%S%p',
529 529 '%b %d %H:%M',
530 530 '%b %d %I:%M%p',
531 531 '%b %d %Y',
532 532 '%b %d',
533 533 '%H:%M:%S',
534 534 '%I:%M:%S%p',
535 535 '%H:%M',
536 536 '%I:%M%p',
537 537 )
538 538
539 539 extendeddateformats = defaultdateformats + (
540 540 "%Y",
541 541 "%Y-%m",
542 542 "%b",
543 543 "%b %Y",
544 544 )
545 545
546 546 def cachefunc(func):
547 547 '''cache the result of function calls'''
548 548 # XXX doesn't handle keywords args
549 549 if func.__code__.co_argcount == 0:
550 550 cache = []
551 551 def f():
552 552 if len(cache) == 0:
553 553 cache.append(func())
554 554 return cache[0]
555 555 return f
556 556 cache = {}
557 557 if func.__code__.co_argcount == 1:
558 558 # we gain a small amount of time because
559 559 # we don't need to pack/unpack the list
560 560 def f(arg):
561 561 if arg not in cache:
562 562 cache[arg] = func(arg)
563 563 return cache[arg]
564 564 else:
565 565 def f(*args):
566 566 if args not in cache:
567 567 cache[args] = func(*args)
568 568 return cache[args]
569 569
570 570 return f
571 571
572 572 class sortdict(collections.OrderedDict):
573 573 '''a simple sorted dictionary
574 574
575 575 >>> d1 = sortdict([('a', 0), ('b', 1)])
576 576 >>> d2 = d1.copy()
577 577 >>> d2
578 578 sortdict([('a', 0), ('b', 1)])
579 579 >>> d2.update([('a', 2)])
580 580 >>> d2.keys() # should still be in last-set order
581 581 ['b', 'a']
582 582 '''
583 583
584 584 def __setitem__(self, key, value):
585 585 if key in self:
586 586 del self[key]
587 587 super(sortdict, self).__setitem__(key, value)
588 588
589 589 class _lrucachenode(object):
590 590 """A node in a doubly linked list.
591 591
592 592 Holds a reference to nodes on either side as well as a key-value
593 593 pair for the dictionary entry.
594 594 """
595 595 __slots__ = (u'next', u'prev', u'key', u'value')
596 596
597 597 def __init__(self):
598 598 self.next = None
599 599 self.prev = None
600 600
601 601 self.key = _notset
602 602 self.value = None
603 603
604 604 def markempty(self):
605 605 """Mark the node as emptied."""
606 606 self.key = _notset
607 607
608 608 class lrucachedict(object):
609 609 """Dict that caches most recent accesses and sets.
610 610
611 611 The dict consists of an actual backing dict - indexed by original
612 612 key - and a doubly linked circular list defining the order of entries in
613 613 the cache.
614 614
615 615 The head node is the newest entry in the cache. If the cache is full,
616 616 we recycle head.prev and make it the new head. Cache accesses result in
617 617 the node being moved to before the existing head and being marked as the
618 618 new head node.
619 619 """
620 620 def __init__(self, max):
621 621 self._cache = {}
622 622
623 623 self._head = head = _lrucachenode()
624 624 head.prev = head
625 625 head.next = head
626 626 self._size = 1
627 627 self._capacity = max
628 628
629 629 def __len__(self):
630 630 return len(self._cache)
631 631
632 632 def __contains__(self, k):
633 633 return k in self._cache
634 634
635 635 def __iter__(self):
636 636 # We don't have to iterate in cache order, but why not.
637 637 n = self._head
638 638 for i in range(len(self._cache)):
639 639 yield n.key
640 640 n = n.next
641 641
642 642 def __getitem__(self, k):
643 643 node = self._cache[k]
644 644 self._movetohead(node)
645 645 return node.value
646 646
647 647 def __setitem__(self, k, v):
648 648 node = self._cache.get(k)
649 649 # Replace existing value and mark as newest.
650 650 if node is not None:
651 651 node.value = v
652 652 self._movetohead(node)
653 653 return
654 654
655 655 if self._size < self._capacity:
656 656 node = self._addcapacity()
657 657 else:
658 658 # Grab the last/oldest item.
659 659 node = self._head.prev
660 660
661 661 # At capacity. Kill the old entry.
662 662 if node.key is not _notset:
663 663 del self._cache[node.key]
664 664
665 665 node.key = k
666 666 node.value = v
667 667 self._cache[k] = node
668 668 # And mark it as newest entry. No need to adjust order since it
669 669 # is already self._head.prev.
670 670 self._head = node
671 671
672 672 def __delitem__(self, k):
673 673 node = self._cache.pop(k)
674 674 node.markempty()
675 675
676 676 # Temporarily mark as newest item before re-adjusting head to make
677 677 # this node the oldest item.
678 678 self._movetohead(node)
679 679 self._head = node.next
680 680
681 681 # Additional dict methods.
682 682
683 683 def get(self, k, default=None):
684 684 try:
685 685 return self._cache[k].value
686 686 except KeyError:
687 687 return default
688 688
689 689 def clear(self):
690 690 n = self._head
691 691 while n.key is not _notset:
692 692 n.markempty()
693 693 n = n.next
694 694
695 695 self._cache.clear()
696 696
697 697 def copy(self):
698 698 result = lrucachedict(self._capacity)
699 699 n = self._head.prev
700 700 # Iterate in oldest-to-newest order, so the copy has the right ordering
701 701 for i in range(len(self._cache)):
702 702 result[n.key] = n.value
703 703 n = n.prev
704 704 return result
705 705
706 706 def _movetohead(self, node):
707 707 """Mark a node as the newest, making it the new head.
708 708
709 709 When a node is accessed, it becomes the freshest entry in the LRU
710 710 list, which is denoted by self._head.
711 711
712 712 Visually, let's make ``N`` the new head node (* denotes head):
713 713
714 714 previous/oldest <-> head <-> next/next newest
715 715
716 716 ----<->--- A* ---<->-----
717 717 | |
718 718 E <-> D <-> N <-> C <-> B
719 719
720 720 To:
721 721
722 722 ----<->--- N* ---<->-----
723 723 | |
724 724 E <-> D <-> C <-> B <-> A
725 725
726 726 This requires the following moves:
727 727
728 728 C.next = D (node.prev.next = node.next)
729 729 D.prev = C (node.next.prev = node.prev)
730 730 E.next = N (head.prev.next = node)
731 731 N.prev = E (node.prev = head.prev)
732 732 N.next = A (node.next = head)
733 733 A.prev = N (head.prev = node)
734 734 """
735 735 head = self._head
736 736 # C.next = D
737 737 node.prev.next = node.next
738 738 # D.prev = C
739 739 node.next.prev = node.prev
740 740 # N.prev = E
741 741 node.prev = head.prev
742 742 # N.next = A
743 743 # It is tempting to do just "head" here, however if node is
744 744 # adjacent to head, this will do bad things.
745 745 node.next = head.prev.next
746 746 # E.next = N
747 747 node.next.prev = node
748 748 # A.prev = N
749 749 node.prev.next = node
750 750
751 751 self._head = node
752 752
753 753 def _addcapacity(self):
754 754 """Add a node to the circular linked list.
755 755
756 756 The new node is inserted before the head node.
757 757 """
758 758 head = self._head
759 759 node = _lrucachenode()
760 760 head.prev.next = node
761 761 node.prev = head.prev
762 762 node.next = head
763 763 head.prev = node
764 764 self._size += 1
765 765 return node
766 766
767 767 def lrucachefunc(func):
768 768 '''cache most recent results of function calls'''
769 769 cache = {}
770 770 order = collections.deque()
771 771 if func.__code__.co_argcount == 1:
772 772 def f(arg):
773 773 if arg not in cache:
774 774 if len(cache) > 20:
775 775 del cache[order.popleft()]
776 776 cache[arg] = func(arg)
777 777 else:
778 778 order.remove(arg)
779 779 order.append(arg)
780 780 return cache[arg]
781 781 else:
782 782 def f(*args):
783 783 if args not in cache:
784 784 if len(cache) > 20:
785 785 del cache[order.popleft()]
786 786 cache[args] = func(*args)
787 787 else:
788 788 order.remove(args)
789 789 order.append(args)
790 790 return cache[args]
791 791
792 792 return f
793 793
794 794 class propertycache(object):
795 795 def __init__(self, func):
796 796 self.func = func
797 797 self.name = func.__name__
798 798 def __get__(self, obj, type=None):
799 799 result = self.func(obj)
800 800 self.cachevalue(obj, result)
801 801 return result
802 802
803 803 def cachevalue(self, obj, value):
804 804 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
805 805 obj.__dict__[self.name] = value
806 806
807 807 def pipefilter(s, cmd):
808 808 '''filter string S through command CMD, returning its output'''
809 809 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
810 810 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
811 811 pout, perr = p.communicate(s)
812 812 return pout
813 813
814 814 def tempfilter(s, cmd):
815 815 '''filter string S through a pair of temporary files with CMD.
816 816 CMD is used as a template to create the real command to be run,
817 817 with the strings INFILE and OUTFILE replaced by the real names of
818 818 the temporary files generated.'''
819 819 inname, outname = None, None
820 820 try:
821 821 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
822 822 fp = os.fdopen(infd, pycompat.sysstr('wb'))
823 823 fp.write(s)
824 824 fp.close()
825 825 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
826 826 os.close(outfd)
827 827 cmd = cmd.replace('INFILE', inname)
828 828 cmd = cmd.replace('OUTFILE', outname)
829 829 code = os.system(cmd)
830 830 if pycompat.sysplatform == 'OpenVMS' and code & 1:
831 831 code = 0
832 832 if code:
833 833 raise Abort(_("command '%s' failed: %s") %
834 834 (cmd, explainexit(code)))
835 835 return readfile(outname)
836 836 finally:
837 837 try:
838 838 if inname:
839 839 os.unlink(inname)
840 840 except OSError:
841 841 pass
842 842 try:
843 843 if outname:
844 844 os.unlink(outname)
845 845 except OSError:
846 846 pass
847 847
848 848 filtertable = {
849 849 'tempfile:': tempfilter,
850 850 'pipe:': pipefilter,
851 851 }
852 852
853 853 def filter(s, cmd):
854 854 "filter a string through a command that transforms its input to its output"
855 855 for name, fn in filtertable.iteritems():
856 856 if cmd.startswith(name):
857 857 return fn(s, cmd[len(name):].lstrip())
858 858 return pipefilter(s, cmd)
859 859
860 860 def binary(s):
861 861 """return true if a string is binary data"""
862 862 return bool(s and '\0' in s)
863 863
864 864 def increasingchunks(source, min=1024, max=65536):
865 865 '''return no less than min bytes per chunk while data remains,
866 866 doubling min after each chunk until it reaches max'''
867 867 def log2(x):
868 868 if not x:
869 869 return 0
870 870 i = 0
871 871 while x:
872 872 x >>= 1
873 873 i += 1
874 874 return i - 1
875 875
876 876 buf = []
877 877 blen = 0
878 878 for chunk in source:
879 879 buf.append(chunk)
880 880 blen += len(chunk)
881 881 if blen >= min:
882 882 if min < max:
883 883 min = min << 1
884 884 nmin = 1 << log2(blen)
885 885 if nmin > min:
886 886 min = nmin
887 887 if min > max:
888 888 min = max
889 889 yield ''.join(buf)
890 890 blen = 0
891 891 buf = []
892 892 if buf:
893 893 yield ''.join(buf)
894 894
895 895 Abort = error.Abort
896 896
897 897 def always(fn):
898 898 return True
899 899
900 900 def never(fn):
901 901 return False
902 902
903 903 def nogc(func):
904 904 """disable garbage collector
905 905
906 906 Python's garbage collector triggers a GC each time a certain number of
907 907 container objects (the number being defined by gc.get_threshold()) are
908 908 allocated even when marked not to be tracked by the collector. Tracking has
909 909 no effect on when GCs are triggered, only on what objects the GC looks
910 910 into. As a workaround, disable GC while building complex (huge)
911 911 containers.
912 912
913 913 This garbage collector issue have been fixed in 2.7.
914 914 """
915 915 if sys.version_info >= (2, 7):
916 916 return func
917 917 def wrapper(*args, **kwargs):
918 918 gcenabled = gc.isenabled()
919 919 gc.disable()
920 920 try:
921 921 return func(*args, **kwargs)
922 922 finally:
923 923 if gcenabled:
924 924 gc.enable()
925 925 return wrapper
926 926
927 927 def pathto(root, n1, n2):
928 928 '''return the relative path from one place to another.
929 929 root should use os.sep to separate directories
930 930 n1 should use os.sep to separate directories
931 931 n2 should use "/" to separate directories
932 932 returns an os.sep-separated path.
933 933
934 934 If n1 is a relative path, it's assumed it's
935 935 relative to root.
936 936 n2 should always be relative to root.
937 937 '''
938 938 if not n1:
939 939 return localpath(n2)
940 940 if os.path.isabs(n1):
941 941 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
942 942 return os.path.join(root, localpath(n2))
943 943 n2 = '/'.join((pconvert(root), n2))
944 944 a, b = splitpath(n1), n2.split('/')
945 945 a.reverse()
946 946 b.reverse()
947 947 while a and b and a[-1] == b[-1]:
948 948 a.pop()
949 949 b.pop()
950 950 b.reverse()
951 951 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
952 952
953 953 def mainfrozen():
954 954 """return True if we are a frozen executable.
955 955
956 956 The code supports py2exe (most common, Windows only) and tools/freeze
957 957 (portable, not much used).
958 958 """
959 959 return (safehasattr(sys, "frozen") or # new py2exe
960 960 safehasattr(sys, "importers") or # old py2exe
961 961 imp.is_frozen(u"__main__")) # tools/freeze
962 962
963 963 # the location of data files matching the source code
964 964 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
965 965 # executable version (py2exe) doesn't support __file__
966 966 datapath = os.path.dirname(pycompat.sysexecutable)
967 967 else:
968 968 datapath = os.path.dirname(pycompat.fsencode(__file__))
969 969
970 970 i18n.setdatapath(datapath)
971 971
972 972 _hgexecutable = None
973 973
974 974 def hgexecutable():
975 975 """return location of the 'hg' executable.
976 976
977 977 Defaults to $HG or 'hg' in the search path.
978 978 """
979 979 if _hgexecutable is None:
980 980 hg = encoding.environ.get('HG')
981 981 mainmod = sys.modules[pycompat.sysstr('__main__')]
982 982 if hg:
983 983 _sethgexecutable(hg)
984 984 elif mainfrozen():
985 985 if getattr(sys, 'frozen', None) == 'macosx_app':
986 986 # Env variable set by py2app
987 987 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
988 988 else:
989 989 _sethgexecutable(pycompat.sysexecutable)
990 990 elif (os.path.basename(
991 991 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
992 992 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
993 993 else:
994 994 exe = findexe('hg') or os.path.basename(sys.argv[0])
995 995 _sethgexecutable(exe)
996 996 return _hgexecutable
997 997
998 998 def _sethgexecutable(path):
999 999 """set location of the 'hg' executable"""
1000 1000 global _hgexecutable
1001 1001 _hgexecutable = path
1002 1002
1003 1003 def _isstdout(f):
1004 1004 fileno = getattr(f, 'fileno', None)
1005 1005 return fileno and fileno() == sys.__stdout__.fileno()
1006 1006
1007 1007 def shellenviron(environ=None):
1008 1008 """return environ with optional override, useful for shelling out"""
1009 1009 def py2shell(val):
1010 1010 'convert python object into string that is useful to shell'
1011 1011 if val is None or val is False:
1012 1012 return '0'
1013 1013 if val is True:
1014 1014 return '1'
1015 1015 return str(val)
1016 1016 env = dict(encoding.environ)
1017 1017 if environ:
1018 1018 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1019 1019 env['HG'] = hgexecutable()
1020 1020 return env
1021 1021
1022 1022 def system(cmd, environ=None, cwd=None, out=None):
1023 1023 '''enhanced shell command execution.
1024 1024 run with environment maybe modified, maybe in different dir.
1025 1025
1026 1026 if out is specified, it is assumed to be a file-like object that has a
1027 1027 write() method. stdout and stderr will be redirected to out.'''
1028 1028 try:
1029 1029 stdout.flush()
1030 1030 except Exception:
1031 1031 pass
1032 1032 cmd = quotecommand(cmd)
1033 1033 if pycompat.sysplatform == 'plan9' and (sys.version_info[0] == 2
1034 1034 and sys.version_info[1] < 7):
1035 1035 # subprocess kludge to work around issues in half-baked Python
1036 1036 # ports, notably bichued/python:
1037 1037 if not cwd is None:
1038 1038 os.chdir(cwd)
1039 1039 rc = os.system(cmd)
1040 1040 else:
1041 1041 env = shellenviron(environ)
1042 1042 if out is None or _isstdout(out):
1043 1043 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1044 1044 env=env, cwd=cwd)
1045 1045 else:
1046 1046 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1047 1047 env=env, cwd=cwd, stdout=subprocess.PIPE,
1048 1048 stderr=subprocess.STDOUT)
1049 1049 for line in iter(proc.stdout.readline, ''):
1050 1050 out.write(line)
1051 1051 proc.wait()
1052 1052 rc = proc.returncode
1053 1053 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1054 1054 rc = 0
1055 1055 return rc
1056 1056
1057 1057 def checksignature(func):
1058 1058 '''wrap a function with code to check for calling errors'''
1059 1059 def check(*args, **kwargs):
1060 1060 try:
1061 1061 return func(*args, **kwargs)
1062 1062 except TypeError:
1063 1063 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1064 1064 raise error.SignatureError
1065 1065 raise
1066 1066
1067 1067 return check
1068 1068
1069 1069 # a whilelist of known filesystems where hardlink works reliably
1070 1070 _hardlinkfswhitelist = {
1071 1071 'btrfs',
1072 1072 'ext2',
1073 1073 'ext3',
1074 1074 'ext4',
1075 1075 'hfs',
1076 1076 'jfs',
1077 1077 'reiserfs',
1078 1078 'tmpfs',
1079 1079 'ufs',
1080 1080 'xfs',
1081 1081 'zfs',
1082 1082 }
1083 1083
1084 1084 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1085 1085 '''copy a file, preserving mode and optionally other stat info like
1086 1086 atime/mtime
1087 1087
1088 1088 checkambig argument is used with filestat, and is useful only if
1089 1089 destination file is guarded by any lock (e.g. repo.lock or
1090 1090 repo.wlock).
1091 1091
1092 1092 copystat and checkambig should be exclusive.
1093 1093 '''
1094 1094 assert not (copystat and checkambig)
1095 1095 oldstat = None
1096 1096 if os.path.lexists(dest):
1097 1097 if checkambig:
1098 1098 oldstat = checkambig and filestat(dest)
1099 1099 unlink(dest)
1100 1100 if hardlink:
1101 1101 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1102 1102 # unless we are confident that dest is on a whitelisted filesystem.
1103 1103 try:
1104 1104 fstype = getfstype(os.path.dirname(dest))
1105 1105 except OSError:
1106 1106 fstype = None
1107 1107 if fstype not in _hardlinkfswhitelist:
1108 1108 hardlink = False
1109 1109 if hardlink:
1110 1110 try:
1111 1111 oslink(src, dest)
1112 1112 return
1113 1113 except (IOError, OSError):
1114 1114 pass # fall back to normal copy
1115 1115 if os.path.islink(src):
1116 1116 os.symlink(os.readlink(src), dest)
1117 1117 # copytime is ignored for symlinks, but in general copytime isn't needed
1118 1118 # for them anyway
1119 1119 else:
1120 1120 try:
1121 1121 shutil.copyfile(src, dest)
1122 1122 if copystat:
1123 1123 # copystat also copies mode
1124 1124 shutil.copystat(src, dest)
1125 1125 else:
1126 1126 shutil.copymode(src, dest)
1127 1127 if oldstat and oldstat.stat:
1128 1128 newstat = filestat(dest)
1129 1129 if newstat.isambig(oldstat):
1130 1130 # stat of copied file is ambiguous to original one
1131 1131 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1132 1132 os.utime(dest, (advanced, advanced))
1133 1133 except shutil.Error as inst:
1134 1134 raise Abort(str(inst))
1135 1135
1136 1136 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1137 1137 """Copy a directory tree using hardlinks if possible."""
1138 1138 num = 0
1139 1139
1140 1140 gettopic = lambda: hardlink and _('linking') or _('copying')
1141 1141
1142 1142 if os.path.isdir(src):
1143 1143 if hardlink is None:
1144 1144 hardlink = (os.stat(src).st_dev ==
1145 1145 os.stat(os.path.dirname(dst)).st_dev)
1146 1146 topic = gettopic()
1147 1147 os.mkdir(dst)
1148 1148 for name, kind in listdir(src):
1149 1149 srcname = os.path.join(src, name)
1150 1150 dstname = os.path.join(dst, name)
1151 1151 def nprog(t, pos):
1152 1152 if pos is not None:
1153 1153 return progress(t, pos + num)
1154 1154 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1155 1155 num += n
1156 1156 else:
1157 1157 if hardlink is None:
1158 1158 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1159 1159 os.stat(os.path.dirname(dst)).st_dev)
1160 1160 topic = gettopic()
1161 1161
1162 1162 if hardlink:
1163 1163 try:
1164 1164 oslink(src, dst)
1165 1165 except (IOError, OSError):
1166 1166 hardlink = False
1167 1167 shutil.copy(src, dst)
1168 1168 else:
1169 1169 shutil.copy(src, dst)
1170 1170 num += 1
1171 1171 progress(topic, num)
1172 1172 progress(topic, None)
1173 1173
1174 1174 return hardlink, num
1175 1175
1176 1176 _winreservednames = '''con prn aux nul
1177 1177 com1 com2 com3 com4 com5 com6 com7 com8 com9
1178 1178 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1179 1179 _winreservedchars = ':*?"<>|'
1180 1180 def checkwinfilename(path):
1181 1181 r'''Check that the base-relative path is a valid filename on Windows.
1182 1182 Returns None if the path is ok, or a UI string describing the problem.
1183 1183
1184 1184 >>> checkwinfilename("just/a/normal/path")
1185 1185 >>> checkwinfilename("foo/bar/con.xml")
1186 1186 "filename contains 'con', which is reserved on Windows"
1187 1187 >>> checkwinfilename("foo/con.xml/bar")
1188 1188 "filename contains 'con', which is reserved on Windows"
1189 1189 >>> checkwinfilename("foo/bar/xml.con")
1190 1190 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1191 1191 "filename contains 'AUX', which is reserved on Windows"
1192 1192 >>> checkwinfilename("foo/bar/bla:.txt")
1193 1193 "filename contains ':', which is reserved on Windows"
1194 1194 >>> checkwinfilename("foo/bar/b\07la.txt")
1195 1195 "filename contains '\\x07', which is invalid on Windows"
1196 1196 >>> checkwinfilename("foo/bar/bla ")
1197 1197 "filename ends with ' ', which is not allowed on Windows"
1198 1198 >>> checkwinfilename("../bar")
1199 1199 >>> checkwinfilename("foo\\")
1200 1200 "filename ends with '\\', which is invalid on Windows"
1201 1201 >>> checkwinfilename("foo\\/bar")
1202 1202 "directory name ends with '\\', which is invalid on Windows"
1203 1203 '''
1204 1204 if path.endswith('\\'):
1205 1205 return _("filename ends with '\\', which is invalid on Windows")
1206 1206 if '\\/' in path:
1207 1207 return _("directory name ends with '\\', which is invalid on Windows")
1208 1208 for n in path.replace('\\', '/').split('/'):
1209 1209 if not n:
1210 1210 continue
1211 1211 for c in pycompat.bytestr(n):
1212 1212 if c in _winreservedchars:
1213 1213 return _("filename contains '%s', which is reserved "
1214 1214 "on Windows") % c
1215 1215 if ord(c) <= 31:
1216 1216 return _("filename contains %r, which is invalid "
1217 1217 "on Windows") % c
1218 1218 base = n.split('.')[0]
1219 1219 if base and base.lower() in _winreservednames:
1220 1220 return _("filename contains '%s', which is reserved "
1221 1221 "on Windows") % base
1222 1222 t = n[-1]
1223 1223 if t in '. ' and n not in '..':
1224 1224 return _("filename ends with '%s', which is not allowed "
1225 1225 "on Windows") % t
1226 1226
1227 1227 if pycompat.osname == 'nt':
1228 1228 checkosfilename = checkwinfilename
1229 1229 timer = time.clock
1230 1230 else:
1231 1231 checkosfilename = platform.checkosfilename
1232 1232 timer = time.time
1233 1233
1234 1234 if safehasattr(time, "perf_counter"):
1235 1235 timer = time.perf_counter
1236 1236
1237 1237 def makelock(info, pathname):
1238 1238 try:
1239 1239 return os.symlink(info, pathname)
1240 1240 except OSError as why:
1241 1241 if why.errno == errno.EEXIST:
1242 1242 raise
1243 1243 except AttributeError: # no symlink in os
1244 1244 pass
1245 1245
1246 1246 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1247 1247 os.write(ld, info)
1248 1248 os.close(ld)
1249 1249
1250 1250 def readlock(pathname):
1251 1251 try:
1252 1252 return os.readlink(pathname)
1253 1253 except OSError as why:
1254 1254 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1255 1255 raise
1256 1256 except AttributeError: # no symlink in os
1257 1257 pass
1258 1258 fp = posixfile(pathname)
1259 1259 r = fp.read()
1260 1260 fp.close()
1261 1261 return r
1262 1262
1263 1263 def fstat(fp):
1264 1264 '''stat file object that may not have fileno method.'''
1265 1265 try:
1266 1266 return os.fstat(fp.fileno())
1267 1267 except AttributeError:
1268 1268 return os.stat(fp.name)
1269 1269
1270 1270 # File system features
1271 1271
1272 1272 def fscasesensitive(path):
1273 1273 """
1274 1274 Return true if the given path is on a case-sensitive filesystem
1275 1275
1276 1276 Requires a path (like /foo/.hg) ending with a foldable final
1277 1277 directory component.
1278 1278 """
1279 1279 s1 = os.lstat(path)
1280 1280 d, b = os.path.split(path)
1281 1281 b2 = b.upper()
1282 1282 if b == b2:
1283 1283 b2 = b.lower()
1284 1284 if b == b2:
1285 1285 return True # no evidence against case sensitivity
1286 1286 p2 = os.path.join(d, b2)
1287 1287 try:
1288 1288 s2 = os.lstat(p2)
1289 1289 if s2 == s1:
1290 1290 return False
1291 1291 return True
1292 1292 except OSError:
1293 1293 return True
1294 1294
1295 1295 try:
1296 1296 import re2
1297 1297 _re2 = None
1298 1298 except ImportError:
1299 1299 _re2 = False
1300 1300
1301 1301 class _re(object):
1302 1302 def _checkre2(self):
1303 1303 global _re2
1304 1304 try:
1305 1305 # check if match works, see issue3964
1306 1306 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1307 1307 except ImportError:
1308 1308 _re2 = False
1309 1309
1310 1310 def compile(self, pat, flags=0):
1311 1311 '''Compile a regular expression, using re2 if possible
1312 1312
1313 1313 For best performance, use only re2-compatible regexp features. The
1314 1314 only flags from the re module that are re2-compatible are
1315 1315 IGNORECASE and MULTILINE.'''
1316 1316 if _re2 is None:
1317 1317 self._checkre2()
1318 1318 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1319 1319 if flags & remod.IGNORECASE:
1320 1320 pat = '(?i)' + pat
1321 1321 if flags & remod.MULTILINE:
1322 1322 pat = '(?m)' + pat
1323 1323 try:
1324 1324 return re2.compile(pat)
1325 1325 except re2.error:
1326 1326 pass
1327 1327 return remod.compile(pat, flags)
1328 1328
1329 1329 @propertycache
1330 1330 def escape(self):
1331 1331 '''Return the version of escape corresponding to self.compile.
1332 1332
1333 1333 This is imperfect because whether re2 or re is used for a particular
1334 1334 function depends on the flags, etc, but it's the best we can do.
1335 1335 '''
1336 1336 global _re2
1337 1337 if _re2 is None:
1338 1338 self._checkre2()
1339 1339 if _re2:
1340 1340 return re2.escape
1341 1341 else:
1342 1342 return remod.escape
1343 1343
1344 1344 re = _re()
1345 1345
1346 1346 _fspathcache = {}
1347 1347 def fspath(name, root):
1348 1348 '''Get name in the case stored in the filesystem
1349 1349
1350 1350 The name should be relative to root, and be normcase-ed for efficiency.
1351 1351
1352 1352 Note that this function is unnecessary, and should not be
1353 1353 called, for case-sensitive filesystems (simply because it's expensive).
1354 1354
1355 1355 The root should be normcase-ed, too.
1356 1356 '''
1357 1357 def _makefspathcacheentry(dir):
1358 1358 return dict((normcase(n), n) for n in os.listdir(dir))
1359 1359
1360 1360 seps = pycompat.ossep
1361 1361 if pycompat.osaltsep:
1362 1362 seps = seps + pycompat.osaltsep
1363 1363 # Protect backslashes. This gets silly very quickly.
1364 1364 seps.replace('\\','\\\\')
1365 1365 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1366 1366 dir = os.path.normpath(root)
1367 1367 result = []
1368 1368 for part, sep in pattern.findall(name):
1369 1369 if sep:
1370 1370 result.append(sep)
1371 1371 continue
1372 1372
1373 1373 if dir not in _fspathcache:
1374 1374 _fspathcache[dir] = _makefspathcacheentry(dir)
1375 1375 contents = _fspathcache[dir]
1376 1376
1377 1377 found = contents.get(part)
1378 1378 if not found:
1379 1379 # retry "once per directory" per "dirstate.walk" which
1380 1380 # may take place for each patches of "hg qpush", for example
1381 1381 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1382 1382 found = contents.get(part)
1383 1383
1384 1384 result.append(found or part)
1385 1385 dir = os.path.join(dir, part)
1386 1386
1387 1387 return ''.join(result)
1388 1388
1389 1389 def getfstype(dirpath):
1390 1390 '''Get the filesystem type name from a directory (best-effort)
1391 1391
1392 1392 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1393 1393 '''
1394 1394 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1395 1395
1396 1396 def checknlink(testfile):
1397 1397 '''check whether hardlink count reporting works properly'''
1398 1398
1399 1399 # testfile may be open, so we need a separate file for checking to
1400 1400 # work around issue2543 (or testfile may get lost on Samba shares)
1401 1401 f1 = testfile + ".hgtmp1"
1402 1402 if os.path.lexists(f1):
1403 1403 return False
1404 1404 try:
1405 1405 posixfile(f1, 'w').close()
1406 1406 except IOError:
1407 1407 try:
1408 1408 os.unlink(f1)
1409 1409 except OSError:
1410 1410 pass
1411 1411 return False
1412 1412
1413 1413 f2 = testfile + ".hgtmp2"
1414 1414 fd = None
1415 1415 try:
1416 1416 oslink(f1, f2)
1417 1417 # nlinks() may behave differently for files on Windows shares if
1418 1418 # the file is open.
1419 1419 fd = posixfile(f2)
1420 1420 return nlinks(f2) > 1
1421 1421 except OSError:
1422 1422 return False
1423 1423 finally:
1424 1424 if fd is not None:
1425 1425 fd.close()
1426 1426 for f in (f1, f2):
1427 1427 try:
1428 1428 os.unlink(f)
1429 1429 except OSError:
1430 1430 pass
1431 1431
1432 1432 def endswithsep(path):
1433 1433 '''Check path ends with os.sep or os.altsep.'''
1434 1434 return (path.endswith(pycompat.ossep)
1435 1435 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1436 1436
1437 1437 def splitpath(path):
1438 1438 '''Split path by os.sep.
1439 1439 Note that this function does not use os.altsep because this is
1440 1440 an alternative of simple "xxx.split(os.sep)".
1441 1441 It is recommended to use os.path.normpath() before using this
1442 1442 function if need.'''
1443 1443 return path.split(pycompat.ossep)
1444 1444
1445 1445 def gui():
1446 1446 '''Are we running in a GUI?'''
1447 1447 if pycompat.sysplatform == 'darwin':
1448 1448 if 'SSH_CONNECTION' in encoding.environ:
1449 1449 # handle SSH access to a box where the user is logged in
1450 1450 return False
1451 1451 elif getattr(osutil, 'isgui', None):
1452 1452 # check if a CoreGraphics session is available
1453 1453 return osutil.isgui()
1454 1454 else:
1455 1455 # pure build; use a safe default
1456 1456 return True
1457 1457 else:
1458 1458 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1459 1459
1460 1460 def mktempcopy(name, emptyok=False, createmode=None):
1461 1461 """Create a temporary file with the same contents from name
1462 1462
1463 1463 The permission bits are copied from the original file.
1464 1464
1465 1465 If the temporary file is going to be truncated immediately, you
1466 1466 can use emptyok=True as an optimization.
1467 1467
1468 1468 Returns the name of the temporary file.
1469 1469 """
1470 1470 d, fn = os.path.split(name)
1471 1471 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1472 1472 os.close(fd)
1473 1473 # Temporary files are created with mode 0600, which is usually not
1474 1474 # what we want. If the original file already exists, just copy
1475 1475 # its mode. Otherwise, manually obey umask.
1476 1476 copymode(name, temp, createmode)
1477 1477 if emptyok:
1478 1478 return temp
1479 1479 try:
1480 1480 try:
1481 1481 ifp = posixfile(name, "rb")
1482 1482 except IOError as inst:
1483 1483 if inst.errno == errno.ENOENT:
1484 1484 return temp
1485 1485 if not getattr(inst, 'filename', None):
1486 1486 inst.filename = name
1487 1487 raise
1488 1488 ofp = posixfile(temp, "wb")
1489 1489 for chunk in filechunkiter(ifp):
1490 1490 ofp.write(chunk)
1491 1491 ifp.close()
1492 1492 ofp.close()
1493 1493 except: # re-raises
1494 1494 try: os.unlink(temp)
1495 1495 except OSError: pass
1496 1496 raise
1497 1497 return temp
1498 1498
1499 1499 class filestat(object):
1500 1500 """help to exactly detect change of a file
1501 1501
1502 1502 'stat' attribute is result of 'os.stat()' if specified 'path'
1503 1503 exists. Otherwise, it is None. This can avoid preparative
1504 1504 'exists()' examination on client side of this class.
1505 1505 """
1506 1506 def __init__(self, path):
1507 1507 try:
1508 1508 self.stat = os.stat(path)
1509 1509 except OSError as err:
1510 1510 if err.errno != errno.ENOENT:
1511 1511 raise
1512 1512 self.stat = None
1513 1513
1514 1514 __hash__ = object.__hash__
1515 1515
1516 1516 def __eq__(self, old):
1517 1517 try:
1518 1518 # if ambiguity between stat of new and old file is
1519 1519 # avoided, comparison of size, ctime and mtime is enough
1520 1520 # to exactly detect change of a file regardless of platform
1521 1521 return (self.stat.st_size == old.stat.st_size and
1522 1522 self.stat.st_ctime == old.stat.st_ctime and
1523 1523 self.stat.st_mtime == old.stat.st_mtime)
1524 1524 except AttributeError:
1525 1525 return False
1526 1526
1527 1527 def isambig(self, old):
1528 1528 """Examine whether new (= self) stat is ambiguous against old one
1529 1529
1530 1530 "S[N]" below means stat of a file at N-th change:
1531 1531
1532 1532 - S[n-1].ctime < S[n].ctime: can detect change of a file
1533 1533 - S[n-1].ctime == S[n].ctime
1534 1534 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1535 1535 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1536 1536 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1537 1537 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1538 1538
1539 1539 Case (*2) above means that a file was changed twice or more at
1540 1540 same time in sec (= S[n-1].ctime), and comparison of timestamp
1541 1541 is ambiguous.
1542 1542
1543 1543 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1544 1544 timestamp is ambiguous".
1545 1545
1546 1546 But advancing mtime only in case (*2) doesn't work as
1547 1547 expected, because naturally advanced S[n].mtime in case (*1)
1548 1548 might be equal to manually advanced S[n-1 or earlier].mtime.
1549 1549
1550 1550 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1551 1551 treated as ambiguous regardless of mtime, to avoid overlooking
1552 1552 by confliction between such mtime.
1553 1553
1554 1554 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1555 1555 S[n].mtime", even if size of a file isn't changed.
1556 1556 """
1557 1557 try:
1558 1558 return (self.stat.st_ctime == old.stat.st_ctime)
1559 1559 except AttributeError:
1560 1560 return False
1561 1561
1562 1562 def avoidambig(self, path, old):
1563 1563 """Change file stat of specified path to avoid ambiguity
1564 1564
1565 1565 'old' should be previous filestat of 'path'.
1566 1566
1567 1567 This skips avoiding ambiguity, if a process doesn't have
1568 1568 appropriate privileges for 'path'.
1569 1569 """
1570 1570 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1571 1571 try:
1572 1572 os.utime(path, (advanced, advanced))
1573 1573 except OSError as inst:
1574 1574 if inst.errno == errno.EPERM:
1575 1575 # utime() on the file created by another user causes EPERM,
1576 1576 # if a process doesn't have appropriate privileges
1577 1577 return
1578 1578 raise
1579 1579
1580 1580 def __ne__(self, other):
1581 1581 return not self == other
1582 1582
1583 1583 class atomictempfile(object):
1584 1584 '''writable file object that atomically updates a file
1585 1585
1586 1586 All writes will go to a temporary copy of the original file. Call
1587 1587 close() when you are done writing, and atomictempfile will rename
1588 1588 the temporary copy to the original name, making the changes
1589 1589 visible. If the object is destroyed without being closed, all your
1590 1590 writes are discarded.
1591 1591
1592 1592 checkambig argument of constructor is used with filestat, and is
1593 1593 useful only if target file is guarded by any lock (e.g. repo.lock
1594 1594 or repo.wlock).
1595 1595 '''
1596 1596 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1597 1597 self.__name = name # permanent name
1598 1598 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1599 1599 createmode=createmode)
1600 1600 self._fp = posixfile(self._tempname, mode)
1601 1601 self._checkambig = checkambig
1602 1602
1603 1603 # delegated methods
1604 1604 self.read = self._fp.read
1605 1605 self.write = self._fp.write
1606 1606 self.seek = self._fp.seek
1607 1607 self.tell = self._fp.tell
1608 1608 self.fileno = self._fp.fileno
1609 1609
1610 1610 def close(self):
1611 1611 if not self._fp.closed:
1612 1612 self._fp.close()
1613 1613 filename = localpath(self.__name)
1614 1614 oldstat = self._checkambig and filestat(filename)
1615 1615 if oldstat and oldstat.stat:
1616 1616 rename(self._tempname, filename)
1617 1617 newstat = filestat(filename)
1618 1618 if newstat.isambig(oldstat):
1619 1619 # stat of changed file is ambiguous to original one
1620 1620 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1621 1621 os.utime(filename, (advanced, advanced))
1622 1622 else:
1623 1623 rename(self._tempname, filename)
1624 1624
1625 1625 def discard(self):
1626 1626 if not self._fp.closed:
1627 1627 try:
1628 1628 os.unlink(self._tempname)
1629 1629 except OSError:
1630 1630 pass
1631 1631 self._fp.close()
1632 1632
1633 1633 def __del__(self):
1634 1634 if safehasattr(self, '_fp'): # constructor actually did something
1635 1635 self.discard()
1636 1636
1637 1637 def __enter__(self):
1638 1638 return self
1639 1639
1640 1640 def __exit__(self, exctype, excvalue, traceback):
1641 1641 if exctype is not None:
1642 1642 self.discard()
1643 1643 else:
1644 1644 self.close()
1645 1645
1646 1646 def unlinkpath(f, ignoremissing=False):
1647 1647 """unlink and remove the directory if it is empty"""
1648 1648 if ignoremissing:
1649 1649 tryunlink(f)
1650 1650 else:
1651 1651 unlink(f)
1652 1652 # try removing directories that might now be empty
1653 1653 try:
1654 1654 removedirs(os.path.dirname(f))
1655 1655 except OSError:
1656 1656 pass
1657 1657
1658 1658 def tryunlink(f):
1659 1659 """Attempt to remove a file, ignoring ENOENT errors."""
1660 1660 try:
1661 1661 unlink(f)
1662 1662 except OSError as e:
1663 1663 if e.errno != errno.ENOENT:
1664 1664 raise
1665 1665
1666 1666 def makedirs(name, mode=None, notindexed=False):
1667 1667 """recursive directory creation with parent mode inheritance
1668 1668
1669 1669 Newly created directories are marked as "not to be indexed by
1670 1670 the content indexing service", if ``notindexed`` is specified
1671 1671 for "write" mode access.
1672 1672 """
1673 1673 try:
1674 1674 makedir(name, notindexed)
1675 1675 except OSError as err:
1676 1676 if err.errno == errno.EEXIST:
1677 1677 return
1678 1678 if err.errno != errno.ENOENT or not name:
1679 1679 raise
1680 1680 parent = os.path.dirname(os.path.abspath(name))
1681 1681 if parent == name:
1682 1682 raise
1683 1683 makedirs(parent, mode, notindexed)
1684 1684 try:
1685 1685 makedir(name, notindexed)
1686 1686 except OSError as err:
1687 1687 # Catch EEXIST to handle races
1688 1688 if err.errno == errno.EEXIST:
1689 1689 return
1690 1690 raise
1691 1691 if mode is not None:
1692 1692 os.chmod(name, mode)
1693 1693
1694 1694 def readfile(path):
1695 1695 with open(path, 'rb') as fp:
1696 1696 return fp.read()
1697 1697
1698 1698 def writefile(path, text):
1699 1699 with open(path, 'wb') as fp:
1700 1700 fp.write(text)
1701 1701
1702 1702 def appendfile(path, text):
1703 1703 with open(path, 'ab') as fp:
1704 1704 fp.write(text)
1705 1705
1706 1706 class chunkbuffer(object):
1707 1707 """Allow arbitrary sized chunks of data to be efficiently read from an
1708 1708 iterator over chunks of arbitrary size."""
1709 1709
1710 1710 def __init__(self, in_iter):
1711 1711 """in_iter is the iterator that's iterating over the input chunks."""
1712 1712 def splitbig(chunks):
1713 1713 for chunk in chunks:
1714 1714 if len(chunk) > 2**20:
1715 1715 pos = 0
1716 1716 while pos < len(chunk):
1717 1717 end = pos + 2 ** 18
1718 1718 yield chunk[pos:end]
1719 1719 pos = end
1720 1720 else:
1721 1721 yield chunk
1722 1722 self.iter = splitbig(in_iter)
1723 1723 self._queue = collections.deque()
1724 1724 self._chunkoffset = 0
1725 1725
1726 1726 def read(self, l=None):
1727 1727 """Read L bytes of data from the iterator of chunks of data.
1728 1728 Returns less than L bytes if the iterator runs dry.
1729 1729
1730 1730 If size parameter is omitted, read everything"""
1731 1731 if l is None:
1732 1732 return ''.join(self.iter)
1733 1733
1734 1734 left = l
1735 1735 buf = []
1736 1736 queue = self._queue
1737 1737 while left > 0:
1738 1738 # refill the queue
1739 1739 if not queue:
1740 1740 target = 2**18
1741 1741 for chunk in self.iter:
1742 1742 queue.append(chunk)
1743 1743 target -= len(chunk)
1744 1744 if target <= 0:
1745 1745 break
1746 1746 if not queue:
1747 1747 break
1748 1748
1749 1749 # The easy way to do this would be to queue.popleft(), modify the
1750 1750 # chunk (if necessary), then queue.appendleft(). However, for cases
1751 1751 # where we read partial chunk content, this incurs 2 dequeue
1752 1752 # mutations and creates a new str for the remaining chunk in the
1753 1753 # queue. Our code below avoids this overhead.
1754 1754
1755 1755 chunk = queue[0]
1756 1756 chunkl = len(chunk)
1757 1757 offset = self._chunkoffset
1758 1758
1759 1759 # Use full chunk.
1760 1760 if offset == 0 and left >= chunkl:
1761 1761 left -= chunkl
1762 1762 queue.popleft()
1763 1763 buf.append(chunk)
1764 1764 # self._chunkoffset remains at 0.
1765 1765 continue
1766 1766
1767 1767 chunkremaining = chunkl - offset
1768 1768
1769 1769 # Use all of unconsumed part of chunk.
1770 1770 if left >= chunkremaining:
1771 1771 left -= chunkremaining
1772 1772 queue.popleft()
1773 1773 # offset == 0 is enabled by block above, so this won't merely
1774 1774 # copy via ``chunk[0:]``.
1775 1775 buf.append(chunk[offset:])
1776 1776 self._chunkoffset = 0
1777 1777
1778 1778 # Partial chunk needed.
1779 1779 else:
1780 1780 buf.append(chunk[offset:offset + left])
1781 1781 self._chunkoffset += left
1782 1782 left -= chunkremaining
1783 1783
1784 1784 return ''.join(buf)
1785 1785
1786 1786 def filechunkiter(f, size=131072, limit=None):
1787 1787 """Create a generator that produces the data in the file size
1788 1788 (default 131072) bytes at a time, up to optional limit (default is
1789 1789 to read all data). Chunks may be less than size bytes if the
1790 1790 chunk is the last chunk in the file, or the file is a socket or
1791 1791 some other type of file that sometimes reads less data than is
1792 1792 requested."""
1793 1793 assert size >= 0
1794 1794 assert limit is None or limit >= 0
1795 1795 while True:
1796 1796 if limit is None:
1797 1797 nbytes = size
1798 1798 else:
1799 1799 nbytes = min(limit, size)
1800 1800 s = nbytes and f.read(nbytes)
1801 1801 if not s:
1802 1802 break
1803 1803 if limit:
1804 1804 limit -= len(s)
1805 1805 yield s
1806 1806
1807 1807 def makedate(timestamp=None):
1808 1808 '''Return a unix timestamp (or the current time) as a (unixtime,
1809 1809 offset) tuple based off the local timezone.'''
1810 1810 if timestamp is None:
1811 1811 timestamp = time.time()
1812 1812 if timestamp < 0:
1813 1813 hint = _("check your clock")
1814 1814 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1815 1815 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1816 1816 datetime.datetime.fromtimestamp(timestamp))
1817 1817 tz = delta.days * 86400 + delta.seconds
1818 1818 return timestamp, tz
1819 1819
1820 1820 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1821 1821 """represent a (unixtime, offset) tuple as a localized time.
1822 1822 unixtime is seconds since the epoch, and offset is the time zone's
1823 1823 number of seconds away from UTC.
1824 1824
1825 1825 >>> datestr((0, 0))
1826 1826 'Thu Jan 01 00:00:00 1970 +0000'
1827 1827 >>> datestr((42, 0))
1828 1828 'Thu Jan 01 00:00:42 1970 +0000'
1829 1829 >>> datestr((-42, 0))
1830 1830 'Wed Dec 31 23:59:18 1969 +0000'
1831 1831 >>> datestr((0x7fffffff, 0))
1832 1832 'Tue Jan 19 03:14:07 2038 +0000'
1833 1833 >>> datestr((-0x80000000, 0))
1834 1834 'Fri Dec 13 20:45:52 1901 +0000'
1835 1835 """
1836 1836 t, tz = date or makedate()
1837 1837 if "%1" in format or "%2" in format or "%z" in format:
1838 1838 sign = (tz > 0) and "-" or "+"
1839 1839 minutes = abs(tz) // 60
1840 1840 q, r = divmod(minutes, 60)
1841 1841 format = format.replace("%z", "%1%2")
1842 1842 format = format.replace("%1", "%c%02d" % (sign, q))
1843 1843 format = format.replace("%2", "%02d" % r)
1844 1844 d = t - tz
1845 1845 if d > 0x7fffffff:
1846 1846 d = 0x7fffffff
1847 1847 elif d < -0x80000000:
1848 1848 d = -0x80000000
1849 1849 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1850 1850 # because they use the gmtime() system call which is buggy on Windows
1851 1851 # for negative values.
1852 1852 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1853 1853 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1854 1854 return s
1855 1855
1856 1856 def shortdate(date=None):
1857 1857 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1858 1858 return datestr(date, format='%Y-%m-%d')
1859 1859
1860 1860 def parsetimezone(s):
1861 1861 """find a trailing timezone, if any, in string, and return a
1862 1862 (offset, remainder) pair"""
1863 1863
1864 1864 if s.endswith("GMT") or s.endswith("UTC"):
1865 1865 return 0, s[:-3].rstrip()
1866 1866
1867 1867 # Unix-style timezones [+-]hhmm
1868 1868 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1869 1869 sign = (s[-5] == "+") and 1 or -1
1870 1870 hours = int(s[-4:-2])
1871 1871 minutes = int(s[-2:])
1872 1872 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1873 1873
1874 1874 # ISO8601 trailing Z
1875 1875 if s.endswith("Z") and s[-2:-1].isdigit():
1876 1876 return 0, s[:-1]
1877 1877
1878 1878 # ISO8601-style [+-]hh:mm
1879 1879 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1880 1880 s[-5:-3].isdigit() and s[-2:].isdigit()):
1881 1881 sign = (s[-6] == "+") and 1 or -1
1882 1882 hours = int(s[-5:-3])
1883 1883 minutes = int(s[-2:])
1884 1884 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1885 1885
1886 1886 return None, s
1887 1887
1888 1888 def strdate(string, format, defaults=None):
1889 1889 """parse a localized time string and return a (unixtime, offset) tuple.
1890 1890 if the string cannot be parsed, ValueError is raised."""
1891 1891 if defaults is None:
1892 1892 defaults = {}
1893 1893
1894 1894 # NOTE: unixtime = localunixtime + offset
1895 1895 offset, date = parsetimezone(string)
1896 1896
1897 1897 # add missing elements from defaults
1898 1898 usenow = False # default to using biased defaults
1899 1899 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1900 1900 part = pycompat.bytestr(part)
1901 1901 found = [True for p in part if ("%"+p) in format]
1902 1902 if not found:
1903 1903 date += "@" + defaults[part][usenow]
1904 1904 format += "@%" + part[0]
1905 1905 else:
1906 1906 # We've found a specific time element, less specific time
1907 1907 # elements are relative to today
1908 1908 usenow = True
1909 1909
1910 1910 timetuple = time.strptime(encoding.strfromlocal(date),
1911 1911 encoding.strfromlocal(format))
1912 1912 localunixtime = int(calendar.timegm(timetuple))
1913 1913 if offset is None:
1914 1914 # local timezone
1915 1915 unixtime = int(time.mktime(timetuple))
1916 1916 offset = unixtime - localunixtime
1917 1917 else:
1918 1918 unixtime = localunixtime + offset
1919 1919 return unixtime, offset
1920 1920
1921 1921 def parsedate(date, formats=None, bias=None):
1922 1922 """parse a localized date/time and return a (unixtime, offset) tuple.
1923 1923
1924 1924 The date may be a "unixtime offset" string or in one of the specified
1925 1925 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1926 1926
1927 This function calls rawparsedate and convert ValueError to Abort for
1928 functions that needs higher level exception.
1929
1930 1927 >>> parsedate(' today ') == parsedate(\
1931 1928 datetime.date.today().strftime('%b %d'))
1932 1929 True
1933 1930 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1934 1931 datetime.timedelta(days=1)\
1935 1932 ).strftime('%b %d'))
1936 1933 True
1937 1934 >>> now, tz = makedate()
1938 1935 >>> strnow, strtz = parsedate('now')
1939 1936 >>> (strnow - now) < 1
1940 1937 True
1941 1938 >>> tz == strtz
1942 1939 True
1943 1940 """
1944 try:
1945 return rawparsedate(date, formats=formats, bias=bias)
1946 except ValueError as exception:
1947 raise Abort(str(exception))
1948
1949 def rawparsedate(date, formats=None, bias=None):
1950 """parse a localized date/time and return a (unixtime, offset) tuple.
1951
1952 The date may be a "unixtime offset" string or in one of the specified
1953 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1954
1955 See docstring of parsedate for example.
1956 Raise ValueError for invalid date value.
1957 """
1958 1941 if bias is None:
1959 1942 bias = {}
1960 1943 if not date:
1961 1944 return 0, 0
1962 1945 if isinstance(date, tuple) and len(date) == 2:
1963 1946 return date
1964 1947 if not formats:
1965 1948 formats = defaultdateformats
1966 1949 date = date.strip()
1967 1950
1968 1951 if date == 'now' or date == _('now'):
1969 1952 return makedate()
1970 1953 if date == 'today' or date == _('today'):
1971 1954 date = datetime.date.today().strftime('%b %d')
1972 1955 elif date == 'yesterday' or date == _('yesterday'):
1973 1956 date = (datetime.date.today() -
1974 1957 datetime.timedelta(days=1)).strftime('%b %d')
1975 1958
1976 1959 try:
1977 1960 when, offset = map(int, date.split(' '))
1978 1961 except ValueError:
1979 1962 # fill out defaults
1980 1963 now = makedate()
1981 1964 defaults = {}
1982 1965 for part in ("d", "mb", "yY", "HI", "M", "S"):
1983 1966 # this piece is for rounding the specific end of unknowns
1984 1967 b = bias.get(part)
1985 1968 if b is None:
1986 1969 if part[0:1] in "HMS":
1987 1970 b = "00"
1988 1971 else:
1989 1972 b = "0"
1990 1973
1991 1974 # this piece is for matching the generic end to today's date
1992 1975 n = datestr(now, "%" + part[0:1])
1993 1976
1994 1977 defaults[part] = (b, n)
1995 1978
1996 1979 for format in formats:
1997 1980 try:
1998 1981 when, offset = strdate(date, format, defaults)
1999 1982 except (ValueError, OverflowError):
2000 1983 pass
2001 1984 else:
2002 1985 break
2003 1986 else:
2004 raise ValueError(_('invalid date: %r') % date)
1987 raise error.ParseError(_('invalid date: %r') % date)
2005 1988 # validate explicit (probably user-specified) date and
2006 1989 # time zone offset. values must fit in signed 32 bits for
2007 1990 # current 32-bit linux runtimes. timezones go from UTC-12
2008 1991 # to UTC+14
2009 1992 if when < -0x80000000 or when > 0x7fffffff:
2010 raise ValueError(_('date exceeds 32 bits: %d') % when)
1993 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2011 1994 if offset < -50400 or offset > 43200:
2012 raise ValueError(_('impossible time zone offset: %d') % offset)
1995 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2013 1996 return when, offset
2014 1997
2015 1998 def matchdate(date):
2016 1999 """Return a function that matches a given date match specifier
2017 2000
2018 2001 Formats include:
2019 2002
2020 2003 '{date}' match a given date to the accuracy provided
2021 2004
2022 2005 '<{date}' on or before a given date
2023 2006
2024 2007 '>{date}' on or after a given date
2025 2008
2026 2009 >>> p1 = parsedate("10:29:59")
2027 2010 >>> p2 = parsedate("10:30:00")
2028 2011 >>> p3 = parsedate("10:30:59")
2029 2012 >>> p4 = parsedate("10:31:00")
2030 2013 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2031 2014 >>> f = matchdate("10:30")
2032 2015 >>> f(p1[0])
2033 2016 False
2034 2017 >>> f(p2[0])
2035 2018 True
2036 2019 >>> f(p3[0])
2037 2020 True
2038 2021 >>> f(p4[0])
2039 2022 False
2040 2023 >>> f(p5[0])
2041 2024 False
2042 2025 """
2043 2026
2044 2027 def lower(date):
2045 2028 d = {'mb': "1", 'd': "1"}
2046 2029 return parsedate(date, extendeddateformats, d)[0]
2047 2030
2048 2031 def upper(date):
2049 2032 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2050 2033 for days in ("31", "30", "29"):
2051 2034 try:
2052 2035 d["d"] = days
2053 2036 return parsedate(date, extendeddateformats, d)[0]
2054 2037 except Abort:
2055 2038 pass
2056 2039 d["d"] = "28"
2057 2040 return parsedate(date, extendeddateformats, d)[0]
2058 2041
2059 2042 date = date.strip()
2060 2043
2061 2044 if not date:
2062 2045 raise Abort(_("dates cannot consist entirely of whitespace"))
2063 2046 elif date[0] == "<":
2064 2047 if not date[1:]:
2065 2048 raise Abort(_("invalid day spec, use '<DATE'"))
2066 2049 when = upper(date[1:])
2067 2050 return lambda x: x <= when
2068 2051 elif date[0] == ">":
2069 2052 if not date[1:]:
2070 2053 raise Abort(_("invalid day spec, use '>DATE'"))
2071 2054 when = lower(date[1:])
2072 2055 return lambda x: x >= when
2073 2056 elif date[0] == "-":
2074 2057 try:
2075 2058 days = int(date[1:])
2076 2059 except ValueError:
2077 2060 raise Abort(_("invalid day spec: %s") % date[1:])
2078 2061 if days < 0:
2079 2062 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2080 2063 % date[1:])
2081 2064 when = makedate()[0] - days * 3600 * 24
2082 2065 return lambda x: x >= when
2083 2066 elif " to " in date:
2084 2067 a, b = date.split(" to ")
2085 2068 start, stop = lower(a), upper(b)
2086 2069 return lambda x: x >= start and x <= stop
2087 2070 else:
2088 2071 start, stop = lower(date), upper(date)
2089 2072 return lambda x: x >= start and x <= stop
2090 2073
2091 2074 def stringmatcher(pattern, casesensitive=True):
2092 2075 """
2093 2076 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2094 2077 returns the matcher name, pattern, and matcher function.
2095 2078 missing or unknown prefixes are treated as literal matches.
2096 2079
2097 2080 helper for tests:
2098 2081 >>> def test(pattern, *tests):
2099 2082 ... kind, pattern, matcher = stringmatcher(pattern)
2100 2083 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2101 2084 >>> def itest(pattern, *tests):
2102 2085 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2103 2086 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2104 2087
2105 2088 exact matching (no prefix):
2106 2089 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2107 2090 ('literal', 'abcdefg', [False, False, True])
2108 2091
2109 2092 regex matching ('re:' prefix)
2110 2093 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2111 2094 ('re', 'a.+b', [False, False, True])
2112 2095
2113 2096 force exact matches ('literal:' prefix)
2114 2097 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2115 2098 ('literal', 're:foobar', [False, True])
2116 2099
2117 2100 unknown prefixes are ignored and treated as literals
2118 2101 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2119 2102 ('literal', 'foo:bar', [False, False, True])
2120 2103
2121 2104 case insensitive regex matches
2122 2105 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2123 2106 ('re', 'A.+b', [False, False, True])
2124 2107
2125 2108 case insensitive literal matches
2126 2109 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2127 2110 ('literal', 'ABCDEFG', [False, False, True])
2128 2111 """
2129 2112 if pattern.startswith('re:'):
2130 2113 pattern = pattern[3:]
2131 2114 try:
2132 2115 flags = 0
2133 2116 if not casesensitive:
2134 2117 flags = remod.I
2135 2118 regex = remod.compile(pattern, flags)
2136 2119 except remod.error as e:
2137 2120 raise error.ParseError(_('invalid regular expression: %s')
2138 2121 % e)
2139 2122 return 're', pattern, regex.search
2140 2123 elif pattern.startswith('literal:'):
2141 2124 pattern = pattern[8:]
2142 2125
2143 2126 match = pattern.__eq__
2144 2127
2145 2128 if not casesensitive:
2146 2129 ipat = encoding.lower(pattern)
2147 2130 match = lambda s: ipat == encoding.lower(s)
2148 2131 return 'literal', pattern, match
2149 2132
2150 2133 def shortuser(user):
2151 2134 """Return a short representation of a user name or email address."""
2152 2135 f = user.find('@')
2153 2136 if f >= 0:
2154 2137 user = user[:f]
2155 2138 f = user.find('<')
2156 2139 if f >= 0:
2157 2140 user = user[f + 1:]
2158 2141 f = user.find(' ')
2159 2142 if f >= 0:
2160 2143 user = user[:f]
2161 2144 f = user.find('.')
2162 2145 if f >= 0:
2163 2146 user = user[:f]
2164 2147 return user
2165 2148
2166 2149 def emailuser(user):
2167 2150 """Return the user portion of an email address."""
2168 2151 f = user.find('@')
2169 2152 if f >= 0:
2170 2153 user = user[:f]
2171 2154 f = user.find('<')
2172 2155 if f >= 0:
2173 2156 user = user[f + 1:]
2174 2157 return user
2175 2158
2176 2159 def email(author):
2177 2160 '''get email of author.'''
2178 2161 r = author.find('>')
2179 2162 if r == -1:
2180 2163 r = None
2181 2164 return author[author.find('<') + 1:r]
2182 2165
2183 2166 def ellipsis(text, maxlength=400):
2184 2167 """Trim string to at most maxlength (default: 400) columns in display."""
2185 2168 return encoding.trim(text, maxlength, ellipsis='...')
2186 2169
2187 2170 def unitcountfn(*unittable):
2188 2171 '''return a function that renders a readable count of some quantity'''
2189 2172
2190 2173 def go(count):
2191 2174 for multiplier, divisor, format in unittable:
2192 2175 if abs(count) >= divisor * multiplier:
2193 2176 return format % (count / float(divisor))
2194 2177 return unittable[-1][2] % count
2195 2178
2196 2179 return go
2197 2180
2198 2181 def processlinerange(fromline, toline):
2199 2182 """Check that linerange <fromline>:<toline> makes sense and return a
2200 2183 0-based range.
2201 2184
2202 2185 >>> processlinerange(10, 20)
2203 2186 (9, 20)
2204 2187 >>> processlinerange(2, 1)
2205 2188 Traceback (most recent call last):
2206 2189 ...
2207 2190 ParseError: line range must be positive
2208 2191 >>> processlinerange(0, 5)
2209 2192 Traceback (most recent call last):
2210 2193 ...
2211 2194 ParseError: fromline must be strictly positive
2212 2195 """
2213 2196 if toline - fromline < 0:
2214 2197 raise error.ParseError(_("line range must be positive"))
2215 2198 if fromline < 1:
2216 2199 raise error.ParseError(_("fromline must be strictly positive"))
2217 2200 return fromline - 1, toline
2218 2201
2219 2202 bytecount = unitcountfn(
2220 2203 (100, 1 << 30, _('%.0f GB')),
2221 2204 (10, 1 << 30, _('%.1f GB')),
2222 2205 (1, 1 << 30, _('%.2f GB')),
2223 2206 (100, 1 << 20, _('%.0f MB')),
2224 2207 (10, 1 << 20, _('%.1f MB')),
2225 2208 (1, 1 << 20, _('%.2f MB')),
2226 2209 (100, 1 << 10, _('%.0f KB')),
2227 2210 (10, 1 << 10, _('%.1f KB')),
2228 2211 (1, 1 << 10, _('%.2f KB')),
2229 2212 (1, 1, _('%.0f bytes')),
2230 2213 )
2231 2214
2232 2215 # Matches a single EOL which can either be a CRLF where repeated CR
2233 2216 # are removed or a LF. We do not care about old Macintosh files, so a
2234 2217 # stray CR is an error.
2235 2218 _eolre = remod.compile(br'\r*\n')
2236 2219
2237 2220 def tolf(s):
2238 2221 return _eolre.sub('\n', s)
2239 2222
2240 2223 def tocrlf(s):
2241 2224 return _eolre.sub('\r\n', s)
2242 2225
2243 2226 if pycompat.oslinesep == '\r\n':
2244 2227 tonativeeol = tocrlf
2245 2228 fromnativeeol = tolf
2246 2229 else:
2247 2230 tonativeeol = pycompat.identity
2248 2231 fromnativeeol = pycompat.identity
2249 2232
2250 2233 def escapestr(s):
2251 2234 # call underlying function of s.encode('string_escape') directly for
2252 2235 # Python 3 compatibility
2253 2236 return codecs.escape_encode(s)[0]
2254 2237
2255 2238 def unescapestr(s):
2256 2239 return codecs.escape_decode(s)[0]
2257 2240
2258 2241 def uirepr(s):
2259 2242 # Avoid double backslash in Windows path repr()
2260 2243 return repr(s).replace('\\\\', '\\')
2261 2244
2262 2245 # delay import of textwrap
2263 2246 def MBTextWrapper(**kwargs):
2264 2247 class tw(textwrap.TextWrapper):
2265 2248 """
2266 2249 Extend TextWrapper for width-awareness.
2267 2250
2268 2251 Neither number of 'bytes' in any encoding nor 'characters' is
2269 2252 appropriate to calculate terminal columns for specified string.
2270 2253
2271 2254 Original TextWrapper implementation uses built-in 'len()' directly,
2272 2255 so overriding is needed to use width information of each characters.
2273 2256
2274 2257 In addition, characters classified into 'ambiguous' width are
2275 2258 treated as wide in East Asian area, but as narrow in other.
2276 2259
2277 2260 This requires use decision to determine width of such characters.
2278 2261 """
2279 2262 def _cutdown(self, ucstr, space_left):
2280 2263 l = 0
2281 2264 colwidth = encoding.ucolwidth
2282 2265 for i in xrange(len(ucstr)):
2283 2266 l += colwidth(ucstr[i])
2284 2267 if space_left < l:
2285 2268 return (ucstr[:i], ucstr[i:])
2286 2269 return ucstr, ''
2287 2270
2288 2271 # overriding of base class
2289 2272 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2290 2273 space_left = max(width - cur_len, 1)
2291 2274
2292 2275 if self.break_long_words:
2293 2276 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2294 2277 cur_line.append(cut)
2295 2278 reversed_chunks[-1] = res
2296 2279 elif not cur_line:
2297 2280 cur_line.append(reversed_chunks.pop())
2298 2281
2299 2282 # this overriding code is imported from TextWrapper of Python 2.6
2300 2283 # to calculate columns of string by 'encoding.ucolwidth()'
2301 2284 def _wrap_chunks(self, chunks):
2302 2285 colwidth = encoding.ucolwidth
2303 2286
2304 2287 lines = []
2305 2288 if self.width <= 0:
2306 2289 raise ValueError("invalid width %r (must be > 0)" % self.width)
2307 2290
2308 2291 # Arrange in reverse order so items can be efficiently popped
2309 2292 # from a stack of chucks.
2310 2293 chunks.reverse()
2311 2294
2312 2295 while chunks:
2313 2296
2314 2297 # Start the list of chunks that will make up the current line.
2315 2298 # cur_len is just the length of all the chunks in cur_line.
2316 2299 cur_line = []
2317 2300 cur_len = 0
2318 2301
2319 2302 # Figure out which static string will prefix this line.
2320 2303 if lines:
2321 2304 indent = self.subsequent_indent
2322 2305 else:
2323 2306 indent = self.initial_indent
2324 2307
2325 2308 # Maximum width for this line.
2326 2309 width = self.width - len(indent)
2327 2310
2328 2311 # First chunk on line is whitespace -- drop it, unless this
2329 2312 # is the very beginning of the text (i.e. no lines started yet).
2330 2313 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2331 2314 del chunks[-1]
2332 2315
2333 2316 while chunks:
2334 2317 l = colwidth(chunks[-1])
2335 2318
2336 2319 # Can at least squeeze this chunk onto the current line.
2337 2320 if cur_len + l <= width:
2338 2321 cur_line.append(chunks.pop())
2339 2322 cur_len += l
2340 2323
2341 2324 # Nope, this line is full.
2342 2325 else:
2343 2326 break
2344 2327
2345 2328 # The current line is full, and the next chunk is too big to
2346 2329 # fit on *any* line (not just this one).
2347 2330 if chunks and colwidth(chunks[-1]) > width:
2348 2331 self._handle_long_word(chunks, cur_line, cur_len, width)
2349 2332
2350 2333 # If the last chunk on this line is all whitespace, drop it.
2351 2334 if (self.drop_whitespace and
2352 2335 cur_line and cur_line[-1].strip() == ''):
2353 2336 del cur_line[-1]
2354 2337
2355 2338 # Convert current line back to a string and store it in list
2356 2339 # of all lines (return value).
2357 2340 if cur_line:
2358 2341 lines.append(indent + ''.join(cur_line))
2359 2342
2360 2343 return lines
2361 2344
2362 2345 global MBTextWrapper
2363 2346 MBTextWrapper = tw
2364 2347 return tw(**kwargs)
2365 2348
2366 2349 def wrap(line, width, initindent='', hangindent=''):
2367 2350 maxindent = max(len(hangindent), len(initindent))
2368 2351 if width <= maxindent:
2369 2352 # adjust for weird terminal size
2370 2353 width = max(78, maxindent + 1)
2371 2354 line = line.decode(pycompat.sysstr(encoding.encoding),
2372 2355 pycompat.sysstr(encoding.encodingmode))
2373 2356 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2374 2357 pycompat.sysstr(encoding.encodingmode))
2375 2358 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2376 2359 pycompat.sysstr(encoding.encodingmode))
2377 2360 wrapper = MBTextWrapper(width=width,
2378 2361 initial_indent=initindent,
2379 2362 subsequent_indent=hangindent)
2380 2363 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2381 2364
2382 2365 if (pyplatform.python_implementation() == 'CPython' and
2383 2366 sys.version_info < (3, 0)):
2384 2367 # There is an issue in CPython that some IO methods do not handle EINTR
2385 2368 # correctly. The following table shows what CPython version (and functions)
2386 2369 # are affected (buggy: has the EINTR bug, okay: otherwise):
2387 2370 #
2388 2371 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2389 2372 # --------------------------------------------------
2390 2373 # fp.__iter__ | buggy | buggy | okay
2391 2374 # fp.read* | buggy | okay [1] | okay
2392 2375 #
2393 2376 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2394 2377 #
2395 2378 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2396 2379 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2397 2380 #
2398 2381 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2399 2382 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2400 2383 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2401 2384 # fp.__iter__ but not other fp.read* methods.
2402 2385 #
2403 2386 # On modern systems like Linux, the "read" syscall cannot be interrupted
2404 2387 # when reading "fast" files like on-disk files. So the EINTR issue only
2405 2388 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2406 2389 # files approximately as "fast" files and use the fast (unsafe) code path,
2407 2390 # to minimize the performance impact.
2408 2391 if sys.version_info >= (2, 7, 4):
2409 2392 # fp.readline deals with EINTR correctly, use it as a workaround.
2410 2393 def _safeiterfile(fp):
2411 2394 return iter(fp.readline, '')
2412 2395 else:
2413 2396 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2414 2397 # note: this may block longer than necessary because of bufsize.
2415 2398 def _safeiterfile(fp, bufsize=4096):
2416 2399 fd = fp.fileno()
2417 2400 line = ''
2418 2401 while True:
2419 2402 try:
2420 2403 buf = os.read(fd, bufsize)
2421 2404 except OSError as ex:
2422 2405 # os.read only raises EINTR before any data is read
2423 2406 if ex.errno == errno.EINTR:
2424 2407 continue
2425 2408 else:
2426 2409 raise
2427 2410 line += buf
2428 2411 if '\n' in buf:
2429 2412 splitted = line.splitlines(True)
2430 2413 line = ''
2431 2414 for l in splitted:
2432 2415 if l[-1] == '\n':
2433 2416 yield l
2434 2417 else:
2435 2418 line = l
2436 2419 if not buf:
2437 2420 break
2438 2421 if line:
2439 2422 yield line
2440 2423
2441 2424 def iterfile(fp):
2442 2425 fastpath = True
2443 2426 if type(fp) is file:
2444 2427 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2445 2428 if fastpath:
2446 2429 return fp
2447 2430 else:
2448 2431 return _safeiterfile(fp)
2449 2432 else:
2450 2433 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2451 2434 def iterfile(fp):
2452 2435 return fp
2453 2436
2454 2437 def iterlines(iterator):
2455 2438 for chunk in iterator:
2456 2439 for line in chunk.splitlines():
2457 2440 yield line
2458 2441
2459 2442 def expandpath(path):
2460 2443 return os.path.expanduser(os.path.expandvars(path))
2461 2444
2462 2445 def hgcmd():
2463 2446 """Return the command used to execute current hg
2464 2447
2465 2448 This is different from hgexecutable() because on Windows we want
2466 2449 to avoid things opening new shell windows like batch files, so we
2467 2450 get either the python call or current executable.
2468 2451 """
2469 2452 if mainfrozen():
2470 2453 if getattr(sys, 'frozen', None) == 'macosx_app':
2471 2454 # Env variable set by py2app
2472 2455 return [encoding.environ['EXECUTABLEPATH']]
2473 2456 else:
2474 2457 return [pycompat.sysexecutable]
2475 2458 return gethgcmd()
2476 2459
2477 2460 def rundetached(args, condfn):
2478 2461 """Execute the argument list in a detached process.
2479 2462
2480 2463 condfn is a callable which is called repeatedly and should return
2481 2464 True once the child process is known to have started successfully.
2482 2465 At this point, the child process PID is returned. If the child
2483 2466 process fails to start or finishes before condfn() evaluates to
2484 2467 True, return -1.
2485 2468 """
2486 2469 # Windows case is easier because the child process is either
2487 2470 # successfully starting and validating the condition or exiting
2488 2471 # on failure. We just poll on its PID. On Unix, if the child
2489 2472 # process fails to start, it will be left in a zombie state until
2490 2473 # the parent wait on it, which we cannot do since we expect a long
2491 2474 # running process on success. Instead we listen for SIGCHLD telling
2492 2475 # us our child process terminated.
2493 2476 terminated = set()
2494 2477 def handler(signum, frame):
2495 2478 terminated.add(os.wait())
2496 2479 prevhandler = None
2497 2480 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2498 2481 if SIGCHLD is not None:
2499 2482 prevhandler = signal.signal(SIGCHLD, handler)
2500 2483 try:
2501 2484 pid = spawndetached(args)
2502 2485 while not condfn():
2503 2486 if ((pid in terminated or not testpid(pid))
2504 2487 and not condfn()):
2505 2488 return -1
2506 2489 time.sleep(0.1)
2507 2490 return pid
2508 2491 finally:
2509 2492 if prevhandler is not None:
2510 2493 signal.signal(signal.SIGCHLD, prevhandler)
2511 2494
2512 2495 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2513 2496 """Return the result of interpolating items in the mapping into string s.
2514 2497
2515 2498 prefix is a single character string, or a two character string with
2516 2499 a backslash as the first character if the prefix needs to be escaped in
2517 2500 a regular expression.
2518 2501
2519 2502 fn is an optional function that will be applied to the replacement text
2520 2503 just before replacement.
2521 2504
2522 2505 escape_prefix is an optional flag that allows using doubled prefix for
2523 2506 its escaping.
2524 2507 """
2525 2508 fn = fn or (lambda s: s)
2526 2509 patterns = '|'.join(mapping.keys())
2527 2510 if escape_prefix:
2528 2511 patterns += '|' + prefix
2529 2512 if len(prefix) > 1:
2530 2513 prefix_char = prefix[1:]
2531 2514 else:
2532 2515 prefix_char = prefix
2533 2516 mapping[prefix_char] = prefix_char
2534 2517 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2535 2518 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2536 2519
2537 2520 def getport(port):
2538 2521 """Return the port for a given network service.
2539 2522
2540 2523 If port is an integer, it's returned as is. If it's a string, it's
2541 2524 looked up using socket.getservbyname(). If there's no matching
2542 2525 service, error.Abort is raised.
2543 2526 """
2544 2527 try:
2545 2528 return int(port)
2546 2529 except ValueError:
2547 2530 pass
2548 2531
2549 2532 try:
2550 2533 return socket.getservbyname(port)
2551 2534 except socket.error:
2552 2535 raise Abort(_("no port number associated with service '%s'") % port)
2553 2536
2554 2537 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2555 2538 '0': False, 'no': False, 'false': False, 'off': False,
2556 2539 'never': False}
2557 2540
2558 2541 def parsebool(s):
2559 2542 """Parse s into a boolean.
2560 2543
2561 2544 If s is not a valid boolean, returns None.
2562 2545 """
2563 2546 return _booleans.get(s.lower(), None)
2564 2547
2565 2548 _hextochr = dict((a + b, chr(int(a + b, 16)))
2566 2549 for a in string.hexdigits for b in string.hexdigits)
2567 2550
2568 2551 class url(object):
2569 2552 r"""Reliable URL parser.
2570 2553
2571 2554 This parses URLs and provides attributes for the following
2572 2555 components:
2573 2556
2574 2557 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2575 2558
2576 2559 Missing components are set to None. The only exception is
2577 2560 fragment, which is set to '' if present but empty.
2578 2561
2579 2562 If parsefragment is False, fragment is included in query. If
2580 2563 parsequery is False, query is included in path. If both are
2581 2564 False, both fragment and query are included in path.
2582 2565
2583 2566 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2584 2567
2585 2568 Note that for backward compatibility reasons, bundle URLs do not
2586 2569 take host names. That means 'bundle://../' has a path of '../'.
2587 2570
2588 2571 Examples:
2589 2572
2590 2573 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2591 2574 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2592 2575 >>> url('ssh://[::1]:2200//home/joe/repo')
2593 2576 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2594 2577 >>> url('file:///home/joe/repo')
2595 2578 <url scheme: 'file', path: '/home/joe/repo'>
2596 2579 >>> url('file:///c:/temp/foo/')
2597 2580 <url scheme: 'file', path: 'c:/temp/foo/'>
2598 2581 >>> url('bundle:foo')
2599 2582 <url scheme: 'bundle', path: 'foo'>
2600 2583 >>> url('bundle://../foo')
2601 2584 <url scheme: 'bundle', path: '../foo'>
2602 2585 >>> url(r'c:\foo\bar')
2603 2586 <url path: 'c:\\foo\\bar'>
2604 2587 >>> url(r'\\blah\blah\blah')
2605 2588 <url path: '\\\\blah\\blah\\blah'>
2606 2589 >>> url(r'\\blah\blah\blah#baz')
2607 2590 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2608 2591 >>> url(r'file:///C:\users\me')
2609 2592 <url scheme: 'file', path: 'C:\\users\\me'>
2610 2593
2611 2594 Authentication credentials:
2612 2595
2613 2596 >>> url('ssh://joe:xyz@x/repo')
2614 2597 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2615 2598 >>> url('ssh://joe@x/repo')
2616 2599 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2617 2600
2618 2601 Query strings and fragments:
2619 2602
2620 2603 >>> url('http://host/a?b#c')
2621 2604 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2622 2605 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2623 2606 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2624 2607
2625 2608 Empty path:
2626 2609
2627 2610 >>> url('')
2628 2611 <url path: ''>
2629 2612 >>> url('#a')
2630 2613 <url path: '', fragment: 'a'>
2631 2614 >>> url('http://host/')
2632 2615 <url scheme: 'http', host: 'host', path: ''>
2633 2616 >>> url('http://host/#a')
2634 2617 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2635 2618
2636 2619 Only scheme:
2637 2620
2638 2621 >>> url('http:')
2639 2622 <url scheme: 'http'>
2640 2623 """
2641 2624
2642 2625 _safechars = "!~*'()+"
2643 2626 _safepchars = "/!~*'()+:\\"
2644 2627 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2645 2628
2646 2629 def __init__(self, path, parsequery=True, parsefragment=True):
2647 2630 # We slowly chomp away at path until we have only the path left
2648 2631 self.scheme = self.user = self.passwd = self.host = None
2649 2632 self.port = self.path = self.query = self.fragment = None
2650 2633 self._localpath = True
2651 2634 self._hostport = ''
2652 2635 self._origpath = path
2653 2636
2654 2637 if parsefragment and '#' in path:
2655 2638 path, self.fragment = path.split('#', 1)
2656 2639
2657 2640 # special case for Windows drive letters and UNC paths
2658 2641 if hasdriveletter(path) or path.startswith('\\\\'):
2659 2642 self.path = path
2660 2643 return
2661 2644
2662 2645 # For compatibility reasons, we can't handle bundle paths as
2663 2646 # normal URLS
2664 2647 if path.startswith('bundle:'):
2665 2648 self.scheme = 'bundle'
2666 2649 path = path[7:]
2667 2650 if path.startswith('//'):
2668 2651 path = path[2:]
2669 2652 self.path = path
2670 2653 return
2671 2654
2672 2655 if self._matchscheme(path):
2673 2656 parts = path.split(':', 1)
2674 2657 if parts[0]:
2675 2658 self.scheme, path = parts
2676 2659 self._localpath = False
2677 2660
2678 2661 if not path:
2679 2662 path = None
2680 2663 if self._localpath:
2681 2664 self.path = ''
2682 2665 return
2683 2666 else:
2684 2667 if self._localpath:
2685 2668 self.path = path
2686 2669 return
2687 2670
2688 2671 if parsequery and '?' in path:
2689 2672 path, self.query = path.split('?', 1)
2690 2673 if not path:
2691 2674 path = None
2692 2675 if not self.query:
2693 2676 self.query = None
2694 2677
2695 2678 # // is required to specify a host/authority
2696 2679 if path and path.startswith('//'):
2697 2680 parts = path[2:].split('/', 1)
2698 2681 if len(parts) > 1:
2699 2682 self.host, path = parts
2700 2683 else:
2701 2684 self.host = parts[0]
2702 2685 path = None
2703 2686 if not self.host:
2704 2687 self.host = None
2705 2688 # path of file:///d is /d
2706 2689 # path of file:///d:/ is d:/, not /d:/
2707 2690 if path and not hasdriveletter(path):
2708 2691 path = '/' + path
2709 2692
2710 2693 if self.host and '@' in self.host:
2711 2694 self.user, self.host = self.host.rsplit('@', 1)
2712 2695 if ':' in self.user:
2713 2696 self.user, self.passwd = self.user.split(':', 1)
2714 2697 if not self.host:
2715 2698 self.host = None
2716 2699
2717 2700 # Don't split on colons in IPv6 addresses without ports
2718 2701 if (self.host and ':' in self.host and
2719 2702 not (self.host.startswith('[') and self.host.endswith(']'))):
2720 2703 self._hostport = self.host
2721 2704 self.host, self.port = self.host.rsplit(':', 1)
2722 2705 if not self.host:
2723 2706 self.host = None
2724 2707
2725 2708 if (self.host and self.scheme == 'file' and
2726 2709 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2727 2710 raise Abort(_('file:// URLs can only refer to localhost'))
2728 2711
2729 2712 self.path = path
2730 2713
2731 2714 # leave the query string escaped
2732 2715 for a in ('user', 'passwd', 'host', 'port',
2733 2716 'path', 'fragment'):
2734 2717 v = getattr(self, a)
2735 2718 if v is not None:
2736 2719 setattr(self, a, urlreq.unquote(v))
2737 2720
2738 2721 def __repr__(self):
2739 2722 attrs = []
2740 2723 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2741 2724 'query', 'fragment'):
2742 2725 v = getattr(self, a)
2743 2726 if v is not None:
2744 2727 attrs.append('%s: %r' % (a, v))
2745 2728 return '<url %s>' % ', '.join(attrs)
2746 2729
2747 2730 def __str__(self):
2748 2731 r"""Join the URL's components back into a URL string.
2749 2732
2750 2733 Examples:
2751 2734
2752 2735 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2753 2736 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2754 2737 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2755 2738 'http://user:pw@host:80/?foo=bar&baz=42'
2756 2739 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2757 2740 'http://user:pw@host:80/?foo=bar%3dbaz'
2758 2741 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2759 2742 'ssh://user:pw@[::1]:2200//home/joe#'
2760 2743 >>> str(url('http://localhost:80//'))
2761 2744 'http://localhost:80//'
2762 2745 >>> str(url('http://localhost:80/'))
2763 2746 'http://localhost:80/'
2764 2747 >>> str(url('http://localhost:80'))
2765 2748 'http://localhost:80/'
2766 2749 >>> str(url('bundle:foo'))
2767 2750 'bundle:foo'
2768 2751 >>> str(url('bundle://../foo'))
2769 2752 'bundle:../foo'
2770 2753 >>> str(url('path'))
2771 2754 'path'
2772 2755 >>> str(url('file:///tmp/foo/bar'))
2773 2756 'file:///tmp/foo/bar'
2774 2757 >>> str(url('file:///c:/tmp/foo/bar'))
2775 2758 'file:///c:/tmp/foo/bar'
2776 2759 >>> print url(r'bundle:foo\bar')
2777 2760 bundle:foo\bar
2778 2761 >>> print url(r'file:///D:\data\hg')
2779 2762 file:///D:\data\hg
2780 2763 """
2781 2764 return encoding.strfromlocal(self.__bytes__())
2782 2765
2783 2766 def __bytes__(self):
2784 2767 if self._localpath:
2785 2768 s = self.path
2786 2769 if self.scheme == 'bundle':
2787 2770 s = 'bundle:' + s
2788 2771 if self.fragment:
2789 2772 s += '#' + self.fragment
2790 2773 return s
2791 2774
2792 2775 s = self.scheme + ':'
2793 2776 if self.user or self.passwd or self.host:
2794 2777 s += '//'
2795 2778 elif self.scheme and (not self.path or self.path.startswith('/')
2796 2779 or hasdriveletter(self.path)):
2797 2780 s += '//'
2798 2781 if hasdriveletter(self.path):
2799 2782 s += '/'
2800 2783 if self.user:
2801 2784 s += urlreq.quote(self.user, safe=self._safechars)
2802 2785 if self.passwd:
2803 2786 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2804 2787 if self.user or self.passwd:
2805 2788 s += '@'
2806 2789 if self.host:
2807 2790 if not (self.host.startswith('[') and self.host.endswith(']')):
2808 2791 s += urlreq.quote(self.host)
2809 2792 else:
2810 2793 s += self.host
2811 2794 if self.port:
2812 2795 s += ':' + urlreq.quote(self.port)
2813 2796 if self.host:
2814 2797 s += '/'
2815 2798 if self.path:
2816 2799 # TODO: similar to the query string, we should not unescape the
2817 2800 # path when we store it, the path might contain '%2f' = '/',
2818 2801 # which we should *not* escape.
2819 2802 s += urlreq.quote(self.path, safe=self._safepchars)
2820 2803 if self.query:
2821 2804 # we store the query in escaped form.
2822 2805 s += '?' + self.query
2823 2806 if self.fragment is not None:
2824 2807 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2825 2808 return s
2826 2809
2827 2810 def authinfo(self):
2828 2811 user, passwd = self.user, self.passwd
2829 2812 try:
2830 2813 self.user, self.passwd = None, None
2831 2814 s = bytes(self)
2832 2815 finally:
2833 2816 self.user, self.passwd = user, passwd
2834 2817 if not self.user:
2835 2818 return (s, None)
2836 2819 # authinfo[1] is passed to urllib2 password manager, and its
2837 2820 # URIs must not contain credentials. The host is passed in the
2838 2821 # URIs list because Python < 2.4.3 uses only that to search for
2839 2822 # a password.
2840 2823 return (s, (None, (s, self.host),
2841 2824 self.user, self.passwd or ''))
2842 2825
2843 2826 def isabs(self):
2844 2827 if self.scheme and self.scheme != 'file':
2845 2828 return True # remote URL
2846 2829 if hasdriveletter(self.path):
2847 2830 return True # absolute for our purposes - can't be joined()
2848 2831 if self.path.startswith(r'\\'):
2849 2832 return True # Windows UNC path
2850 2833 if self.path.startswith('/'):
2851 2834 return True # POSIX-style
2852 2835 return False
2853 2836
2854 2837 def localpath(self):
2855 2838 if self.scheme == 'file' or self.scheme == 'bundle':
2856 2839 path = self.path or '/'
2857 2840 # For Windows, we need to promote hosts containing drive
2858 2841 # letters to paths with drive letters.
2859 2842 if hasdriveletter(self._hostport):
2860 2843 path = self._hostport + '/' + self.path
2861 2844 elif (self.host is not None and self.path
2862 2845 and not hasdriveletter(path)):
2863 2846 path = '/' + path
2864 2847 return path
2865 2848 return self._origpath
2866 2849
2867 2850 def islocal(self):
2868 2851 '''whether localpath will return something that posixfile can open'''
2869 2852 return (not self.scheme or self.scheme == 'file'
2870 2853 or self.scheme == 'bundle')
2871 2854
2872 2855 def hasscheme(path):
2873 2856 return bool(url(path).scheme)
2874 2857
2875 2858 def hasdriveletter(path):
2876 2859 return path and path[1:2] == ':' and path[0:1].isalpha()
2877 2860
2878 2861 def urllocalpath(path):
2879 2862 return url(path, parsequery=False, parsefragment=False).localpath()
2880 2863
2881 2864 def hidepassword(u):
2882 2865 '''hide user credential in a url string'''
2883 2866 u = url(u)
2884 2867 if u.passwd:
2885 2868 u.passwd = '***'
2886 2869 return bytes(u)
2887 2870
2888 2871 def removeauth(u):
2889 2872 '''remove all authentication information from a url string'''
2890 2873 u = url(u)
2891 2874 u.user = u.passwd = None
2892 2875 return str(u)
2893 2876
2894 2877 timecount = unitcountfn(
2895 2878 (1, 1e3, _('%.0f s')),
2896 2879 (100, 1, _('%.1f s')),
2897 2880 (10, 1, _('%.2f s')),
2898 2881 (1, 1, _('%.3f s')),
2899 2882 (100, 0.001, _('%.1f ms')),
2900 2883 (10, 0.001, _('%.2f ms')),
2901 2884 (1, 0.001, _('%.3f ms')),
2902 2885 (100, 0.000001, _('%.1f us')),
2903 2886 (10, 0.000001, _('%.2f us')),
2904 2887 (1, 0.000001, _('%.3f us')),
2905 2888 (100, 0.000000001, _('%.1f ns')),
2906 2889 (10, 0.000000001, _('%.2f ns')),
2907 2890 (1, 0.000000001, _('%.3f ns')),
2908 2891 )
2909 2892
2910 2893 _timenesting = [0]
2911 2894
2912 2895 def timed(func):
2913 2896 '''Report the execution time of a function call to stderr.
2914 2897
2915 2898 During development, use as a decorator when you need to measure
2916 2899 the cost of a function, e.g. as follows:
2917 2900
2918 2901 @util.timed
2919 2902 def foo(a, b, c):
2920 2903 pass
2921 2904 '''
2922 2905
2923 2906 def wrapper(*args, **kwargs):
2924 2907 start = timer()
2925 2908 indent = 2
2926 2909 _timenesting[0] += indent
2927 2910 try:
2928 2911 return func(*args, **kwargs)
2929 2912 finally:
2930 2913 elapsed = timer() - start
2931 2914 _timenesting[0] -= indent
2932 2915 stderr.write('%s%s: %s\n' %
2933 2916 (' ' * _timenesting[0], func.__name__,
2934 2917 timecount(elapsed)))
2935 2918 return wrapper
2936 2919
2937 2920 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2938 2921 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2939 2922
2940 2923 def sizetoint(s):
2941 2924 '''Convert a space specifier to a byte count.
2942 2925
2943 2926 >>> sizetoint('30')
2944 2927 30
2945 2928 >>> sizetoint('2.2kb')
2946 2929 2252
2947 2930 >>> sizetoint('6M')
2948 2931 6291456
2949 2932 '''
2950 2933 t = s.strip().lower()
2951 2934 try:
2952 2935 for k, u in _sizeunits:
2953 2936 if t.endswith(k):
2954 2937 return int(float(t[:-len(k)]) * u)
2955 2938 return int(t)
2956 2939 except ValueError:
2957 2940 raise error.ParseError(_("couldn't parse size: %s") % s)
2958 2941
2959 2942 class hooks(object):
2960 2943 '''A collection of hook functions that can be used to extend a
2961 2944 function's behavior. Hooks are called in lexicographic order,
2962 2945 based on the names of their sources.'''
2963 2946
2964 2947 def __init__(self):
2965 2948 self._hooks = []
2966 2949
2967 2950 def add(self, source, hook):
2968 2951 self._hooks.append((source, hook))
2969 2952
2970 2953 def __call__(self, *args):
2971 2954 self._hooks.sort(key=lambda x: x[0])
2972 2955 results = []
2973 2956 for source, hook in self._hooks:
2974 2957 results.append(hook(*args))
2975 2958 return results
2976 2959
2977 2960 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
2978 2961 '''Yields lines for a nicely formatted stacktrace.
2979 2962 Skips the 'skip' last entries, then return the last 'depth' entries.
2980 2963 Each file+linenumber is formatted according to fileline.
2981 2964 Each line is formatted according to line.
2982 2965 If line is None, it yields:
2983 2966 length of longest filepath+line number,
2984 2967 filepath+linenumber,
2985 2968 function
2986 2969
2987 2970 Not be used in production code but very convenient while developing.
2988 2971 '''
2989 2972 entries = [(fileline % (fn, ln), func)
2990 2973 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
2991 2974 ][-depth:]
2992 2975 if entries:
2993 2976 fnmax = max(len(entry[0]) for entry in entries)
2994 2977 for fnln, func in entries:
2995 2978 if line is None:
2996 2979 yield (fnmax, fnln, func)
2997 2980 else:
2998 2981 yield line % (fnmax, fnln, func)
2999 2982
3000 2983 def debugstacktrace(msg='stacktrace', skip=0,
3001 2984 f=stderr, otherf=stdout, depth=0):
3002 2985 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3003 2986 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3004 2987 By default it will flush stdout first.
3005 2988 It can be used everywhere and intentionally does not require an ui object.
3006 2989 Not be used in production code but very convenient while developing.
3007 2990 '''
3008 2991 if otherf:
3009 2992 otherf.flush()
3010 2993 f.write('%s at:\n' % msg.rstrip())
3011 2994 for line in getstackframes(skip + 1, depth=depth):
3012 2995 f.write(line)
3013 2996 f.flush()
3014 2997
3015 2998 class dirs(object):
3016 2999 '''a multiset of directory names from a dirstate or manifest'''
3017 3000
3018 3001 def __init__(self, map, skip=None):
3019 3002 self._dirs = {}
3020 3003 addpath = self.addpath
3021 3004 if safehasattr(map, 'iteritems') and skip is not None:
3022 3005 for f, s in map.iteritems():
3023 3006 if s[0] != skip:
3024 3007 addpath(f)
3025 3008 else:
3026 3009 for f in map:
3027 3010 addpath(f)
3028 3011
3029 3012 def addpath(self, path):
3030 3013 dirs = self._dirs
3031 3014 for base in finddirs(path):
3032 3015 if base in dirs:
3033 3016 dirs[base] += 1
3034 3017 return
3035 3018 dirs[base] = 1
3036 3019
3037 3020 def delpath(self, path):
3038 3021 dirs = self._dirs
3039 3022 for base in finddirs(path):
3040 3023 if dirs[base] > 1:
3041 3024 dirs[base] -= 1
3042 3025 return
3043 3026 del dirs[base]
3044 3027
3045 3028 def __iter__(self):
3046 3029 return iter(self._dirs)
3047 3030
3048 3031 def __contains__(self, d):
3049 3032 return d in self._dirs
3050 3033
3051 3034 if safehasattr(parsers, 'dirs'):
3052 3035 dirs = parsers.dirs
3053 3036
3054 3037 def finddirs(path):
3055 3038 pos = path.rfind('/')
3056 3039 while pos != -1:
3057 3040 yield path[:pos]
3058 3041 pos = path.rfind('/', 0, pos)
3059 3042
3060 3043 class ctxmanager(object):
3061 3044 '''A context manager for use in 'with' blocks to allow multiple
3062 3045 contexts to be entered at once. This is both safer and more
3063 3046 flexible than contextlib.nested.
3064 3047
3065 3048 Once Mercurial supports Python 2.7+, this will become mostly
3066 3049 unnecessary.
3067 3050 '''
3068 3051
3069 3052 def __init__(self, *args):
3070 3053 '''Accepts a list of no-argument functions that return context
3071 3054 managers. These will be invoked at __call__ time.'''
3072 3055 self._pending = args
3073 3056 self._atexit = []
3074 3057
3075 3058 def __enter__(self):
3076 3059 return self
3077 3060
3078 3061 def enter(self):
3079 3062 '''Create and enter context managers in the order in which they were
3080 3063 passed to the constructor.'''
3081 3064 values = []
3082 3065 for func in self._pending:
3083 3066 obj = func()
3084 3067 values.append(obj.__enter__())
3085 3068 self._atexit.append(obj.__exit__)
3086 3069 del self._pending
3087 3070 return values
3088 3071
3089 3072 def atexit(self, func, *args, **kwargs):
3090 3073 '''Add a function to call when this context manager exits. The
3091 3074 ordering of multiple atexit calls is unspecified, save that
3092 3075 they will happen before any __exit__ functions.'''
3093 3076 def wrapper(exc_type, exc_val, exc_tb):
3094 3077 func(*args, **kwargs)
3095 3078 self._atexit.append(wrapper)
3096 3079 return func
3097 3080
3098 3081 def __exit__(self, exc_type, exc_val, exc_tb):
3099 3082 '''Context managers are exited in the reverse order from which
3100 3083 they were created.'''
3101 3084 received = exc_type is not None
3102 3085 suppressed = False
3103 3086 pending = None
3104 3087 self._atexit.reverse()
3105 3088 for exitfunc in self._atexit:
3106 3089 try:
3107 3090 if exitfunc(exc_type, exc_val, exc_tb):
3108 3091 suppressed = True
3109 3092 exc_type = None
3110 3093 exc_val = None
3111 3094 exc_tb = None
3112 3095 except BaseException:
3113 3096 pending = sys.exc_info()
3114 3097 exc_type, exc_val, exc_tb = pending = sys.exc_info()
3115 3098 del self._atexit
3116 3099 if pending:
3117 3100 raise exc_val
3118 3101 return received and suppressed
3119 3102
3120 3103 # compression code
3121 3104
3122 3105 SERVERROLE = 'server'
3123 3106 CLIENTROLE = 'client'
3124 3107
3125 3108 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3126 3109 (u'name', u'serverpriority',
3127 3110 u'clientpriority'))
3128 3111
3129 3112 class compressormanager(object):
3130 3113 """Holds registrations of various compression engines.
3131 3114
3132 3115 This class essentially abstracts the differences between compression
3133 3116 engines to allow new compression formats to be added easily, possibly from
3134 3117 extensions.
3135 3118
3136 3119 Compressors are registered against the global instance by calling its
3137 3120 ``register()`` method.
3138 3121 """
3139 3122 def __init__(self):
3140 3123 self._engines = {}
3141 3124 # Bundle spec human name to engine name.
3142 3125 self._bundlenames = {}
3143 3126 # Internal bundle identifier to engine name.
3144 3127 self._bundletypes = {}
3145 3128 # Revlog header to engine name.
3146 3129 self._revlogheaders = {}
3147 3130 # Wire proto identifier to engine name.
3148 3131 self._wiretypes = {}
3149 3132
3150 3133 def __getitem__(self, key):
3151 3134 return self._engines[key]
3152 3135
3153 3136 def __contains__(self, key):
3154 3137 return key in self._engines
3155 3138
3156 3139 def __iter__(self):
3157 3140 return iter(self._engines.keys())
3158 3141
3159 3142 def register(self, engine):
3160 3143 """Register a compression engine with the manager.
3161 3144
3162 3145 The argument must be a ``compressionengine`` instance.
3163 3146 """
3164 3147 if not isinstance(engine, compressionengine):
3165 3148 raise ValueError(_('argument must be a compressionengine'))
3166 3149
3167 3150 name = engine.name()
3168 3151
3169 3152 if name in self._engines:
3170 3153 raise error.Abort(_('compression engine %s already registered') %
3171 3154 name)
3172 3155
3173 3156 bundleinfo = engine.bundletype()
3174 3157 if bundleinfo:
3175 3158 bundlename, bundletype = bundleinfo
3176 3159
3177 3160 if bundlename in self._bundlenames:
3178 3161 raise error.Abort(_('bundle name %s already registered') %
3179 3162 bundlename)
3180 3163 if bundletype in self._bundletypes:
3181 3164 raise error.Abort(_('bundle type %s already registered by %s') %
3182 3165 (bundletype, self._bundletypes[bundletype]))
3183 3166
3184 3167 # No external facing name declared.
3185 3168 if bundlename:
3186 3169 self._bundlenames[bundlename] = name
3187 3170
3188 3171 self._bundletypes[bundletype] = name
3189 3172
3190 3173 wiresupport = engine.wireprotosupport()
3191 3174 if wiresupport:
3192 3175 wiretype = wiresupport.name
3193 3176 if wiretype in self._wiretypes:
3194 3177 raise error.Abort(_('wire protocol compression %s already '
3195 3178 'registered by %s') %
3196 3179 (wiretype, self._wiretypes[wiretype]))
3197 3180
3198 3181 self._wiretypes[wiretype] = name
3199 3182
3200 3183 revlogheader = engine.revlogheader()
3201 3184 if revlogheader and revlogheader in self._revlogheaders:
3202 3185 raise error.Abort(_('revlog header %s already registered by %s') %
3203 3186 (revlogheader, self._revlogheaders[revlogheader]))
3204 3187
3205 3188 if revlogheader:
3206 3189 self._revlogheaders[revlogheader] = name
3207 3190
3208 3191 self._engines[name] = engine
3209 3192
3210 3193 @property
3211 3194 def supportedbundlenames(self):
3212 3195 return set(self._bundlenames.keys())
3213 3196
3214 3197 @property
3215 3198 def supportedbundletypes(self):
3216 3199 return set(self._bundletypes.keys())
3217 3200
3218 3201 def forbundlename(self, bundlename):
3219 3202 """Obtain a compression engine registered to a bundle name.
3220 3203
3221 3204 Will raise KeyError if the bundle type isn't registered.
3222 3205
3223 3206 Will abort if the engine is known but not available.
3224 3207 """
3225 3208 engine = self._engines[self._bundlenames[bundlename]]
3226 3209 if not engine.available():
3227 3210 raise error.Abort(_('compression engine %s could not be loaded') %
3228 3211 engine.name())
3229 3212 return engine
3230 3213
3231 3214 def forbundletype(self, bundletype):
3232 3215 """Obtain a compression engine registered to a bundle type.
3233 3216
3234 3217 Will raise KeyError if the bundle type isn't registered.
3235 3218
3236 3219 Will abort if the engine is known but not available.
3237 3220 """
3238 3221 engine = self._engines[self._bundletypes[bundletype]]
3239 3222 if not engine.available():
3240 3223 raise error.Abort(_('compression engine %s could not be loaded') %
3241 3224 engine.name())
3242 3225 return engine
3243 3226
3244 3227 def supportedwireengines(self, role, onlyavailable=True):
3245 3228 """Obtain compression engines that support the wire protocol.
3246 3229
3247 3230 Returns a list of engines in prioritized order, most desired first.
3248 3231
3249 3232 If ``onlyavailable`` is set, filter out engines that can't be
3250 3233 loaded.
3251 3234 """
3252 3235 assert role in (SERVERROLE, CLIENTROLE)
3253 3236
3254 3237 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3255 3238
3256 3239 engines = [self._engines[e] for e in self._wiretypes.values()]
3257 3240 if onlyavailable:
3258 3241 engines = [e for e in engines if e.available()]
3259 3242
3260 3243 def getkey(e):
3261 3244 # Sort first by priority, highest first. In case of tie, sort
3262 3245 # alphabetically. This is arbitrary, but ensures output is
3263 3246 # stable.
3264 3247 w = e.wireprotosupport()
3265 3248 return -1 * getattr(w, attr), w.name
3266 3249
3267 3250 return list(sorted(engines, key=getkey))
3268 3251
3269 3252 def forwiretype(self, wiretype):
3270 3253 engine = self._engines[self._wiretypes[wiretype]]
3271 3254 if not engine.available():
3272 3255 raise error.Abort(_('compression engine %s could not be loaded') %
3273 3256 engine.name())
3274 3257 return engine
3275 3258
3276 3259 def forrevlogheader(self, header):
3277 3260 """Obtain a compression engine registered to a revlog header.
3278 3261
3279 3262 Will raise KeyError if the revlog header value isn't registered.
3280 3263 """
3281 3264 return self._engines[self._revlogheaders[header]]
3282 3265
3283 3266 compengines = compressormanager()
3284 3267
3285 3268 class compressionengine(object):
3286 3269 """Base class for compression engines.
3287 3270
3288 3271 Compression engines must implement the interface defined by this class.
3289 3272 """
3290 3273 def name(self):
3291 3274 """Returns the name of the compression engine.
3292 3275
3293 3276 This is the key the engine is registered under.
3294 3277
3295 3278 This method must be implemented.
3296 3279 """
3297 3280 raise NotImplementedError()
3298 3281
3299 3282 def available(self):
3300 3283 """Whether the compression engine is available.
3301 3284
3302 3285 The intent of this method is to allow optional compression engines
3303 3286 that may not be available in all installations (such as engines relying
3304 3287 on C extensions that may not be present).
3305 3288 """
3306 3289 return True
3307 3290
3308 3291 def bundletype(self):
3309 3292 """Describes bundle identifiers for this engine.
3310 3293
3311 3294 If this compression engine isn't supported for bundles, returns None.
3312 3295
3313 3296 If this engine can be used for bundles, returns a 2-tuple of strings of
3314 3297 the user-facing "bundle spec" compression name and an internal
3315 3298 identifier used to denote the compression format within bundles. To
3316 3299 exclude the name from external usage, set the first element to ``None``.
3317 3300
3318 3301 If bundle compression is supported, the class must also implement
3319 3302 ``compressstream`` and `decompressorreader``.
3320 3303
3321 3304 The docstring of this method is used in the help system to tell users
3322 3305 about this engine.
3323 3306 """
3324 3307 return None
3325 3308
3326 3309 def wireprotosupport(self):
3327 3310 """Declare support for this compression format on the wire protocol.
3328 3311
3329 3312 If this compression engine isn't supported for compressing wire
3330 3313 protocol payloads, returns None.
3331 3314
3332 3315 Otherwise, returns ``compenginewireprotosupport`` with the following
3333 3316 fields:
3334 3317
3335 3318 * String format identifier
3336 3319 * Integer priority for the server
3337 3320 * Integer priority for the client
3338 3321
3339 3322 The integer priorities are used to order the advertisement of format
3340 3323 support by server and client. The highest integer is advertised
3341 3324 first. Integers with non-positive values aren't advertised.
3342 3325
3343 3326 The priority values are somewhat arbitrary and only used for default
3344 3327 ordering. The relative order can be changed via config options.
3345 3328
3346 3329 If wire protocol compression is supported, the class must also implement
3347 3330 ``compressstream`` and ``decompressorreader``.
3348 3331 """
3349 3332 return None
3350 3333
3351 3334 def revlogheader(self):
3352 3335 """Header added to revlog chunks that identifies this engine.
3353 3336
3354 3337 If this engine can be used to compress revlogs, this method should
3355 3338 return the bytes used to identify chunks compressed with this engine.
3356 3339 Else, the method should return ``None`` to indicate it does not
3357 3340 participate in revlog compression.
3358 3341 """
3359 3342 return None
3360 3343
3361 3344 def compressstream(self, it, opts=None):
3362 3345 """Compress an iterator of chunks.
3363 3346
3364 3347 The method receives an iterator (ideally a generator) of chunks of
3365 3348 bytes to be compressed. It returns an iterator (ideally a generator)
3366 3349 of bytes of chunks representing the compressed output.
3367 3350
3368 3351 Optionally accepts an argument defining how to perform compression.
3369 3352 Each engine treats this argument differently.
3370 3353 """
3371 3354 raise NotImplementedError()
3372 3355
3373 3356 def decompressorreader(self, fh):
3374 3357 """Perform decompression on a file object.
3375 3358
3376 3359 Argument is an object with a ``read(size)`` method that returns
3377 3360 compressed data. Return value is an object with a ``read(size)`` that
3378 3361 returns uncompressed data.
3379 3362 """
3380 3363 raise NotImplementedError()
3381 3364
3382 3365 def revlogcompressor(self, opts=None):
3383 3366 """Obtain an object that can be used to compress revlog entries.
3384 3367
3385 3368 The object has a ``compress(data)`` method that compresses binary
3386 3369 data. This method returns compressed binary data or ``None`` if
3387 3370 the data could not be compressed (too small, not compressible, etc).
3388 3371 The returned data should have a header uniquely identifying this
3389 3372 compression format so decompression can be routed to this engine.
3390 3373 This header should be identified by the ``revlogheader()`` return
3391 3374 value.
3392 3375
3393 3376 The object has a ``decompress(data)`` method that decompresses
3394 3377 data. The method will only be called if ``data`` begins with
3395 3378 ``revlogheader()``. The method should return the raw, uncompressed
3396 3379 data or raise a ``RevlogError``.
3397 3380
3398 3381 The object is reusable but is not thread safe.
3399 3382 """
3400 3383 raise NotImplementedError()
3401 3384
3402 3385 class _zlibengine(compressionengine):
3403 3386 def name(self):
3404 3387 return 'zlib'
3405 3388
3406 3389 def bundletype(self):
3407 3390 """zlib compression using the DEFLATE algorithm.
3408 3391
3409 3392 All Mercurial clients should support this format. The compression
3410 3393 algorithm strikes a reasonable balance between compression ratio
3411 3394 and size.
3412 3395 """
3413 3396 return 'gzip', 'GZ'
3414 3397
3415 3398 def wireprotosupport(self):
3416 3399 return compewireprotosupport('zlib', 20, 20)
3417 3400
3418 3401 def revlogheader(self):
3419 3402 return 'x'
3420 3403
3421 3404 def compressstream(self, it, opts=None):
3422 3405 opts = opts or {}
3423 3406
3424 3407 z = zlib.compressobj(opts.get('level', -1))
3425 3408 for chunk in it:
3426 3409 data = z.compress(chunk)
3427 3410 # Not all calls to compress emit data. It is cheaper to inspect
3428 3411 # here than to feed empty chunks through generator.
3429 3412 if data:
3430 3413 yield data
3431 3414
3432 3415 yield z.flush()
3433 3416
3434 3417 def decompressorreader(self, fh):
3435 3418 def gen():
3436 3419 d = zlib.decompressobj()
3437 3420 for chunk in filechunkiter(fh):
3438 3421 while chunk:
3439 3422 # Limit output size to limit memory.
3440 3423 yield d.decompress(chunk, 2 ** 18)
3441 3424 chunk = d.unconsumed_tail
3442 3425
3443 3426 return chunkbuffer(gen())
3444 3427
3445 3428 class zlibrevlogcompressor(object):
3446 3429 def compress(self, data):
3447 3430 insize = len(data)
3448 3431 # Caller handles empty input case.
3449 3432 assert insize > 0
3450 3433
3451 3434 if insize < 44:
3452 3435 return None
3453 3436
3454 3437 elif insize <= 1000000:
3455 3438 compressed = zlib.compress(data)
3456 3439 if len(compressed) < insize:
3457 3440 return compressed
3458 3441 return None
3459 3442
3460 3443 # zlib makes an internal copy of the input buffer, doubling
3461 3444 # memory usage for large inputs. So do streaming compression
3462 3445 # on large inputs.
3463 3446 else:
3464 3447 z = zlib.compressobj()
3465 3448 parts = []
3466 3449 pos = 0
3467 3450 while pos < insize:
3468 3451 pos2 = pos + 2**20
3469 3452 parts.append(z.compress(data[pos:pos2]))
3470 3453 pos = pos2
3471 3454 parts.append(z.flush())
3472 3455
3473 3456 if sum(map(len, parts)) < insize:
3474 3457 return ''.join(parts)
3475 3458 return None
3476 3459
3477 3460 def decompress(self, data):
3478 3461 try:
3479 3462 return zlib.decompress(data)
3480 3463 except zlib.error as e:
3481 3464 raise error.RevlogError(_('revlog decompress error: %s') %
3482 3465 str(e))
3483 3466
3484 3467 def revlogcompressor(self, opts=None):
3485 3468 return self.zlibrevlogcompressor()
3486 3469
3487 3470 compengines.register(_zlibengine())
3488 3471
3489 3472 class _bz2engine(compressionengine):
3490 3473 def name(self):
3491 3474 return 'bz2'
3492 3475
3493 3476 def bundletype(self):
3494 3477 """An algorithm that produces smaller bundles than ``gzip``.
3495 3478
3496 3479 All Mercurial clients should support this format.
3497 3480
3498 3481 This engine will likely produce smaller bundles than ``gzip`` but
3499 3482 will be significantly slower, both during compression and
3500 3483 decompression.
3501 3484
3502 3485 If available, the ``zstd`` engine can yield similar or better
3503 3486 compression at much higher speeds.
3504 3487 """
3505 3488 return 'bzip2', 'BZ'
3506 3489
3507 3490 # We declare a protocol name but don't advertise by default because
3508 3491 # it is slow.
3509 3492 def wireprotosupport(self):
3510 3493 return compewireprotosupport('bzip2', 0, 0)
3511 3494
3512 3495 def compressstream(self, it, opts=None):
3513 3496 opts = opts or {}
3514 3497 z = bz2.BZ2Compressor(opts.get('level', 9))
3515 3498 for chunk in it:
3516 3499 data = z.compress(chunk)
3517 3500 if data:
3518 3501 yield data
3519 3502
3520 3503 yield z.flush()
3521 3504
3522 3505 def decompressorreader(self, fh):
3523 3506 def gen():
3524 3507 d = bz2.BZ2Decompressor()
3525 3508 for chunk in filechunkiter(fh):
3526 3509 yield d.decompress(chunk)
3527 3510
3528 3511 return chunkbuffer(gen())
3529 3512
3530 3513 compengines.register(_bz2engine())
3531 3514
3532 3515 class _truncatedbz2engine(compressionengine):
3533 3516 def name(self):
3534 3517 return 'bz2truncated'
3535 3518
3536 3519 def bundletype(self):
3537 3520 return None, '_truncatedBZ'
3538 3521
3539 3522 # We don't implement compressstream because it is hackily handled elsewhere.
3540 3523
3541 3524 def decompressorreader(self, fh):
3542 3525 def gen():
3543 3526 # The input stream doesn't have the 'BZ' header. So add it back.
3544 3527 d = bz2.BZ2Decompressor()
3545 3528 d.decompress('BZ')
3546 3529 for chunk in filechunkiter(fh):
3547 3530 yield d.decompress(chunk)
3548 3531
3549 3532 return chunkbuffer(gen())
3550 3533
3551 3534 compengines.register(_truncatedbz2engine())
3552 3535
3553 3536 class _noopengine(compressionengine):
3554 3537 def name(self):
3555 3538 return 'none'
3556 3539
3557 3540 def bundletype(self):
3558 3541 """No compression is performed.
3559 3542
3560 3543 Use this compression engine to explicitly disable compression.
3561 3544 """
3562 3545 return 'none', 'UN'
3563 3546
3564 3547 # Clients always support uncompressed payloads. Servers don't because
3565 3548 # unless you are on a fast network, uncompressed payloads can easily
3566 3549 # saturate your network pipe.
3567 3550 def wireprotosupport(self):
3568 3551 return compewireprotosupport('none', 0, 10)
3569 3552
3570 3553 # We don't implement revlogheader because it is handled specially
3571 3554 # in the revlog class.
3572 3555
3573 3556 def compressstream(self, it, opts=None):
3574 3557 return it
3575 3558
3576 3559 def decompressorreader(self, fh):
3577 3560 return fh
3578 3561
3579 3562 class nooprevlogcompressor(object):
3580 3563 def compress(self, data):
3581 3564 return None
3582 3565
3583 3566 def revlogcompressor(self, opts=None):
3584 3567 return self.nooprevlogcompressor()
3585 3568
3586 3569 compengines.register(_noopengine())
3587 3570
3588 3571 class _zstdengine(compressionengine):
3589 3572 def name(self):
3590 3573 return 'zstd'
3591 3574
3592 3575 @propertycache
3593 3576 def _module(self):
3594 3577 # Not all installs have the zstd module available. So defer importing
3595 3578 # until first access.
3596 3579 try:
3597 3580 from . import zstd
3598 3581 # Force delayed import.
3599 3582 zstd.__version__
3600 3583 return zstd
3601 3584 except ImportError:
3602 3585 return None
3603 3586
3604 3587 def available(self):
3605 3588 return bool(self._module)
3606 3589
3607 3590 def bundletype(self):
3608 3591 """A modern compression algorithm that is fast and highly flexible.
3609 3592
3610 3593 Only supported by Mercurial 4.1 and newer clients.
3611 3594
3612 3595 With the default settings, zstd compression is both faster and yields
3613 3596 better compression than ``gzip``. It also frequently yields better
3614 3597 compression than ``bzip2`` while operating at much higher speeds.
3615 3598
3616 3599 If this engine is available and backwards compatibility is not a
3617 3600 concern, it is likely the best available engine.
3618 3601 """
3619 3602 return 'zstd', 'ZS'
3620 3603
3621 3604 def wireprotosupport(self):
3622 3605 return compewireprotosupport('zstd', 50, 50)
3623 3606
3624 3607 def revlogheader(self):
3625 3608 return '\x28'
3626 3609
3627 3610 def compressstream(self, it, opts=None):
3628 3611 opts = opts or {}
3629 3612 # zstd level 3 is almost always significantly faster than zlib
3630 3613 # while providing no worse compression. It strikes a good balance
3631 3614 # between speed and compression.
3632 3615 level = opts.get('level', 3)
3633 3616
3634 3617 zstd = self._module
3635 3618 z = zstd.ZstdCompressor(level=level).compressobj()
3636 3619 for chunk in it:
3637 3620 data = z.compress(chunk)
3638 3621 if data:
3639 3622 yield data
3640 3623
3641 3624 yield z.flush()
3642 3625
3643 3626 def decompressorreader(self, fh):
3644 3627 zstd = self._module
3645 3628 dctx = zstd.ZstdDecompressor()
3646 3629 return chunkbuffer(dctx.read_from(fh))
3647 3630
3648 3631 class zstdrevlogcompressor(object):
3649 3632 def __init__(self, zstd, level=3):
3650 3633 # Writing the content size adds a few bytes to the output. However,
3651 3634 # it allows decompression to be more optimal since we can
3652 3635 # pre-allocate a buffer to hold the result.
3653 3636 self._cctx = zstd.ZstdCompressor(level=level,
3654 3637 write_content_size=True)
3655 3638 self._dctx = zstd.ZstdDecompressor()
3656 3639 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3657 3640 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3658 3641
3659 3642 def compress(self, data):
3660 3643 insize = len(data)
3661 3644 # Caller handles empty input case.
3662 3645 assert insize > 0
3663 3646
3664 3647 if insize < 50:
3665 3648 return None
3666 3649
3667 3650 elif insize <= 1000000:
3668 3651 compressed = self._cctx.compress(data)
3669 3652 if len(compressed) < insize:
3670 3653 return compressed
3671 3654 return None
3672 3655 else:
3673 3656 z = self._cctx.compressobj()
3674 3657 chunks = []
3675 3658 pos = 0
3676 3659 while pos < insize:
3677 3660 pos2 = pos + self._compinsize
3678 3661 chunk = z.compress(data[pos:pos2])
3679 3662 if chunk:
3680 3663 chunks.append(chunk)
3681 3664 pos = pos2
3682 3665 chunks.append(z.flush())
3683 3666
3684 3667 if sum(map(len, chunks)) < insize:
3685 3668 return ''.join(chunks)
3686 3669 return None
3687 3670
3688 3671 def decompress(self, data):
3689 3672 insize = len(data)
3690 3673
3691 3674 try:
3692 3675 # This was measured to be faster than other streaming
3693 3676 # decompressors.
3694 3677 dobj = self._dctx.decompressobj()
3695 3678 chunks = []
3696 3679 pos = 0
3697 3680 while pos < insize:
3698 3681 pos2 = pos + self._decompinsize
3699 3682 chunk = dobj.decompress(data[pos:pos2])
3700 3683 if chunk:
3701 3684 chunks.append(chunk)
3702 3685 pos = pos2
3703 3686 # Frame should be exhausted, so no finish() API.
3704 3687
3705 3688 return ''.join(chunks)
3706 3689 except Exception as e:
3707 3690 raise error.RevlogError(_('revlog decompress error: %s') %
3708 3691 str(e))
3709 3692
3710 3693 def revlogcompressor(self, opts=None):
3711 3694 opts = opts or {}
3712 3695 return self.zstdrevlogcompressor(self._module,
3713 3696 level=opts.get('level', 3))
3714 3697
3715 3698 compengines.register(_zstdengine())
3716 3699
3717 3700 def bundlecompressiontopics():
3718 3701 """Obtains a list of available bundle compressions for use in help."""
3719 3702 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3720 3703 items = {}
3721 3704
3722 3705 # We need to format the docstring. So use a dummy object/type to hold it
3723 3706 # rather than mutating the original.
3724 3707 class docobject(object):
3725 3708 pass
3726 3709
3727 3710 for name in compengines:
3728 3711 engine = compengines[name]
3729 3712
3730 3713 if not engine.available():
3731 3714 continue
3732 3715
3733 3716 bt = engine.bundletype()
3734 3717 if not bt or not bt[0]:
3735 3718 continue
3736 3719
3737 3720 doc = pycompat.sysstr('``%s``\n %s') % (
3738 3721 bt[0], engine.bundletype.__doc__)
3739 3722
3740 3723 value = docobject()
3741 3724 value.__doc__ = doc
3742 3725
3743 3726 items[bt[0]] = value
3744 3727
3745 3728 return items
3746 3729
3747 3730 # convenient shortcut
3748 3731 dst = debugstacktrace
@@ -1,793 +1,793 b''
1 1 commit date test
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo > foo
6 6 $ hg add foo
7 7 $ cat > $TESTTMP/checkeditform.sh <<EOF
8 8 > env | grep HGEDITFORM
9 9 > true
10 10 > EOF
11 11 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
12 12 HGEDITFORM=commit.normal.normal
13 13 abort: empty commit message
14 14 [255]
15 15 $ hg commit -d '0 0' -m commit-1
16 16 $ echo foo >> foo
17 17 $ hg commit -d '1 4444444' -m commit-3
18 abort: impossible time zone offset: 4444444
18 hg: parse error: impossible time zone offset: 4444444
19 19 [255]
20 20 $ hg commit -d '1 15.1' -m commit-4
21 abort: invalid date: '1\t15.1'
21 hg: parse error: invalid date: '1\t15.1'
22 22 [255]
23 23 $ hg commit -d 'foo bar' -m commit-5
24 abort: invalid date: 'foo bar'
24 hg: parse error: invalid date: 'foo bar'
25 25 [255]
26 26 $ hg commit -d ' 1 4444' -m commit-6
27 27 $ hg commit -d '111111111111 0' -m commit-7
28 abort: date exceeds 32 bits: 111111111111
28 hg: parse error: date exceeds 32 bits: 111111111111
29 29 [255]
30 30 $ hg commit -d '-111111111111 0' -m commit-7
31 abort: date exceeds 32 bits: -111111111111
31 hg: parse error: date exceeds 32 bits: -111111111111
32 32 [255]
33 33 $ echo foo >> foo
34 34 $ hg commit -d '1901-12-13 20:45:52 +0000' -m commit-7-2
35 35 $ echo foo >> foo
36 36 $ hg commit -d '-2147483648 0' -m commit-7-3
37 37 $ hg log -T '{rev} {date|isodatesec}\n' -l2
38 38 3 1901-12-13 20:45:52 +0000
39 39 2 1901-12-13 20:45:52 +0000
40 40 $ hg commit -d '1901-12-13 20:45:51 +0000' -m commit-7
41 abort: date exceeds 32 bits: -2147483649
41 hg: parse error: date exceeds 32 bits: -2147483649
42 42 [255]
43 43 $ hg commit -d '-2147483649 0' -m commit-7
44 abort: date exceeds 32 bits: -2147483649
44 hg: parse error: date exceeds 32 bits: -2147483649
45 45 [255]
46 46
47 47 commit added file that has been deleted
48 48
49 49 $ echo bar > bar
50 50 $ hg add bar
51 51 $ rm bar
52 52 $ hg commit -m commit-8
53 53 nothing changed (1 missing files, see 'hg status')
54 54 [1]
55 55 $ hg commit -m commit-8-2 bar
56 56 abort: bar: file not found!
57 57 [255]
58 58
59 59 $ hg -q revert -a --no-backup
60 60
61 61 $ mkdir dir
62 62 $ echo boo > dir/file
63 63 $ hg add
64 64 adding dir/file (glob)
65 65 $ hg -v commit -m commit-9 dir
66 66 committing files:
67 67 dir/file
68 68 committing manifest
69 69 committing changelog
70 70 committed changeset 4:1957363f1ced
71 71
72 72 $ echo > dir.file
73 73 $ hg add
74 74 adding dir.file
75 75 $ hg commit -m commit-10 dir dir.file
76 76 abort: dir: no match under directory!
77 77 [255]
78 78
79 79 $ echo >> dir/file
80 80 $ mkdir bleh
81 81 $ mkdir dir2
82 82 $ cd bleh
83 83 $ hg commit -m commit-11 .
84 84 abort: bleh: no match under directory!
85 85 [255]
86 86 $ hg commit -m commit-12 ../dir ../dir2
87 87 abort: dir2: no match under directory!
88 88 [255]
89 89 $ hg -v commit -m commit-13 ../dir
90 90 committing files:
91 91 dir/file
92 92 committing manifest
93 93 committing changelog
94 94 committed changeset 5:a31d8f87544a
95 95 $ cd ..
96 96
97 97 $ hg commit -m commit-14 does-not-exist
98 98 abort: does-not-exist: * (glob)
99 99 [255]
100 100
101 101 #if symlink
102 102 $ ln -s foo baz
103 103 $ hg commit -m commit-15 baz
104 104 abort: baz: file not tracked!
105 105 [255]
106 106 #endif
107 107
108 108 $ touch quux
109 109 $ hg commit -m commit-16 quux
110 110 abort: quux: file not tracked!
111 111 [255]
112 112 $ echo >> dir/file
113 113 $ hg -v commit -m commit-17 dir/file
114 114 committing files:
115 115 dir/file
116 116 committing manifest
117 117 committing changelog
118 118 committed changeset 6:32d054c9d085
119 119
120 120 An empty date was interpreted as epoch origin
121 121
122 122 $ echo foo >> foo
123 123 $ hg commit -d '' -m commit-no-date --config devel.default-date=
124 124 $ hg tip --template '{date|isodate}\n' | grep '1970'
125 125 [1]
126 126
127 127 Make sure we do not obscure unknown requires file entries (issue2649)
128 128
129 129 $ echo foo >> foo
130 130 $ echo fake >> .hg/requires
131 131 $ hg commit -m bla
132 132 abort: repository requires features unknown to this Mercurial: fake!
133 133 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
134 134 [255]
135 135
136 136 $ cd ..
137 137
138 138
139 139 partial subdir commit test
140 140
141 141 $ hg init test2
142 142 $ cd test2
143 143 $ mkdir foo
144 144 $ echo foo > foo/foo
145 145 $ mkdir bar
146 146 $ echo bar > bar/bar
147 147 $ hg add
148 148 adding bar/bar (glob)
149 149 adding foo/foo (glob)
150 150 $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo
151 151 commit-subdir-1
152 152
153 153
154 154 HG: Enter commit message. Lines beginning with 'HG:' are removed.
155 155 HG: Leave message empty to abort commit.
156 156 HG: --
157 157 HG: user: test
158 158 HG: branch 'default'
159 159 HG: added foo/foo
160 160
161 161
162 162 $ hg ci -m commit-subdir-2 bar
163 163
164 164 subdir log 1
165 165
166 166 $ hg log -v foo
167 167 changeset: 0:f97e73a25882
168 168 user: test
169 169 date: Thu Jan 01 00:00:00 1970 +0000
170 170 files: foo/foo
171 171 description:
172 172 commit-subdir-1
173 173
174 174
175 175
176 176 subdir log 2
177 177
178 178 $ hg log -v bar
179 179 changeset: 1:aa809156d50d
180 180 tag: tip
181 181 user: test
182 182 date: Thu Jan 01 00:00:00 1970 +0000
183 183 files: bar/bar
184 184 description:
185 185 commit-subdir-2
186 186
187 187
188 188
189 189 full log
190 190
191 191 $ hg log -v
192 192 changeset: 1:aa809156d50d
193 193 tag: tip
194 194 user: test
195 195 date: Thu Jan 01 00:00:00 1970 +0000
196 196 files: bar/bar
197 197 description:
198 198 commit-subdir-2
199 199
200 200
201 201 changeset: 0:f97e73a25882
202 202 user: test
203 203 date: Thu Jan 01 00:00:00 1970 +0000
204 204 files: foo/foo
205 205 description:
206 206 commit-subdir-1
207 207
208 208
209 209 $ cd ..
210 210
211 211
212 212 dot and subdir commit test
213 213
214 214 $ hg init test3
215 215 $ echo commit-foo-subdir > commit-log-test
216 216 $ cd test3
217 217 $ mkdir foo
218 218 $ echo foo content > foo/plain-file
219 219 $ hg add foo/plain-file
220 220 $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo
221 221 commit-foo-subdir
222 222
223 223
224 224 HG: Enter commit message. Lines beginning with 'HG:' are removed.
225 225 HG: Leave message empty to abort commit.
226 226 HG: --
227 227 HG: user: test
228 228 HG: branch 'default'
229 229 HG: added foo/plain-file
230 230
231 231
232 232 $ echo modified foo content > foo/plain-file
233 233 $ hg ci -m commit-foo-dot .
234 234
235 235 full log
236 236
237 237 $ hg log -v
238 238 changeset: 1:95b38e3a5b2e
239 239 tag: tip
240 240 user: test
241 241 date: Thu Jan 01 00:00:00 1970 +0000
242 242 files: foo/plain-file
243 243 description:
244 244 commit-foo-dot
245 245
246 246
247 247 changeset: 0:65d4e9386227
248 248 user: test
249 249 date: Thu Jan 01 00:00:00 1970 +0000
250 250 files: foo/plain-file
251 251 description:
252 252 commit-foo-subdir
253 253
254 254
255 255
256 256 subdir log
257 257
258 258 $ cd foo
259 259 $ hg log .
260 260 changeset: 1:95b38e3a5b2e
261 261 tag: tip
262 262 user: test
263 263 date: Thu Jan 01 00:00:00 1970 +0000
264 264 summary: commit-foo-dot
265 265
266 266 changeset: 0:65d4e9386227
267 267 user: test
268 268 date: Thu Jan 01 00:00:00 1970 +0000
269 269 summary: commit-foo-subdir
270 270
271 271 $ cd ..
272 272 $ cd ..
273 273
274 274 Issue1049: Hg permits partial commit of merge without warning
275 275
276 276 $ hg init issue1049
277 277 $ cd issue1049
278 278 $ echo a > a
279 279 $ hg ci -Ama
280 280 adding a
281 281 $ echo a >> a
282 282 $ hg ci -mb
283 283 $ hg up 0
284 284 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 285 $ echo b >> a
286 286 $ hg ci -mc
287 287 created new head
288 288 $ HGMERGE=true hg merge
289 289 merging a
290 290 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
291 291 (branch merge, don't forget to commit)
292 292
293 293 should fail because we are specifying a file name
294 294
295 295 $ hg ci -mmerge a
296 296 abort: cannot partially commit a merge (do not specify files or patterns)
297 297 [255]
298 298
299 299 should fail because we are specifying a pattern
300 300
301 301 $ hg ci -mmerge -I a
302 302 abort: cannot partially commit a merge (do not specify files or patterns)
303 303 [255]
304 304
305 305 should succeed
306 306
307 307 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
308 308 HGEDITFORM=commit.normal.merge
309 309 $ cd ..
310 310
311 311
312 312 test commit message content
313 313
314 314 $ hg init commitmsg
315 315 $ cd commitmsg
316 316 $ echo changed > changed
317 317 $ echo removed > removed
318 318 $ hg book activebookmark
319 319 $ hg ci -qAm init
320 320
321 321 $ hg rm removed
322 322 $ echo changed >> changed
323 323 $ echo added > added
324 324 $ hg add added
325 325 $ HGEDITOR=cat hg ci -A
326 326
327 327
328 328 HG: Enter commit message. Lines beginning with 'HG:' are removed.
329 329 HG: Leave message empty to abort commit.
330 330 HG: --
331 331 HG: user: test
332 332 HG: branch 'default'
333 333 HG: bookmark 'activebookmark'
334 334 HG: added added
335 335 HG: changed changed
336 336 HG: removed removed
337 337 abort: empty commit message
338 338 [255]
339 339
340 340 test saving last-message.txt
341 341
342 342 $ hg init sub
343 343 $ echo a > sub/a
344 344 $ hg -R sub add sub/a
345 345 $ cat > sub/.hg/hgrc <<EOF
346 346 > [hooks]
347 347 > precommit.test-saving-last-message = false
348 348 > EOF
349 349
350 350 $ echo 'sub = sub' > .hgsub
351 351 $ hg add .hgsub
352 352
353 353 $ cat > $TESTTMP/editor.sh <<EOF
354 354 > echo "==== before editing:"
355 355 > cat \$1
356 356 > echo "===="
357 357 > echo "test saving last-message.txt" >> \$1
358 358 > EOF
359 359
360 360 $ rm -f .hg/last-message.txt
361 361 $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q
362 362 ==== before editing:
363 363
364 364
365 365 HG: Enter commit message. Lines beginning with 'HG:' are removed.
366 366 HG: Leave message empty to abort commit.
367 367 HG: --
368 368 HG: user: test
369 369 HG: branch 'default'
370 370 HG: bookmark 'activebookmark'
371 371 HG: subrepo sub
372 372 HG: added .hgsub
373 373 HG: added added
374 374 HG: changed .hgsubstate
375 375 HG: changed changed
376 376 HG: removed removed
377 377 ====
378 378 abort: precommit.test-saving-last-message hook exited with status 1 (in subrepo sub)
379 379 [255]
380 380 $ cat .hg/last-message.txt
381 381
382 382
383 383 test saving last-message.txt
384 384
385 385 test that '[committemplate] changeset' definition and commit log
386 386 specific template keywords work well
387 387
388 388 $ cat >> .hg/hgrc <<EOF
389 389 > [committemplate]
390 390 > changeset.commit.normal = 'HG: this is "commit.normal" template
391 391 > HG: {extramsg}
392 392 > {if(activebookmark,
393 393 > "HG: bookmark '{activebookmark}' is activated\n",
394 394 > "HG: no bookmark is activated\n")}{subrepos %
395 395 > "HG: subrepo '{subrepo}' is changed\n"}'
396 396 >
397 397 > changeset.commit = HG: this is "commit" template
398 398 > HG: {extramsg}
399 399 > {if(activebookmark,
400 400 > "HG: bookmark '{activebookmark}' is activated\n",
401 401 > "HG: no bookmark is activated\n")}{subrepos %
402 402 > "HG: subrepo '{subrepo}' is changed\n"}
403 403 >
404 404 > changeset = HG: this is customized commit template
405 405 > HG: {extramsg}
406 406 > {if(activebookmark,
407 407 > "HG: bookmark '{activebookmark}' is activated\n",
408 408 > "HG: no bookmark is activated\n")}{subrepos %
409 409 > "HG: subrepo '{subrepo}' is changed\n"}
410 410 > EOF
411 411
412 412 $ hg init sub2
413 413 $ echo a > sub2/a
414 414 $ hg -R sub2 add sub2/a
415 415 $ echo 'sub2 = sub2' >> .hgsub
416 416
417 417 $ HGEDITOR=cat hg commit -S -q
418 418 HG: this is "commit.normal" template
419 419 HG: Leave message empty to abort commit.
420 420 HG: bookmark 'activebookmark' is activated
421 421 HG: subrepo 'sub' is changed
422 422 HG: subrepo 'sub2' is changed
423 423 abort: empty commit message
424 424 [255]
425 425
426 426 $ cat >> .hg/hgrc <<EOF
427 427 > [committemplate]
428 428 > changeset.commit.normal =
429 429 > # now, "changeset.commit" should be chosen for "hg commit"
430 430 > EOF
431 431
432 432 $ hg bookmark --inactive activebookmark
433 433 $ hg forget .hgsub
434 434 $ HGEDITOR=cat hg commit -q
435 435 HG: this is "commit" template
436 436 HG: Leave message empty to abort commit.
437 437 HG: no bookmark is activated
438 438 abort: empty commit message
439 439 [255]
440 440
441 441 $ cat >> .hg/hgrc <<EOF
442 442 > [committemplate]
443 443 > changeset.commit =
444 444 > # now, "changeset" should be chosen for "hg commit"
445 445 > EOF
446 446
447 447 $ HGEDITOR=cat hg commit -q
448 448 HG: this is customized commit template
449 449 HG: Leave message empty to abort commit.
450 450 HG: no bookmark is activated
451 451 abort: empty commit message
452 452 [255]
453 453
454 454 $ cat >> .hg/hgrc <<EOF
455 455 > [committemplate]
456 456 > changeset = {desc}
457 457 > HG: mods={file_mods}
458 458 > HG: adds={file_adds}
459 459 > HG: dels={file_dels}
460 460 > HG: files={files}
461 461 > HG:
462 462 > {splitlines(diff()) % 'HG: {line}\n'
463 463 > }HG:
464 464 > HG: mods={file_mods}
465 465 > HG: adds={file_adds}
466 466 > HG: dels={file_dels}
467 467 > HG: files={files}\n
468 468 > EOF
469 469 $ hg status -amr
470 470 M changed
471 471 A added
472 472 R removed
473 473 $ HGEDITOR=cat hg commit -q -e -m "foo bar" changed
474 474 foo bar
475 475 HG: mods=changed
476 476 HG: adds=
477 477 HG: dels=
478 478 HG: files=changed
479 479 HG:
480 480 HG: --- a/changed Thu Jan 01 00:00:00 1970 +0000
481 481 HG: +++ b/changed Thu Jan 01 00:00:00 1970 +0000
482 482 HG: @@ -1,1 +1,2 @@
483 483 HG: changed
484 484 HG: +changed
485 485 HG:
486 486 HG: mods=changed
487 487 HG: adds=
488 488 HG: dels=
489 489 HG: files=changed
490 490 $ hg status -amr
491 491 A added
492 492 R removed
493 493 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
494 494 M changed
495 495 A
496 496 R
497 497 $ hg rollback -q
498 498
499 499 $ cat >> .hg/hgrc <<EOF
500 500 > [committemplate]
501 501 > changeset = {desc}
502 502 > HG: mods={file_mods}
503 503 > HG: adds={file_adds}
504 504 > HG: dels={file_dels}
505 505 > HG: files={files}
506 506 > HG:
507 507 > {splitlines(diff("changed")) % 'HG: {line}\n'
508 508 > }HG:
509 509 > HG: mods={file_mods}
510 510 > HG: adds={file_adds}
511 511 > HG: dels={file_dels}
512 512 > HG: files={files}
513 513 > HG:
514 514 > {splitlines(diff("added")) % 'HG: {line}\n'
515 515 > }HG:
516 516 > HG: mods={file_mods}
517 517 > HG: adds={file_adds}
518 518 > HG: dels={file_dels}
519 519 > HG: files={files}
520 520 > HG:
521 521 > {splitlines(diff("removed")) % 'HG: {line}\n'
522 522 > }HG:
523 523 > HG: mods={file_mods}
524 524 > HG: adds={file_adds}
525 525 > HG: dels={file_dels}
526 526 > HG: files={files}\n
527 527 > EOF
528 528 $ HGEDITOR=cat hg commit -q -e -m "foo bar" added removed
529 529 foo bar
530 530 HG: mods=
531 531 HG: adds=added
532 532 HG: dels=removed
533 533 HG: files=added removed
534 534 HG:
535 535 HG:
536 536 HG: mods=
537 537 HG: adds=added
538 538 HG: dels=removed
539 539 HG: files=added removed
540 540 HG:
541 541 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
542 542 HG: +++ b/added Thu Jan 01 00:00:00 1970 +0000
543 543 HG: @@ -0,0 +1,1 @@
544 544 HG: +added
545 545 HG:
546 546 HG: mods=
547 547 HG: adds=added
548 548 HG: dels=removed
549 549 HG: files=added removed
550 550 HG:
551 551 HG: --- a/removed Thu Jan 01 00:00:00 1970 +0000
552 552 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
553 553 HG: @@ -1,1 +0,0 @@
554 554 HG: -removed
555 555 HG:
556 556 HG: mods=
557 557 HG: adds=added
558 558 HG: dels=removed
559 559 HG: files=added removed
560 560 $ hg status -amr
561 561 M changed
562 562 $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n"
563 563 M
564 564 A added
565 565 R removed
566 566 $ hg rollback -q
567 567
568 568 $ cat >> .hg/hgrc <<EOF
569 569 > # disable customizing for subsequent tests
570 570 > [committemplate]
571 571 > changeset =
572 572 > EOF
573 573
574 574 $ cd ..
575 575
576 576
577 577 commit copy
578 578
579 579 $ hg init dir2
580 580 $ cd dir2
581 581 $ echo bleh > bar
582 582 $ hg add bar
583 583 $ hg ci -m 'add bar'
584 584
585 585 $ hg cp bar foo
586 586 $ echo >> bar
587 587 $ hg ci -m 'cp bar foo; change bar'
588 588
589 589 $ hg debugrename foo
590 590 foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9
591 591 $ hg debugindex bar
592 592 rev offset length ..... linkrev nodeid p1 p2 (re)
593 593 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re)
594 594 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re)
595 595
596 596 Test making empty commits
597 597 $ hg commit --config ui.allowemptycommit=True -m "empty commit"
598 598 $ hg log -r . -v --stat
599 599 changeset: 2:d809f3644287
600 600 tag: tip
601 601 user: test
602 602 date: Thu Jan 01 00:00:00 1970 +0000
603 603 description:
604 604 empty commit
605 605
606 606
607 607
608 608 verify pathauditor blocks evil filepaths
609 609 $ cat > evil-commit.py <<EOF
610 610 > from mercurial import ui, hg, context, node
611 611 > notrc = u".h\u200cg".encode('utf-8') + '/hgrc'
612 612 > u = ui.ui.load()
613 613 > r = hg.repository(u, '.')
614 614 > def filectxfn(repo, memctx, path):
615 615 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
616 616 > c = context.memctx(r, [r['tip'].node(), node.nullid],
617 617 > 'evil', [notrc], filectxfn, 0)
618 618 > r.commitctx(c)
619 619 > EOF
620 620 $ $PYTHON evil-commit.py
621 621 #if windows
622 622 $ hg co --clean tip
623 623 abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc)
624 624 [255]
625 625 #else
626 626 $ hg co --clean tip
627 627 abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc)
628 628 [255]
629 629 #endif
630 630
631 631 $ hg rollback -f
632 632 repository tip rolled back to revision 2 (undo commit)
633 633 $ cat > evil-commit.py <<EOF
634 634 > from mercurial import ui, hg, context, node
635 635 > notrc = "HG~1/hgrc"
636 636 > u = ui.ui.load()
637 637 > r = hg.repository(u, '.')
638 638 > def filectxfn(repo, memctx, path):
639 639 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
640 640 > c = context.memctx(r, [r['tip'].node(), node.nullid],
641 641 > 'evil', [notrc], filectxfn, 0)
642 642 > r.commitctx(c)
643 643 > EOF
644 644 $ $PYTHON evil-commit.py
645 645 $ hg co --clean tip
646 646 abort: path contains illegal component: HG~1/hgrc (glob)
647 647 [255]
648 648
649 649 $ hg rollback -f
650 650 repository tip rolled back to revision 2 (undo commit)
651 651 $ cat > evil-commit.py <<EOF
652 652 > from mercurial import ui, hg, context, node
653 653 > notrc = "HG8B6C~2/hgrc"
654 654 > u = ui.ui.load()
655 655 > r = hg.repository(u, '.')
656 656 > def filectxfn(repo, memctx, path):
657 657 > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
658 658 > c = context.memctx(r, [r['tip'].node(), node.nullid],
659 659 > 'evil', [notrc], filectxfn, 0)
660 660 > r.commitctx(c)
661 661 > EOF
662 662 $ $PYTHON evil-commit.py
663 663 $ hg co --clean tip
664 664 abort: path contains illegal component: HG8B6C~2/hgrc (glob)
665 665 [255]
666 666
667 667 # test that an unmodified commit template message aborts
668 668
669 669 $ hg init unmodified_commit_template
670 670 $ cd unmodified_commit_template
671 671 $ echo foo > foo
672 672 $ hg add foo
673 673 $ hg commit -m "foo"
674 674 $ cat >> .hg/hgrc <<EOF
675 675 > [committemplate]
676 676 > changeset.commit = HI THIS IS NOT STRIPPED
677 677 > HG: this is customized commit template
678 678 > HG: {extramsg}
679 679 > {if(activebookmark,
680 680 > "HG: bookmark '{activebookmark}' is activated\n",
681 681 > "HG: no bookmark is activated\n")}{subrepos %
682 682 > "HG: subrepo '{subrepo}' is changed\n"}
683 683 > EOF
684 684 $ cat > $TESTTMP/notouching.sh <<EOF
685 685 > true
686 686 > EOF
687 687 $ echo foo2 > foo2
688 688 $ hg add foo2
689 689 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg commit
690 690 abort: commit message unchanged
691 691 [255]
692 692
693 693 test that text below the --- >8 --- special string is ignored
694 694
695 695 $ cat <<'EOF' > $TESTTMP/lowercaseline.sh
696 696 > cat $1 | sed s/LINE/line/ | tee $1.new
697 697 > mv $1.new $1
698 698 > EOF
699 699
700 700 $ hg init ignore_below_special_string
701 701 $ cd ignore_below_special_string
702 702 $ echo foo > foo
703 703 $ hg add foo
704 704 $ hg commit -m "foo"
705 705 $ cat >> .hg/hgrc <<EOF
706 706 > [committemplate]
707 707 > changeset.commit = first LINE
708 708 > HG: this is customized commit template
709 709 > HG: {extramsg}
710 710 > HG: ------------------------ >8 ------------------------
711 711 > {diff()}
712 712 > EOF
713 713 $ echo foo2 > foo2
714 714 $ hg add foo2
715 715 $ HGEDITOR="sh $TESTTMP/notouching.sh" hg ci
716 716 abort: commit message unchanged
717 717 [255]
718 718 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
719 719 first line
720 720 HG: this is customized commit template
721 721 HG: Leave message empty to abort commit.
722 722 HG: ------------------------ >8 ------------------------
723 723 diff -r e63c23eaa88a foo2
724 724 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
725 725 +++ b/foo2 Thu Jan 01 00:00:00 1970 +0000
726 726 @@ -0,0 +1,1 @@
727 727 +foo2
728 728 $ hg log -T '{desc}\n' -r .
729 729 first line
730 730
731 731 test that the special string --- >8 --- isn't used when not at the beginning of
732 732 a line
733 733
734 734 $ cat >> .hg/hgrc <<EOF
735 735 > [committemplate]
736 736 > changeset.commit = first LINE2
737 737 > another line HG: ------------------------ >8 ------------------------
738 738 > HG: this is customized commit template
739 739 > HG: {extramsg}
740 740 > HG: ------------------------ >8 ------------------------
741 741 > {diff()}
742 742 > EOF
743 743 $ echo foo >> foo
744 744 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
745 745 first line2
746 746 another line HG: ------------------------ >8 ------------------------
747 747 HG: this is customized commit template
748 748 HG: Leave message empty to abort commit.
749 749 HG: ------------------------ >8 ------------------------
750 750 diff -r 3661b22b0702 foo
751 751 --- a/foo Thu Jan 01 00:00:00 1970 +0000
752 752 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
753 753 @@ -1,1 +1,2 @@
754 754 foo
755 755 +foo
756 756 $ hg log -T '{desc}\n' -r .
757 757 first line2
758 758 another line HG: ------------------------ >8 ------------------------
759 759
760 760 also test that this special string isn't accepted when there is some extra text
761 761 at the end
762 762
763 763 $ cat >> .hg/hgrc <<EOF
764 764 > [committemplate]
765 765 > changeset.commit = first LINE3
766 766 > HG: ------------------------ >8 ------------------------foobar
767 767 > second line
768 768 > HG: this is customized commit template
769 769 > HG: {extramsg}
770 770 > HG: ------------------------ >8 ------------------------
771 771 > {diff()}
772 772 > EOF
773 773 $ echo foo >> foo
774 774 $ HGEDITOR="sh $TESTTMP/lowercaseline.sh" hg ci
775 775 first line3
776 776 HG: ------------------------ >8 ------------------------foobar
777 777 second line
778 778 HG: this is customized commit template
779 779 HG: Leave message empty to abort commit.
780 780 HG: ------------------------ >8 ------------------------
781 781 diff -r ce648f5f066f foo
782 782 --- a/foo Thu Jan 01 00:00:00 1970 +0000
783 783 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
784 784 @@ -1,2 +1,3 @@
785 785 foo
786 786 foo
787 787 +foo
788 788 $ hg log -T '{desc}\n' -r .
789 789 first line3
790 790 second line
791 791
792 792 $ cd ..
793 793
@@ -1,3462 +1,3462 b''
1 1 @ (34) head
2 2 |
3 3 | o (33) head
4 4 | |
5 5 o | (32) expand
6 6 |\ \
7 7 | o \ (31) expand
8 8 | |\ \
9 9 | | o \ (30) expand
10 10 | | |\ \
11 11 | | | o | (29) regular commit
12 12 | | | | |
13 13 | | o | | (28) merge zero known
14 14 | | |\ \ \
15 15 o | | | | | (27) collapse
16 16 |/ / / / /
17 17 | | o---+ (26) merge one known; far right
18 18 | | | | |
19 19 +---o | | (25) merge one known; far left
20 20 | | | | |
21 21 | | o | | (24) merge one known; immediate right
22 22 | | |\| |
23 23 | | o | | (23) merge one known; immediate left
24 24 | |/| | |
25 25 +---o---+ (22) merge two known; one far left, one far right
26 26 | | / /
27 27 o | | | (21) expand
28 28 |\ \ \ \
29 29 | o---+-+ (20) merge two known; two far right
30 30 | / / /
31 31 o | | | (19) expand
32 32 |\ \ \ \
33 33 +---+---o (18) merge two known; two far left
34 34 | | | |
35 35 | o | | (17) expand
36 36 | |\ \ \
37 37 | | o---+ (16) merge two known; one immediate right, one near right
38 38 | | |/ /
39 39 o | | | (15) expand
40 40 |\ \ \ \
41 41 | o-----+ (14) merge two known; one immediate right, one far right
42 42 | |/ / /
43 43 o | | | (13) expand
44 44 |\ \ \ \
45 45 +---o | | (12) merge two known; one immediate right, one far left
46 46 | | |/ /
47 47 | o | | (11) expand
48 48 | |\ \ \
49 49 | | o---+ (10) merge two known; one immediate left, one near right
50 50 | |/ / /
51 51 o | | | (9) expand
52 52 |\ \ \ \
53 53 | o-----+ (8) merge two known; one immediate left, one far right
54 54 |/ / / /
55 55 o | | | (7) expand
56 56 |\ \ \ \
57 57 +---o | | (6) merge two known; one immediate left, one far left
58 58 | |/ / /
59 59 | o | | (5) expand
60 60 | |\ \ \
61 61 | | o | | (4) merge two known; one immediate left, one immediate right
62 62 | |/|/ /
63 63 | o / / (3) collapse
64 64 |/ / /
65 65 o / / (2) collapse
66 66 |/ /
67 67 o / (1) collapse
68 68 |/
69 69 o (0) root
70 70
71 71
72 72 $ commit()
73 73 > {
74 74 > rev=$1
75 75 > msg=$2
76 76 > shift 2
77 77 > if [ "$#" -gt 0 ]; then
78 78 > hg debugsetparents "$@"
79 79 > fi
80 80 > echo $rev > a
81 81 > hg commit -Aqd "$rev 0" -m "($rev) $msg"
82 82 > }
83 83
84 84 $ cat > printrevset.py <<EOF
85 85 > from mercurial import extensions, revsetlang, commands, cmdutil
86 86 >
87 87 > def uisetup(ui):
88 88 > def printrevset(orig, ui, repo, *pats, **opts):
89 89 > if opts.get('print_revset'):
90 90 > expr = cmdutil.getgraphlogrevs(repo, pats, opts)[1]
91 91 > if expr:
92 92 > tree = revsetlang.parse(expr)
93 93 > else:
94 94 > tree = []
95 95 > ui.write('%r\n' % (opts.get('rev', []),))
96 96 > ui.write(revsetlang.prettyformat(tree) + '\n')
97 97 > return 0
98 98 > return orig(ui, repo, *pats, **opts)
99 99 > entry = extensions.wrapcommand(commands.table, 'log', printrevset)
100 100 > entry[1].append(('', 'print-revset', False,
101 101 > 'print generated revset and exit (DEPRECATED)'))
102 102 > EOF
103 103
104 104 $ echo "[extensions]" >> $HGRCPATH
105 105 $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
106 106
107 107 $ hg init repo
108 108 $ cd repo
109 109
110 110 Empty repo:
111 111
112 112 $ hg log -G
113 113
114 114
115 115 Building DAG:
116 116
117 117 $ commit 0 "root"
118 118 $ commit 1 "collapse" 0
119 119 $ commit 2 "collapse" 1
120 120 $ commit 3 "collapse" 2
121 121 $ commit 4 "merge two known; one immediate left, one immediate right" 1 3
122 122 $ commit 5 "expand" 3 4
123 123 $ commit 6 "merge two known; one immediate left, one far left" 2 5
124 124 $ commit 7 "expand" 2 5
125 125 $ commit 8 "merge two known; one immediate left, one far right" 0 7
126 126 $ commit 9 "expand" 7 8
127 127 $ commit 10 "merge two known; one immediate left, one near right" 0 6
128 128 $ commit 11 "expand" 6 10
129 129 $ commit 12 "merge two known; one immediate right, one far left" 1 9
130 130 $ commit 13 "expand" 9 11
131 131 $ commit 14 "merge two known; one immediate right, one far right" 0 12
132 132 $ commit 15 "expand" 13 14
133 133 $ commit 16 "merge two known; one immediate right, one near right" 0 1
134 134 $ commit 17 "expand" 12 16
135 135 $ commit 18 "merge two known; two far left" 1 15
136 136 $ commit 19 "expand" 15 17
137 137 $ commit 20 "merge two known; two far right" 0 18
138 138 $ commit 21 "expand" 19 20
139 139 $ commit 22 "merge two known; one far left, one far right" 18 21
140 140 $ commit 23 "merge one known; immediate left" 1 22
141 141 $ commit 24 "merge one known; immediate right" 0 23
142 142 $ commit 25 "merge one known; far left" 21 24
143 143 $ commit 26 "merge one known; far right" 18 25
144 144 $ commit 27 "collapse" 21
145 145 $ commit 28 "merge zero known" 1 26
146 146 $ commit 29 "regular commit" 0
147 147 $ commit 30 "expand" 28 29
148 148 $ commit 31 "expand" 21 30
149 149 $ commit 32 "expand" 27 31
150 150 $ commit 33 "head" 18
151 151 $ commit 34 "head" 32
152 152
153 153
154 154 $ hg log -G -q
155 155 @ 34:fea3ac5810e0
156 156 |
157 157 | o 33:68608f5145f9
158 158 | |
159 159 o | 32:d06dffa21a31
160 160 |\ \
161 161 | o \ 31:621d83e11f67
162 162 | |\ \
163 163 | | o \ 30:6e11cd4b648f
164 164 | | |\ \
165 165 | | | o | 29:cd9bb2be7593
166 166 | | | | |
167 167 | | o | | 28:44ecd0b9ae99
168 168 | | |\ \ \
169 169 o | | | | | 27:886ed638191b
170 170 |/ / / / /
171 171 | | o---+ 26:7f25b6c2f0b9
172 172 | | | | |
173 173 +---o | | 25:91da8ed57247
174 174 | | | | |
175 175 | | o | | 24:a9c19a3d96b7
176 176 | | |\| |
177 177 | | o | | 23:a01cddf0766d
178 178 | |/| | |
179 179 +---o---+ 22:e0d9cccacb5d
180 180 | | / /
181 181 o | | | 21:d42a756af44d
182 182 |\ \ \ \
183 183 | o---+-+ 20:d30ed6450e32
184 184 | / / /
185 185 o | | | 19:31ddc2c1573b
186 186 |\ \ \ \
187 187 +---+---o 18:1aa84d96232a
188 188 | | | |
189 189 | o | | 17:44765d7c06e0
190 190 | |\ \ \
191 191 | | o---+ 16:3677d192927d
192 192 | | |/ /
193 193 o | | | 15:1dda3f72782d
194 194 |\ \ \ \
195 195 | o-----+ 14:8eac370358ef
196 196 | |/ / /
197 197 o | | | 13:22d8966a97e3
198 198 |\ \ \ \
199 199 +---o | | 12:86b91144a6e9
200 200 | | |/ /
201 201 | o | | 11:832d76e6bdf2
202 202 | |\ \ \
203 203 | | o---+ 10:74c64d036d72
204 204 | |/ / /
205 205 o | | | 9:7010c0af0a35
206 206 |\ \ \ \
207 207 | o-----+ 8:7a0b11f71937
208 208 |/ / / /
209 209 o | | | 7:b632bb1b1224
210 210 |\ \ \ \
211 211 +---o | | 6:b105a072e251
212 212 | |/ / /
213 213 | o | | 5:4409d547b708
214 214 | |\ \ \
215 215 | | o | | 4:26a8bac39d9f
216 216 | |/|/ /
217 217 | o / / 3:27eef8ed80b4
218 218 |/ / /
219 219 o / / 2:3d9a33b8d1e1
220 220 |/ /
221 221 o / 1:6db2ef61d156
222 222 |/
223 223 o 0:e6eb3150255d
224 224
225 225
226 226 $ hg log -G
227 227 @ changeset: 34:fea3ac5810e0
228 228 | tag: tip
229 229 | parent: 32:d06dffa21a31
230 230 | user: test
231 231 | date: Thu Jan 01 00:00:34 1970 +0000
232 232 | summary: (34) head
233 233 |
234 234 | o changeset: 33:68608f5145f9
235 235 | | parent: 18:1aa84d96232a
236 236 | | user: test
237 237 | | date: Thu Jan 01 00:00:33 1970 +0000
238 238 | | summary: (33) head
239 239 | |
240 240 o | changeset: 32:d06dffa21a31
241 241 |\ \ parent: 27:886ed638191b
242 242 | | | parent: 31:621d83e11f67
243 243 | | | user: test
244 244 | | | date: Thu Jan 01 00:00:32 1970 +0000
245 245 | | | summary: (32) expand
246 246 | | |
247 247 | o | changeset: 31:621d83e11f67
248 248 | |\ \ parent: 21:d42a756af44d
249 249 | | | | parent: 30:6e11cd4b648f
250 250 | | | | user: test
251 251 | | | | date: Thu Jan 01 00:00:31 1970 +0000
252 252 | | | | summary: (31) expand
253 253 | | | |
254 254 | | o | changeset: 30:6e11cd4b648f
255 255 | | |\ \ parent: 28:44ecd0b9ae99
256 256 | | | | | parent: 29:cd9bb2be7593
257 257 | | | | | user: test
258 258 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
259 259 | | | | | summary: (30) expand
260 260 | | | | |
261 261 | | | o | changeset: 29:cd9bb2be7593
262 262 | | | | | parent: 0:e6eb3150255d
263 263 | | | | | user: test
264 264 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
265 265 | | | | | summary: (29) regular commit
266 266 | | | | |
267 267 | | o | | changeset: 28:44ecd0b9ae99
268 268 | | |\ \ \ parent: 1:6db2ef61d156
269 269 | | | | | | parent: 26:7f25b6c2f0b9
270 270 | | | | | | user: test
271 271 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
272 272 | | | | | | summary: (28) merge zero known
273 273 | | | | | |
274 274 o | | | | | changeset: 27:886ed638191b
275 275 |/ / / / / parent: 21:d42a756af44d
276 276 | | | | | user: test
277 277 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
278 278 | | | | | summary: (27) collapse
279 279 | | | | |
280 280 | | o---+ changeset: 26:7f25b6c2f0b9
281 281 | | | | | parent: 18:1aa84d96232a
282 282 | | | | | parent: 25:91da8ed57247
283 283 | | | | | user: test
284 284 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
285 285 | | | | | summary: (26) merge one known; far right
286 286 | | | | |
287 287 +---o | | changeset: 25:91da8ed57247
288 288 | | | | | parent: 21:d42a756af44d
289 289 | | | | | parent: 24:a9c19a3d96b7
290 290 | | | | | user: test
291 291 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
292 292 | | | | | summary: (25) merge one known; far left
293 293 | | | | |
294 294 | | o | | changeset: 24:a9c19a3d96b7
295 295 | | |\| | parent: 0:e6eb3150255d
296 296 | | | | | parent: 23:a01cddf0766d
297 297 | | | | | user: test
298 298 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
299 299 | | | | | summary: (24) merge one known; immediate right
300 300 | | | | |
301 301 | | o | | changeset: 23:a01cddf0766d
302 302 | |/| | | parent: 1:6db2ef61d156
303 303 | | | | | parent: 22:e0d9cccacb5d
304 304 | | | | | user: test
305 305 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
306 306 | | | | | summary: (23) merge one known; immediate left
307 307 | | | | |
308 308 +---o---+ changeset: 22:e0d9cccacb5d
309 309 | | | | parent: 18:1aa84d96232a
310 310 | | / / parent: 21:d42a756af44d
311 311 | | | | user: test
312 312 | | | | date: Thu Jan 01 00:00:22 1970 +0000
313 313 | | | | summary: (22) merge two known; one far left, one far right
314 314 | | | |
315 315 o | | | changeset: 21:d42a756af44d
316 316 |\ \ \ \ parent: 19:31ddc2c1573b
317 317 | | | | | parent: 20:d30ed6450e32
318 318 | | | | | user: test
319 319 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
320 320 | | | | | summary: (21) expand
321 321 | | | | |
322 322 | o---+-+ changeset: 20:d30ed6450e32
323 323 | | | | parent: 0:e6eb3150255d
324 324 | / / / parent: 18:1aa84d96232a
325 325 | | | | user: test
326 326 | | | | date: Thu Jan 01 00:00:20 1970 +0000
327 327 | | | | summary: (20) merge two known; two far right
328 328 | | | |
329 329 o | | | changeset: 19:31ddc2c1573b
330 330 |\ \ \ \ parent: 15:1dda3f72782d
331 331 | | | | | parent: 17:44765d7c06e0
332 332 | | | | | user: test
333 333 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
334 334 | | | | | summary: (19) expand
335 335 | | | | |
336 336 +---+---o changeset: 18:1aa84d96232a
337 337 | | | | parent: 1:6db2ef61d156
338 338 | | | | parent: 15:1dda3f72782d
339 339 | | | | user: test
340 340 | | | | date: Thu Jan 01 00:00:18 1970 +0000
341 341 | | | | summary: (18) merge two known; two far left
342 342 | | | |
343 343 | o | | changeset: 17:44765d7c06e0
344 344 | |\ \ \ parent: 12:86b91144a6e9
345 345 | | | | | parent: 16:3677d192927d
346 346 | | | | | user: test
347 347 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
348 348 | | | | | summary: (17) expand
349 349 | | | | |
350 350 | | o---+ changeset: 16:3677d192927d
351 351 | | | | | parent: 0:e6eb3150255d
352 352 | | |/ / parent: 1:6db2ef61d156
353 353 | | | | user: test
354 354 | | | | date: Thu Jan 01 00:00:16 1970 +0000
355 355 | | | | summary: (16) merge two known; one immediate right, one near right
356 356 | | | |
357 357 o | | | changeset: 15:1dda3f72782d
358 358 |\ \ \ \ parent: 13:22d8966a97e3
359 359 | | | | | parent: 14:8eac370358ef
360 360 | | | | | user: test
361 361 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
362 362 | | | | | summary: (15) expand
363 363 | | | | |
364 364 | o-----+ changeset: 14:8eac370358ef
365 365 | | | | | parent: 0:e6eb3150255d
366 366 | |/ / / parent: 12:86b91144a6e9
367 367 | | | | user: test
368 368 | | | | date: Thu Jan 01 00:00:14 1970 +0000
369 369 | | | | summary: (14) merge two known; one immediate right, one far right
370 370 | | | |
371 371 o | | | changeset: 13:22d8966a97e3
372 372 |\ \ \ \ parent: 9:7010c0af0a35
373 373 | | | | | parent: 11:832d76e6bdf2
374 374 | | | | | user: test
375 375 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
376 376 | | | | | summary: (13) expand
377 377 | | | | |
378 378 +---o | | changeset: 12:86b91144a6e9
379 379 | | |/ / parent: 1:6db2ef61d156
380 380 | | | | parent: 9:7010c0af0a35
381 381 | | | | user: test
382 382 | | | | date: Thu Jan 01 00:00:12 1970 +0000
383 383 | | | | summary: (12) merge two known; one immediate right, one far left
384 384 | | | |
385 385 | o | | changeset: 11:832d76e6bdf2
386 386 | |\ \ \ parent: 6:b105a072e251
387 387 | | | | | parent: 10:74c64d036d72
388 388 | | | | | user: test
389 389 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
390 390 | | | | | summary: (11) expand
391 391 | | | | |
392 392 | | o---+ changeset: 10:74c64d036d72
393 393 | | | | | parent: 0:e6eb3150255d
394 394 | |/ / / parent: 6:b105a072e251
395 395 | | | | user: test
396 396 | | | | date: Thu Jan 01 00:00:10 1970 +0000
397 397 | | | | summary: (10) merge two known; one immediate left, one near right
398 398 | | | |
399 399 o | | | changeset: 9:7010c0af0a35
400 400 |\ \ \ \ parent: 7:b632bb1b1224
401 401 | | | | | parent: 8:7a0b11f71937
402 402 | | | | | user: test
403 403 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
404 404 | | | | | summary: (9) expand
405 405 | | | | |
406 406 | o-----+ changeset: 8:7a0b11f71937
407 407 | | | | | parent: 0:e6eb3150255d
408 408 |/ / / / parent: 7:b632bb1b1224
409 409 | | | | user: test
410 410 | | | | date: Thu Jan 01 00:00:08 1970 +0000
411 411 | | | | summary: (8) merge two known; one immediate left, one far right
412 412 | | | |
413 413 o | | | changeset: 7:b632bb1b1224
414 414 |\ \ \ \ parent: 2:3d9a33b8d1e1
415 415 | | | | | parent: 5:4409d547b708
416 416 | | | | | user: test
417 417 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
418 418 | | | | | summary: (7) expand
419 419 | | | | |
420 420 +---o | | changeset: 6:b105a072e251
421 421 | |/ / / parent: 2:3d9a33b8d1e1
422 422 | | | | parent: 5:4409d547b708
423 423 | | | | user: test
424 424 | | | | date: Thu Jan 01 00:00:06 1970 +0000
425 425 | | | | summary: (6) merge two known; one immediate left, one far left
426 426 | | | |
427 427 | o | | changeset: 5:4409d547b708
428 428 | |\ \ \ parent: 3:27eef8ed80b4
429 429 | | | | | parent: 4:26a8bac39d9f
430 430 | | | | | user: test
431 431 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
432 432 | | | | | summary: (5) expand
433 433 | | | | |
434 434 | | o | | changeset: 4:26a8bac39d9f
435 435 | |/|/ / parent: 1:6db2ef61d156
436 436 | | | | parent: 3:27eef8ed80b4
437 437 | | | | user: test
438 438 | | | | date: Thu Jan 01 00:00:04 1970 +0000
439 439 | | | | summary: (4) merge two known; one immediate left, one immediate right
440 440 | | | |
441 441 | o | | changeset: 3:27eef8ed80b4
442 442 |/ / / user: test
443 443 | | | date: Thu Jan 01 00:00:03 1970 +0000
444 444 | | | summary: (3) collapse
445 445 | | |
446 446 o | | changeset: 2:3d9a33b8d1e1
447 447 |/ / user: test
448 448 | | date: Thu Jan 01 00:00:02 1970 +0000
449 449 | | summary: (2) collapse
450 450 | |
451 451 o | changeset: 1:6db2ef61d156
452 452 |/ user: test
453 453 | date: Thu Jan 01 00:00:01 1970 +0000
454 454 | summary: (1) collapse
455 455 |
456 456 o changeset: 0:e6eb3150255d
457 457 user: test
458 458 date: Thu Jan 01 00:00:00 1970 +0000
459 459 summary: (0) root
460 460
461 461
462 462 File glog:
463 463 $ hg log -G a
464 464 @ changeset: 34:fea3ac5810e0
465 465 | tag: tip
466 466 | parent: 32:d06dffa21a31
467 467 | user: test
468 468 | date: Thu Jan 01 00:00:34 1970 +0000
469 469 | summary: (34) head
470 470 |
471 471 | o changeset: 33:68608f5145f9
472 472 | | parent: 18:1aa84d96232a
473 473 | | user: test
474 474 | | date: Thu Jan 01 00:00:33 1970 +0000
475 475 | | summary: (33) head
476 476 | |
477 477 o | changeset: 32:d06dffa21a31
478 478 |\ \ parent: 27:886ed638191b
479 479 | | | parent: 31:621d83e11f67
480 480 | | | user: test
481 481 | | | date: Thu Jan 01 00:00:32 1970 +0000
482 482 | | | summary: (32) expand
483 483 | | |
484 484 | o | changeset: 31:621d83e11f67
485 485 | |\ \ parent: 21:d42a756af44d
486 486 | | | | parent: 30:6e11cd4b648f
487 487 | | | | user: test
488 488 | | | | date: Thu Jan 01 00:00:31 1970 +0000
489 489 | | | | summary: (31) expand
490 490 | | | |
491 491 | | o | changeset: 30:6e11cd4b648f
492 492 | | |\ \ parent: 28:44ecd0b9ae99
493 493 | | | | | parent: 29:cd9bb2be7593
494 494 | | | | | user: test
495 495 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
496 496 | | | | | summary: (30) expand
497 497 | | | | |
498 498 | | | o | changeset: 29:cd9bb2be7593
499 499 | | | | | parent: 0:e6eb3150255d
500 500 | | | | | user: test
501 501 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
502 502 | | | | | summary: (29) regular commit
503 503 | | | | |
504 504 | | o | | changeset: 28:44ecd0b9ae99
505 505 | | |\ \ \ parent: 1:6db2ef61d156
506 506 | | | | | | parent: 26:7f25b6c2f0b9
507 507 | | | | | | user: test
508 508 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
509 509 | | | | | | summary: (28) merge zero known
510 510 | | | | | |
511 511 o | | | | | changeset: 27:886ed638191b
512 512 |/ / / / / parent: 21:d42a756af44d
513 513 | | | | | user: test
514 514 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
515 515 | | | | | summary: (27) collapse
516 516 | | | | |
517 517 | | o---+ changeset: 26:7f25b6c2f0b9
518 518 | | | | | parent: 18:1aa84d96232a
519 519 | | | | | parent: 25:91da8ed57247
520 520 | | | | | user: test
521 521 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
522 522 | | | | | summary: (26) merge one known; far right
523 523 | | | | |
524 524 +---o | | changeset: 25:91da8ed57247
525 525 | | | | | parent: 21:d42a756af44d
526 526 | | | | | parent: 24:a9c19a3d96b7
527 527 | | | | | user: test
528 528 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
529 529 | | | | | summary: (25) merge one known; far left
530 530 | | | | |
531 531 | | o | | changeset: 24:a9c19a3d96b7
532 532 | | |\| | parent: 0:e6eb3150255d
533 533 | | | | | parent: 23:a01cddf0766d
534 534 | | | | | user: test
535 535 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
536 536 | | | | | summary: (24) merge one known; immediate right
537 537 | | | | |
538 538 | | o | | changeset: 23:a01cddf0766d
539 539 | |/| | | parent: 1:6db2ef61d156
540 540 | | | | | parent: 22:e0d9cccacb5d
541 541 | | | | | user: test
542 542 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
543 543 | | | | | summary: (23) merge one known; immediate left
544 544 | | | | |
545 545 +---o---+ changeset: 22:e0d9cccacb5d
546 546 | | | | parent: 18:1aa84d96232a
547 547 | | / / parent: 21:d42a756af44d
548 548 | | | | user: test
549 549 | | | | date: Thu Jan 01 00:00:22 1970 +0000
550 550 | | | | summary: (22) merge two known; one far left, one far right
551 551 | | | |
552 552 o | | | changeset: 21:d42a756af44d
553 553 |\ \ \ \ parent: 19:31ddc2c1573b
554 554 | | | | | parent: 20:d30ed6450e32
555 555 | | | | | user: test
556 556 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
557 557 | | | | | summary: (21) expand
558 558 | | | | |
559 559 | o---+-+ changeset: 20:d30ed6450e32
560 560 | | | | parent: 0:e6eb3150255d
561 561 | / / / parent: 18:1aa84d96232a
562 562 | | | | user: test
563 563 | | | | date: Thu Jan 01 00:00:20 1970 +0000
564 564 | | | | summary: (20) merge two known; two far right
565 565 | | | |
566 566 o | | | changeset: 19:31ddc2c1573b
567 567 |\ \ \ \ parent: 15:1dda3f72782d
568 568 | | | | | parent: 17:44765d7c06e0
569 569 | | | | | user: test
570 570 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
571 571 | | | | | summary: (19) expand
572 572 | | | | |
573 573 +---+---o changeset: 18:1aa84d96232a
574 574 | | | | parent: 1:6db2ef61d156
575 575 | | | | parent: 15:1dda3f72782d
576 576 | | | | user: test
577 577 | | | | date: Thu Jan 01 00:00:18 1970 +0000
578 578 | | | | summary: (18) merge two known; two far left
579 579 | | | |
580 580 | o | | changeset: 17:44765d7c06e0
581 581 | |\ \ \ parent: 12:86b91144a6e9
582 582 | | | | | parent: 16:3677d192927d
583 583 | | | | | user: test
584 584 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
585 585 | | | | | summary: (17) expand
586 586 | | | | |
587 587 | | o---+ changeset: 16:3677d192927d
588 588 | | | | | parent: 0:e6eb3150255d
589 589 | | |/ / parent: 1:6db2ef61d156
590 590 | | | | user: test
591 591 | | | | date: Thu Jan 01 00:00:16 1970 +0000
592 592 | | | | summary: (16) merge two known; one immediate right, one near right
593 593 | | | |
594 594 o | | | changeset: 15:1dda3f72782d
595 595 |\ \ \ \ parent: 13:22d8966a97e3
596 596 | | | | | parent: 14:8eac370358ef
597 597 | | | | | user: test
598 598 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
599 599 | | | | | summary: (15) expand
600 600 | | | | |
601 601 | o-----+ changeset: 14:8eac370358ef
602 602 | | | | | parent: 0:e6eb3150255d
603 603 | |/ / / parent: 12:86b91144a6e9
604 604 | | | | user: test
605 605 | | | | date: Thu Jan 01 00:00:14 1970 +0000
606 606 | | | | summary: (14) merge two known; one immediate right, one far right
607 607 | | | |
608 608 o | | | changeset: 13:22d8966a97e3
609 609 |\ \ \ \ parent: 9:7010c0af0a35
610 610 | | | | | parent: 11:832d76e6bdf2
611 611 | | | | | user: test
612 612 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
613 613 | | | | | summary: (13) expand
614 614 | | | | |
615 615 +---o | | changeset: 12:86b91144a6e9
616 616 | | |/ / parent: 1:6db2ef61d156
617 617 | | | | parent: 9:7010c0af0a35
618 618 | | | | user: test
619 619 | | | | date: Thu Jan 01 00:00:12 1970 +0000
620 620 | | | | summary: (12) merge two known; one immediate right, one far left
621 621 | | | |
622 622 | o | | changeset: 11:832d76e6bdf2
623 623 | |\ \ \ parent: 6:b105a072e251
624 624 | | | | | parent: 10:74c64d036d72
625 625 | | | | | user: test
626 626 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
627 627 | | | | | summary: (11) expand
628 628 | | | | |
629 629 | | o---+ changeset: 10:74c64d036d72
630 630 | | | | | parent: 0:e6eb3150255d
631 631 | |/ / / parent: 6:b105a072e251
632 632 | | | | user: test
633 633 | | | | date: Thu Jan 01 00:00:10 1970 +0000
634 634 | | | | summary: (10) merge two known; one immediate left, one near right
635 635 | | | |
636 636 o | | | changeset: 9:7010c0af0a35
637 637 |\ \ \ \ parent: 7:b632bb1b1224
638 638 | | | | | parent: 8:7a0b11f71937
639 639 | | | | | user: test
640 640 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
641 641 | | | | | summary: (9) expand
642 642 | | | | |
643 643 | o-----+ changeset: 8:7a0b11f71937
644 644 | | | | | parent: 0:e6eb3150255d
645 645 |/ / / / parent: 7:b632bb1b1224
646 646 | | | | user: test
647 647 | | | | date: Thu Jan 01 00:00:08 1970 +0000
648 648 | | | | summary: (8) merge two known; one immediate left, one far right
649 649 | | | |
650 650 o | | | changeset: 7:b632bb1b1224
651 651 |\ \ \ \ parent: 2:3d9a33b8d1e1
652 652 | | | | | parent: 5:4409d547b708
653 653 | | | | | user: test
654 654 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
655 655 | | | | | summary: (7) expand
656 656 | | | | |
657 657 +---o | | changeset: 6:b105a072e251
658 658 | |/ / / parent: 2:3d9a33b8d1e1
659 659 | | | | parent: 5:4409d547b708
660 660 | | | | user: test
661 661 | | | | date: Thu Jan 01 00:00:06 1970 +0000
662 662 | | | | summary: (6) merge two known; one immediate left, one far left
663 663 | | | |
664 664 | o | | changeset: 5:4409d547b708
665 665 | |\ \ \ parent: 3:27eef8ed80b4
666 666 | | | | | parent: 4:26a8bac39d9f
667 667 | | | | | user: test
668 668 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
669 669 | | | | | summary: (5) expand
670 670 | | | | |
671 671 | | o | | changeset: 4:26a8bac39d9f
672 672 | |/|/ / parent: 1:6db2ef61d156
673 673 | | | | parent: 3:27eef8ed80b4
674 674 | | | | user: test
675 675 | | | | date: Thu Jan 01 00:00:04 1970 +0000
676 676 | | | | summary: (4) merge two known; one immediate left, one immediate right
677 677 | | | |
678 678 | o | | changeset: 3:27eef8ed80b4
679 679 |/ / / user: test
680 680 | | | date: Thu Jan 01 00:00:03 1970 +0000
681 681 | | | summary: (3) collapse
682 682 | | |
683 683 o | | changeset: 2:3d9a33b8d1e1
684 684 |/ / user: test
685 685 | | date: Thu Jan 01 00:00:02 1970 +0000
686 686 | | summary: (2) collapse
687 687 | |
688 688 o | changeset: 1:6db2ef61d156
689 689 |/ user: test
690 690 | date: Thu Jan 01 00:00:01 1970 +0000
691 691 | summary: (1) collapse
692 692 |
693 693 o changeset: 0:e6eb3150255d
694 694 user: test
695 695 date: Thu Jan 01 00:00:00 1970 +0000
696 696 summary: (0) root
697 697
698 698
699 699 File glog per revset:
700 700
701 701 $ hg log -G -r 'file("a")'
702 702 @ changeset: 34:fea3ac5810e0
703 703 | tag: tip
704 704 | parent: 32:d06dffa21a31
705 705 | user: test
706 706 | date: Thu Jan 01 00:00:34 1970 +0000
707 707 | summary: (34) head
708 708 |
709 709 | o changeset: 33:68608f5145f9
710 710 | | parent: 18:1aa84d96232a
711 711 | | user: test
712 712 | | date: Thu Jan 01 00:00:33 1970 +0000
713 713 | | summary: (33) head
714 714 | |
715 715 o | changeset: 32:d06dffa21a31
716 716 |\ \ parent: 27:886ed638191b
717 717 | | | parent: 31:621d83e11f67
718 718 | | | user: test
719 719 | | | date: Thu Jan 01 00:00:32 1970 +0000
720 720 | | | summary: (32) expand
721 721 | | |
722 722 | o | changeset: 31:621d83e11f67
723 723 | |\ \ parent: 21:d42a756af44d
724 724 | | | | parent: 30:6e11cd4b648f
725 725 | | | | user: test
726 726 | | | | date: Thu Jan 01 00:00:31 1970 +0000
727 727 | | | | summary: (31) expand
728 728 | | | |
729 729 | | o | changeset: 30:6e11cd4b648f
730 730 | | |\ \ parent: 28:44ecd0b9ae99
731 731 | | | | | parent: 29:cd9bb2be7593
732 732 | | | | | user: test
733 733 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
734 734 | | | | | summary: (30) expand
735 735 | | | | |
736 736 | | | o | changeset: 29:cd9bb2be7593
737 737 | | | | | parent: 0:e6eb3150255d
738 738 | | | | | user: test
739 739 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
740 740 | | | | | summary: (29) regular commit
741 741 | | | | |
742 742 | | o | | changeset: 28:44ecd0b9ae99
743 743 | | |\ \ \ parent: 1:6db2ef61d156
744 744 | | | | | | parent: 26:7f25b6c2f0b9
745 745 | | | | | | user: test
746 746 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
747 747 | | | | | | summary: (28) merge zero known
748 748 | | | | | |
749 749 o | | | | | changeset: 27:886ed638191b
750 750 |/ / / / / parent: 21:d42a756af44d
751 751 | | | | | user: test
752 752 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
753 753 | | | | | summary: (27) collapse
754 754 | | | | |
755 755 | | o---+ changeset: 26:7f25b6c2f0b9
756 756 | | | | | parent: 18:1aa84d96232a
757 757 | | | | | parent: 25:91da8ed57247
758 758 | | | | | user: test
759 759 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
760 760 | | | | | summary: (26) merge one known; far right
761 761 | | | | |
762 762 +---o | | changeset: 25:91da8ed57247
763 763 | | | | | parent: 21:d42a756af44d
764 764 | | | | | parent: 24:a9c19a3d96b7
765 765 | | | | | user: test
766 766 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
767 767 | | | | | summary: (25) merge one known; far left
768 768 | | | | |
769 769 | | o | | changeset: 24:a9c19a3d96b7
770 770 | | |\| | parent: 0:e6eb3150255d
771 771 | | | | | parent: 23:a01cddf0766d
772 772 | | | | | user: test
773 773 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
774 774 | | | | | summary: (24) merge one known; immediate right
775 775 | | | | |
776 776 | | o | | changeset: 23:a01cddf0766d
777 777 | |/| | | parent: 1:6db2ef61d156
778 778 | | | | | parent: 22:e0d9cccacb5d
779 779 | | | | | user: test
780 780 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
781 781 | | | | | summary: (23) merge one known; immediate left
782 782 | | | | |
783 783 +---o---+ changeset: 22:e0d9cccacb5d
784 784 | | | | parent: 18:1aa84d96232a
785 785 | | / / parent: 21:d42a756af44d
786 786 | | | | user: test
787 787 | | | | date: Thu Jan 01 00:00:22 1970 +0000
788 788 | | | | summary: (22) merge two known; one far left, one far right
789 789 | | | |
790 790 o | | | changeset: 21:d42a756af44d
791 791 |\ \ \ \ parent: 19:31ddc2c1573b
792 792 | | | | | parent: 20:d30ed6450e32
793 793 | | | | | user: test
794 794 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
795 795 | | | | | summary: (21) expand
796 796 | | | | |
797 797 | o---+-+ changeset: 20:d30ed6450e32
798 798 | | | | parent: 0:e6eb3150255d
799 799 | / / / parent: 18:1aa84d96232a
800 800 | | | | user: test
801 801 | | | | date: Thu Jan 01 00:00:20 1970 +0000
802 802 | | | | summary: (20) merge two known; two far right
803 803 | | | |
804 804 o | | | changeset: 19:31ddc2c1573b
805 805 |\ \ \ \ parent: 15:1dda3f72782d
806 806 | | | | | parent: 17:44765d7c06e0
807 807 | | | | | user: test
808 808 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
809 809 | | | | | summary: (19) expand
810 810 | | | | |
811 811 +---+---o changeset: 18:1aa84d96232a
812 812 | | | | parent: 1:6db2ef61d156
813 813 | | | | parent: 15:1dda3f72782d
814 814 | | | | user: test
815 815 | | | | date: Thu Jan 01 00:00:18 1970 +0000
816 816 | | | | summary: (18) merge two known; two far left
817 817 | | | |
818 818 | o | | changeset: 17:44765d7c06e0
819 819 | |\ \ \ parent: 12:86b91144a6e9
820 820 | | | | | parent: 16:3677d192927d
821 821 | | | | | user: test
822 822 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
823 823 | | | | | summary: (17) expand
824 824 | | | | |
825 825 | | o---+ changeset: 16:3677d192927d
826 826 | | | | | parent: 0:e6eb3150255d
827 827 | | |/ / parent: 1:6db2ef61d156
828 828 | | | | user: test
829 829 | | | | date: Thu Jan 01 00:00:16 1970 +0000
830 830 | | | | summary: (16) merge two known; one immediate right, one near right
831 831 | | | |
832 832 o | | | changeset: 15:1dda3f72782d
833 833 |\ \ \ \ parent: 13:22d8966a97e3
834 834 | | | | | parent: 14:8eac370358ef
835 835 | | | | | user: test
836 836 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
837 837 | | | | | summary: (15) expand
838 838 | | | | |
839 839 | o-----+ changeset: 14:8eac370358ef
840 840 | | | | | parent: 0:e6eb3150255d
841 841 | |/ / / parent: 12:86b91144a6e9
842 842 | | | | user: test
843 843 | | | | date: Thu Jan 01 00:00:14 1970 +0000
844 844 | | | | summary: (14) merge two known; one immediate right, one far right
845 845 | | | |
846 846 o | | | changeset: 13:22d8966a97e3
847 847 |\ \ \ \ parent: 9:7010c0af0a35
848 848 | | | | | parent: 11:832d76e6bdf2
849 849 | | | | | user: test
850 850 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
851 851 | | | | | summary: (13) expand
852 852 | | | | |
853 853 +---o | | changeset: 12:86b91144a6e9
854 854 | | |/ / parent: 1:6db2ef61d156
855 855 | | | | parent: 9:7010c0af0a35
856 856 | | | | user: test
857 857 | | | | date: Thu Jan 01 00:00:12 1970 +0000
858 858 | | | | summary: (12) merge two known; one immediate right, one far left
859 859 | | | |
860 860 | o | | changeset: 11:832d76e6bdf2
861 861 | |\ \ \ parent: 6:b105a072e251
862 862 | | | | | parent: 10:74c64d036d72
863 863 | | | | | user: test
864 864 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
865 865 | | | | | summary: (11) expand
866 866 | | | | |
867 867 | | o---+ changeset: 10:74c64d036d72
868 868 | | | | | parent: 0:e6eb3150255d
869 869 | |/ / / parent: 6:b105a072e251
870 870 | | | | user: test
871 871 | | | | date: Thu Jan 01 00:00:10 1970 +0000
872 872 | | | | summary: (10) merge two known; one immediate left, one near right
873 873 | | | |
874 874 o | | | changeset: 9:7010c0af0a35
875 875 |\ \ \ \ parent: 7:b632bb1b1224
876 876 | | | | | parent: 8:7a0b11f71937
877 877 | | | | | user: test
878 878 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
879 879 | | | | | summary: (9) expand
880 880 | | | | |
881 881 | o-----+ changeset: 8:7a0b11f71937
882 882 | | | | | parent: 0:e6eb3150255d
883 883 |/ / / / parent: 7:b632bb1b1224
884 884 | | | | user: test
885 885 | | | | date: Thu Jan 01 00:00:08 1970 +0000
886 886 | | | | summary: (8) merge two known; one immediate left, one far right
887 887 | | | |
888 888 o | | | changeset: 7:b632bb1b1224
889 889 |\ \ \ \ parent: 2:3d9a33b8d1e1
890 890 | | | | | parent: 5:4409d547b708
891 891 | | | | | user: test
892 892 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
893 893 | | | | | summary: (7) expand
894 894 | | | | |
895 895 +---o | | changeset: 6:b105a072e251
896 896 | |/ / / parent: 2:3d9a33b8d1e1
897 897 | | | | parent: 5:4409d547b708
898 898 | | | | user: test
899 899 | | | | date: Thu Jan 01 00:00:06 1970 +0000
900 900 | | | | summary: (6) merge two known; one immediate left, one far left
901 901 | | | |
902 902 | o | | changeset: 5:4409d547b708
903 903 | |\ \ \ parent: 3:27eef8ed80b4
904 904 | | | | | parent: 4:26a8bac39d9f
905 905 | | | | | user: test
906 906 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
907 907 | | | | | summary: (5) expand
908 908 | | | | |
909 909 | | o | | changeset: 4:26a8bac39d9f
910 910 | |/|/ / parent: 1:6db2ef61d156
911 911 | | | | parent: 3:27eef8ed80b4
912 912 | | | | user: test
913 913 | | | | date: Thu Jan 01 00:00:04 1970 +0000
914 914 | | | | summary: (4) merge two known; one immediate left, one immediate right
915 915 | | | |
916 916 | o | | changeset: 3:27eef8ed80b4
917 917 |/ / / user: test
918 918 | | | date: Thu Jan 01 00:00:03 1970 +0000
919 919 | | | summary: (3) collapse
920 920 | | |
921 921 o | | changeset: 2:3d9a33b8d1e1
922 922 |/ / user: test
923 923 | | date: Thu Jan 01 00:00:02 1970 +0000
924 924 | | summary: (2) collapse
925 925 | |
926 926 o | changeset: 1:6db2ef61d156
927 927 |/ user: test
928 928 | date: Thu Jan 01 00:00:01 1970 +0000
929 929 | summary: (1) collapse
930 930 |
931 931 o changeset: 0:e6eb3150255d
932 932 user: test
933 933 date: Thu Jan 01 00:00:00 1970 +0000
934 934 summary: (0) root
935 935
936 936
937 937
938 938 File glog per revset (only merges):
939 939
940 940 $ hg log -G -r 'file("a")' -m
941 941 o changeset: 32:d06dffa21a31
942 942 |\ parent: 27:886ed638191b
943 943 | : parent: 31:621d83e11f67
944 944 | : user: test
945 945 | : date: Thu Jan 01 00:00:32 1970 +0000
946 946 | : summary: (32) expand
947 947 | :
948 948 o : changeset: 31:621d83e11f67
949 949 |\: parent: 21:d42a756af44d
950 950 | : parent: 30:6e11cd4b648f
951 951 | : user: test
952 952 | : date: Thu Jan 01 00:00:31 1970 +0000
953 953 | : summary: (31) expand
954 954 | :
955 955 o : changeset: 30:6e11cd4b648f
956 956 |\ \ parent: 28:44ecd0b9ae99
957 957 | ~ : parent: 29:cd9bb2be7593
958 958 | : user: test
959 959 | : date: Thu Jan 01 00:00:30 1970 +0000
960 960 | : summary: (30) expand
961 961 | /
962 962 o : changeset: 28:44ecd0b9ae99
963 963 |\ \ parent: 1:6db2ef61d156
964 964 | ~ : parent: 26:7f25b6c2f0b9
965 965 | : user: test
966 966 | : date: Thu Jan 01 00:00:28 1970 +0000
967 967 | : summary: (28) merge zero known
968 968 | /
969 969 o : changeset: 26:7f25b6c2f0b9
970 970 |\ \ parent: 18:1aa84d96232a
971 971 | | : parent: 25:91da8ed57247
972 972 | | : user: test
973 973 | | : date: Thu Jan 01 00:00:26 1970 +0000
974 974 | | : summary: (26) merge one known; far right
975 975 | | :
976 976 | o : changeset: 25:91da8ed57247
977 977 | |\: parent: 21:d42a756af44d
978 978 | | : parent: 24:a9c19a3d96b7
979 979 | | : user: test
980 980 | | : date: Thu Jan 01 00:00:25 1970 +0000
981 981 | | : summary: (25) merge one known; far left
982 982 | | :
983 983 | o : changeset: 24:a9c19a3d96b7
984 984 | |\ \ parent: 0:e6eb3150255d
985 985 | | ~ : parent: 23:a01cddf0766d
986 986 | | : user: test
987 987 | | : date: Thu Jan 01 00:00:24 1970 +0000
988 988 | | : summary: (24) merge one known; immediate right
989 989 | | /
990 990 | o : changeset: 23:a01cddf0766d
991 991 | |\ \ parent: 1:6db2ef61d156
992 992 | | ~ : parent: 22:e0d9cccacb5d
993 993 | | : user: test
994 994 | | : date: Thu Jan 01 00:00:23 1970 +0000
995 995 | | : summary: (23) merge one known; immediate left
996 996 | | /
997 997 | o : changeset: 22:e0d9cccacb5d
998 998 |/:/ parent: 18:1aa84d96232a
999 999 | : parent: 21:d42a756af44d
1000 1000 | : user: test
1001 1001 | : date: Thu Jan 01 00:00:22 1970 +0000
1002 1002 | : summary: (22) merge two known; one far left, one far right
1003 1003 | :
1004 1004 | o changeset: 21:d42a756af44d
1005 1005 | |\ parent: 19:31ddc2c1573b
1006 1006 | | | parent: 20:d30ed6450e32
1007 1007 | | | user: test
1008 1008 | | | date: Thu Jan 01 00:00:21 1970 +0000
1009 1009 | | | summary: (21) expand
1010 1010 | | |
1011 1011 +---o changeset: 20:d30ed6450e32
1012 1012 | | | parent: 0:e6eb3150255d
1013 1013 | | ~ parent: 18:1aa84d96232a
1014 1014 | | user: test
1015 1015 | | date: Thu Jan 01 00:00:20 1970 +0000
1016 1016 | | summary: (20) merge two known; two far right
1017 1017 | |
1018 1018 | o changeset: 19:31ddc2c1573b
1019 1019 | |\ parent: 15:1dda3f72782d
1020 1020 | | | parent: 17:44765d7c06e0
1021 1021 | | | user: test
1022 1022 | | | date: Thu Jan 01 00:00:19 1970 +0000
1023 1023 | | | summary: (19) expand
1024 1024 | | |
1025 1025 o | | changeset: 18:1aa84d96232a
1026 1026 |\| | parent: 1:6db2ef61d156
1027 1027 ~ | | parent: 15:1dda3f72782d
1028 1028 | | user: test
1029 1029 | | date: Thu Jan 01 00:00:18 1970 +0000
1030 1030 | | summary: (18) merge two known; two far left
1031 1031 / /
1032 1032 | o changeset: 17:44765d7c06e0
1033 1033 | |\ parent: 12:86b91144a6e9
1034 1034 | | | parent: 16:3677d192927d
1035 1035 | | | user: test
1036 1036 | | | date: Thu Jan 01 00:00:17 1970 +0000
1037 1037 | | | summary: (17) expand
1038 1038 | | |
1039 1039 | | o changeset: 16:3677d192927d
1040 1040 | | |\ parent: 0:e6eb3150255d
1041 1041 | | ~ ~ parent: 1:6db2ef61d156
1042 1042 | | user: test
1043 1043 | | date: Thu Jan 01 00:00:16 1970 +0000
1044 1044 | | summary: (16) merge two known; one immediate right, one near right
1045 1045 | |
1046 1046 o | changeset: 15:1dda3f72782d
1047 1047 |\ \ parent: 13:22d8966a97e3
1048 1048 | | | parent: 14:8eac370358ef
1049 1049 | | | user: test
1050 1050 | | | date: Thu Jan 01 00:00:15 1970 +0000
1051 1051 | | | summary: (15) expand
1052 1052 | | |
1053 1053 | o | changeset: 14:8eac370358ef
1054 1054 | |\| parent: 0:e6eb3150255d
1055 1055 | ~ | parent: 12:86b91144a6e9
1056 1056 | | user: test
1057 1057 | | date: Thu Jan 01 00:00:14 1970 +0000
1058 1058 | | summary: (14) merge two known; one immediate right, one far right
1059 1059 | /
1060 1060 o | changeset: 13:22d8966a97e3
1061 1061 |\ \ parent: 9:7010c0af0a35
1062 1062 | | | parent: 11:832d76e6bdf2
1063 1063 | | | user: test
1064 1064 | | | date: Thu Jan 01 00:00:13 1970 +0000
1065 1065 | | | summary: (13) expand
1066 1066 | | |
1067 1067 +---o changeset: 12:86b91144a6e9
1068 1068 | | | parent: 1:6db2ef61d156
1069 1069 | | ~ parent: 9:7010c0af0a35
1070 1070 | | user: test
1071 1071 | | date: Thu Jan 01 00:00:12 1970 +0000
1072 1072 | | summary: (12) merge two known; one immediate right, one far left
1073 1073 | |
1074 1074 | o changeset: 11:832d76e6bdf2
1075 1075 | |\ parent: 6:b105a072e251
1076 1076 | | | parent: 10:74c64d036d72
1077 1077 | | | user: test
1078 1078 | | | date: Thu Jan 01 00:00:11 1970 +0000
1079 1079 | | | summary: (11) expand
1080 1080 | | |
1081 1081 | | o changeset: 10:74c64d036d72
1082 1082 | |/| parent: 0:e6eb3150255d
1083 1083 | | ~ parent: 6:b105a072e251
1084 1084 | | user: test
1085 1085 | | date: Thu Jan 01 00:00:10 1970 +0000
1086 1086 | | summary: (10) merge two known; one immediate left, one near right
1087 1087 | |
1088 1088 o | changeset: 9:7010c0af0a35
1089 1089 |\ \ parent: 7:b632bb1b1224
1090 1090 | | | parent: 8:7a0b11f71937
1091 1091 | | | user: test
1092 1092 | | | date: Thu Jan 01 00:00:09 1970 +0000
1093 1093 | | | summary: (9) expand
1094 1094 | | |
1095 1095 | o | changeset: 8:7a0b11f71937
1096 1096 |/| | parent: 0:e6eb3150255d
1097 1097 | ~ | parent: 7:b632bb1b1224
1098 1098 | | user: test
1099 1099 | | date: Thu Jan 01 00:00:08 1970 +0000
1100 1100 | | summary: (8) merge two known; one immediate left, one far right
1101 1101 | /
1102 1102 o | changeset: 7:b632bb1b1224
1103 1103 |\ \ parent: 2:3d9a33b8d1e1
1104 1104 | ~ | parent: 5:4409d547b708
1105 1105 | | user: test
1106 1106 | | date: Thu Jan 01 00:00:07 1970 +0000
1107 1107 | | summary: (7) expand
1108 1108 | /
1109 1109 | o changeset: 6:b105a072e251
1110 1110 |/| parent: 2:3d9a33b8d1e1
1111 1111 | ~ parent: 5:4409d547b708
1112 1112 | user: test
1113 1113 | date: Thu Jan 01 00:00:06 1970 +0000
1114 1114 | summary: (6) merge two known; one immediate left, one far left
1115 1115 |
1116 1116 o changeset: 5:4409d547b708
1117 1117 |\ parent: 3:27eef8ed80b4
1118 1118 | ~ parent: 4:26a8bac39d9f
1119 1119 | user: test
1120 1120 | date: Thu Jan 01 00:00:05 1970 +0000
1121 1121 | summary: (5) expand
1122 1122 |
1123 1123 o changeset: 4:26a8bac39d9f
1124 1124 |\ parent: 1:6db2ef61d156
1125 1125 ~ ~ parent: 3:27eef8ed80b4
1126 1126 user: test
1127 1127 date: Thu Jan 01 00:00:04 1970 +0000
1128 1128 summary: (4) merge two known; one immediate left, one immediate right
1129 1129
1130 1130
1131 1131
1132 1132 Empty revision range - display nothing:
1133 1133 $ hg log -G -r 1..0
1134 1134
1135 1135 $ cd ..
1136 1136
1137 1137 #if no-outer-repo
1138 1138
1139 1139 From outer space:
1140 1140 $ hg log -G -l1 repo
1141 1141 @ changeset: 34:fea3ac5810e0
1142 1142 | tag: tip
1143 1143 ~ parent: 32:d06dffa21a31
1144 1144 user: test
1145 1145 date: Thu Jan 01 00:00:34 1970 +0000
1146 1146 summary: (34) head
1147 1147
1148 1148 $ hg log -G -l1 repo/a
1149 1149 @ changeset: 34:fea3ac5810e0
1150 1150 | tag: tip
1151 1151 ~ parent: 32:d06dffa21a31
1152 1152 user: test
1153 1153 date: Thu Jan 01 00:00:34 1970 +0000
1154 1154 summary: (34) head
1155 1155
1156 1156 $ hg log -G -l1 repo/missing
1157 1157
1158 1158 #endif
1159 1159
1160 1160 File log with revs != cset revs:
1161 1161 $ hg init flog
1162 1162 $ cd flog
1163 1163 $ echo one >one
1164 1164 $ hg add one
1165 1165 $ hg commit -mone
1166 1166 $ echo two >two
1167 1167 $ hg add two
1168 1168 $ hg commit -mtwo
1169 1169 $ echo more >two
1170 1170 $ hg commit -mmore
1171 1171 $ hg log -G two
1172 1172 @ changeset: 2:12c28321755b
1173 1173 | tag: tip
1174 1174 | user: test
1175 1175 | date: Thu Jan 01 00:00:00 1970 +0000
1176 1176 | summary: more
1177 1177 |
1178 1178 o changeset: 1:5ac72c0599bf
1179 1179 | user: test
1180 1180 ~ date: Thu Jan 01 00:00:00 1970 +0000
1181 1181 summary: two
1182 1182
1183 1183
1184 1184 Issue1896: File log with explicit style
1185 1185 $ hg log -G --style=default one
1186 1186 o changeset: 0:3d578b4a1f53
1187 1187 user: test
1188 1188 date: Thu Jan 01 00:00:00 1970 +0000
1189 1189 summary: one
1190 1190
1191 1191 Issue2395: glog --style header and footer
1192 1192 $ hg log -G --style=xml one
1193 1193 <?xml version="1.0"?>
1194 1194 <log>
1195 1195 o <logentry revision="0" node="3d578b4a1f537d5fcf7301bfa9c0b97adfaa6fb1">
1196 1196 <author email="test">test</author>
1197 1197 <date>1970-01-01T00:00:00+00:00</date>
1198 1198 <msg xml:space="preserve">one</msg>
1199 1199 </logentry>
1200 1200 </log>
1201 1201
1202 1202 $ cd ..
1203 1203
1204 1204 Incoming and outgoing:
1205 1205
1206 1206 $ hg clone -U -r31 repo repo2
1207 1207 adding changesets
1208 1208 adding manifests
1209 1209 adding file changes
1210 1210 added 31 changesets with 31 changes to 1 files
1211 1211 $ cd repo2
1212 1212
1213 1213 $ hg incoming --graph ../repo
1214 1214 comparing with ../repo
1215 1215 searching for changes
1216 1216 o changeset: 34:fea3ac5810e0
1217 1217 | tag: tip
1218 1218 | parent: 32:d06dffa21a31
1219 1219 | user: test
1220 1220 | date: Thu Jan 01 00:00:34 1970 +0000
1221 1221 | summary: (34) head
1222 1222 |
1223 1223 | o changeset: 33:68608f5145f9
1224 1224 | parent: 18:1aa84d96232a
1225 1225 | user: test
1226 1226 | date: Thu Jan 01 00:00:33 1970 +0000
1227 1227 | summary: (33) head
1228 1228 |
1229 1229 o changeset: 32:d06dffa21a31
1230 1230 | parent: 27:886ed638191b
1231 1231 | parent: 31:621d83e11f67
1232 1232 | user: test
1233 1233 | date: Thu Jan 01 00:00:32 1970 +0000
1234 1234 | summary: (32) expand
1235 1235 |
1236 1236 o changeset: 27:886ed638191b
1237 1237 parent: 21:d42a756af44d
1238 1238 user: test
1239 1239 date: Thu Jan 01 00:00:27 1970 +0000
1240 1240 summary: (27) collapse
1241 1241
1242 1242 $ cd ..
1243 1243
1244 1244 $ hg -R repo outgoing --graph repo2
1245 1245 comparing with repo2
1246 1246 searching for changes
1247 1247 @ changeset: 34:fea3ac5810e0
1248 1248 | tag: tip
1249 1249 | parent: 32:d06dffa21a31
1250 1250 | user: test
1251 1251 | date: Thu Jan 01 00:00:34 1970 +0000
1252 1252 | summary: (34) head
1253 1253 |
1254 1254 | o changeset: 33:68608f5145f9
1255 1255 | parent: 18:1aa84d96232a
1256 1256 | user: test
1257 1257 | date: Thu Jan 01 00:00:33 1970 +0000
1258 1258 | summary: (33) head
1259 1259 |
1260 1260 o changeset: 32:d06dffa21a31
1261 1261 | parent: 27:886ed638191b
1262 1262 | parent: 31:621d83e11f67
1263 1263 | user: test
1264 1264 | date: Thu Jan 01 00:00:32 1970 +0000
1265 1265 | summary: (32) expand
1266 1266 |
1267 1267 o changeset: 27:886ed638191b
1268 1268 parent: 21:d42a756af44d
1269 1269 user: test
1270 1270 date: Thu Jan 01 00:00:27 1970 +0000
1271 1271 summary: (27) collapse
1272 1272
1273 1273
1274 1274 File + limit with revs != cset revs:
1275 1275 $ cd repo
1276 1276 $ touch b
1277 1277 $ hg ci -Aqm0
1278 1278 $ hg log -G -l2 a
1279 1279 o changeset: 34:fea3ac5810e0
1280 1280 | parent: 32:d06dffa21a31
1281 1281 ~ user: test
1282 1282 date: Thu Jan 01 00:00:34 1970 +0000
1283 1283 summary: (34) head
1284 1284
1285 1285 o changeset: 33:68608f5145f9
1286 1286 | parent: 18:1aa84d96232a
1287 1287 ~ user: test
1288 1288 date: Thu Jan 01 00:00:33 1970 +0000
1289 1289 summary: (33) head
1290 1290
1291 1291
1292 1292 File + limit + -ra:b, (b - a) < limit:
1293 1293 $ hg log -G -l3000 -r32:tip a
1294 1294 o changeset: 34:fea3ac5810e0
1295 1295 | parent: 32:d06dffa21a31
1296 1296 | user: test
1297 1297 | date: Thu Jan 01 00:00:34 1970 +0000
1298 1298 | summary: (34) head
1299 1299 |
1300 1300 | o changeset: 33:68608f5145f9
1301 1301 | | parent: 18:1aa84d96232a
1302 1302 | ~ user: test
1303 1303 | date: Thu Jan 01 00:00:33 1970 +0000
1304 1304 | summary: (33) head
1305 1305 |
1306 1306 o changeset: 32:d06dffa21a31
1307 1307 |\ parent: 27:886ed638191b
1308 1308 ~ ~ parent: 31:621d83e11f67
1309 1309 user: test
1310 1310 date: Thu Jan 01 00:00:32 1970 +0000
1311 1311 summary: (32) expand
1312 1312
1313 1313
1314 1314 Point out a common and an uncommon unshown parent
1315 1315
1316 1316 $ hg log -G -r 'rev(8) or rev(9)'
1317 1317 o changeset: 9:7010c0af0a35
1318 1318 |\ parent: 7:b632bb1b1224
1319 1319 | ~ parent: 8:7a0b11f71937
1320 1320 | user: test
1321 1321 | date: Thu Jan 01 00:00:09 1970 +0000
1322 1322 | summary: (9) expand
1323 1323 |
1324 1324 o changeset: 8:7a0b11f71937
1325 1325 |\ parent: 0:e6eb3150255d
1326 1326 ~ ~ parent: 7:b632bb1b1224
1327 1327 user: test
1328 1328 date: Thu Jan 01 00:00:08 1970 +0000
1329 1329 summary: (8) merge two known; one immediate left, one far right
1330 1330
1331 1331
1332 1332 File + limit + -ra:b, b < tip:
1333 1333
1334 1334 $ hg log -G -l1 -r32:34 a
1335 1335 o changeset: 34:fea3ac5810e0
1336 1336 | parent: 32:d06dffa21a31
1337 1337 ~ user: test
1338 1338 date: Thu Jan 01 00:00:34 1970 +0000
1339 1339 summary: (34) head
1340 1340
1341 1341
1342 1342 file(File) + limit + -ra:b, b < tip:
1343 1343
1344 1344 $ hg log -G -l1 -r32:34 -r 'file("a")'
1345 1345 o changeset: 34:fea3ac5810e0
1346 1346 | parent: 32:d06dffa21a31
1347 1347 ~ user: test
1348 1348 date: Thu Jan 01 00:00:34 1970 +0000
1349 1349 summary: (34) head
1350 1350
1351 1351
1352 1352 limit(file(File) and a::b), b < tip:
1353 1353
1354 1354 $ hg log -G -r 'limit(file("a") and 32::34, 1)'
1355 1355 o changeset: 32:d06dffa21a31
1356 1356 |\ parent: 27:886ed638191b
1357 1357 ~ ~ parent: 31:621d83e11f67
1358 1358 user: test
1359 1359 date: Thu Jan 01 00:00:32 1970 +0000
1360 1360 summary: (32) expand
1361 1361
1362 1362
1363 1363 File + limit + -ra:b, b < tip:
1364 1364
1365 1365 $ hg log -G -r 'limit(file("a") and 34::32, 1)'
1366 1366
1367 1367 File + limit + -ra:b, b < tip, (b - a) < limit:
1368 1368
1369 1369 $ hg log -G -l10 -r33:34 a
1370 1370 o changeset: 34:fea3ac5810e0
1371 1371 | parent: 32:d06dffa21a31
1372 1372 ~ user: test
1373 1373 date: Thu Jan 01 00:00:34 1970 +0000
1374 1374 summary: (34) head
1375 1375
1376 1376 o changeset: 33:68608f5145f9
1377 1377 | parent: 18:1aa84d96232a
1378 1378 ~ user: test
1379 1379 date: Thu Jan 01 00:00:33 1970 +0000
1380 1380 summary: (33) head
1381 1381
1382 1382
1383 1383 Do not crash or produce strange graphs if history is buggy
1384 1384
1385 1385 $ hg branch branch
1386 1386 marked working directory as branch branch
1387 1387 (branches are permanent and global, did you want a bookmark?)
1388 1388 $ commit 36 "buggy merge: identical parents" 35 35
1389 1389 $ hg log -G -l5
1390 1390 @ changeset: 36:08a19a744424
1391 1391 | branch: branch
1392 1392 | tag: tip
1393 1393 | parent: 35:9159c3644c5e
1394 1394 | parent: 35:9159c3644c5e
1395 1395 | user: test
1396 1396 | date: Thu Jan 01 00:00:36 1970 +0000
1397 1397 | summary: (36) buggy merge: identical parents
1398 1398 |
1399 1399 o changeset: 35:9159c3644c5e
1400 1400 | user: test
1401 1401 | date: Thu Jan 01 00:00:00 1970 +0000
1402 1402 | summary: 0
1403 1403 |
1404 1404 o changeset: 34:fea3ac5810e0
1405 1405 | parent: 32:d06dffa21a31
1406 1406 | user: test
1407 1407 | date: Thu Jan 01 00:00:34 1970 +0000
1408 1408 | summary: (34) head
1409 1409 |
1410 1410 | o changeset: 33:68608f5145f9
1411 1411 | | parent: 18:1aa84d96232a
1412 1412 | ~ user: test
1413 1413 | date: Thu Jan 01 00:00:33 1970 +0000
1414 1414 | summary: (33) head
1415 1415 |
1416 1416 o changeset: 32:d06dffa21a31
1417 1417 |\ parent: 27:886ed638191b
1418 1418 ~ ~ parent: 31:621d83e11f67
1419 1419 user: test
1420 1420 date: Thu Jan 01 00:00:32 1970 +0000
1421 1421 summary: (32) expand
1422 1422
1423 1423
1424 1424 Test log -G options
1425 1425
1426 1426 $ testlog() {
1427 1427 > hg log -G --print-revset "$@"
1428 1428 > hg log --template 'nodetag {rev}\n' "$@" | grep nodetag \
1429 1429 > | sed 's/.*nodetag/nodetag/' > log.nodes
1430 1430 > hg log -G --template 'nodetag {rev}\n' "$@" | grep nodetag \
1431 1431 > | sed 's/.*nodetag/nodetag/' > glog.nodes
1432 1432 > (cmp log.nodes glog.nodes || diff -u log.nodes glog.nodes) \
1433 1433 > | grep '^[-+@ ]' || :
1434 1434 > }
1435 1435
1436 1436 glog always reorders nodes which explains the difference with log
1437 1437
1438 1438 $ testlog -r 27 -r 25 -r 21 -r 34 -r 32 -r 31
1439 1439 ['27', '25', '21', '34', '32', '31']
1440 1440 []
1441 1441 --- log.nodes * (glob)
1442 1442 +++ glog.nodes * (glob)
1443 1443 @@ -1,6 +1,6 @@
1444 1444 -nodetag 27
1445 1445 -nodetag 25
1446 1446 -nodetag 21
1447 1447 nodetag 34
1448 1448 nodetag 32
1449 1449 nodetag 31
1450 1450 +nodetag 27
1451 1451 +nodetag 25
1452 1452 +nodetag 21
1453 1453 $ testlog -u test -u not-a-user
1454 1454 []
1455 1455 (group
1456 1456 (group
1457 1457 (or
1458 1458 (list
1459 1459 (func
1460 1460 ('symbol', 'user')
1461 1461 ('string', 'test'))
1462 1462 (func
1463 1463 ('symbol', 'user')
1464 1464 ('string', 'not-a-user'))))))
1465 1465 $ testlog -b not-a-branch
1466 1466 abort: unknown revision 'not-a-branch'!
1467 1467 abort: unknown revision 'not-a-branch'!
1468 1468 abort: unknown revision 'not-a-branch'!
1469 1469 $ testlog -b 35 -b 36 --only-branch branch
1470 1470 []
1471 1471 (group
1472 1472 (group
1473 1473 (or
1474 1474 (list
1475 1475 (func
1476 1476 ('symbol', 'branch')
1477 1477 ('string', 'default'))
1478 1478 (func
1479 1479 ('symbol', 'branch')
1480 1480 ('string', 'branch'))
1481 1481 (func
1482 1482 ('symbol', 'branch')
1483 1483 ('string', 'branch'))))))
1484 1484 $ testlog -k expand -k merge
1485 1485 []
1486 1486 (group
1487 1487 (group
1488 1488 (or
1489 1489 (list
1490 1490 (func
1491 1491 ('symbol', 'keyword')
1492 1492 ('string', 'expand'))
1493 1493 (func
1494 1494 ('symbol', 'keyword')
1495 1495 ('string', 'merge'))))))
1496 1496 $ testlog --only-merges
1497 1497 []
1498 1498 (group
1499 1499 (func
1500 1500 ('symbol', 'merge')
1501 1501 None))
1502 1502 $ testlog --no-merges
1503 1503 []
1504 1504 (group
1505 1505 (not
1506 1506 (func
1507 1507 ('symbol', 'merge')
1508 1508 None)))
1509 1509 $ testlog --date '2 0 to 4 0'
1510 1510 []
1511 1511 (group
1512 1512 (func
1513 1513 ('symbol', 'date')
1514 1514 ('string', '2 0 to 4 0')))
1515 1515 $ hg log -G -d 'brace ) in a date'
1516 abort: invalid date: 'brace ) in a date'
1516 hg: parse error: invalid date: 'brace ) in a date'
1517 1517 [255]
1518 1518 $ testlog --prune 31 --prune 32
1519 1519 []
1520 1520 (group
1521 1521 (group
1522 1522 (and
1523 1523 (not
1524 1524 (group
1525 1525 (or
1526 1526 (list
1527 1527 ('string', '31')
1528 1528 (func
1529 1529 ('symbol', 'ancestors')
1530 1530 ('string', '31'))))))
1531 1531 (not
1532 1532 (group
1533 1533 (or
1534 1534 (list
1535 1535 ('string', '32')
1536 1536 (func
1537 1537 ('symbol', 'ancestors')
1538 1538 ('string', '32')))))))))
1539 1539
1540 1540 Dedicated repo for --follow and paths filtering. The g is crafted to
1541 1541 have 2 filelog topological heads in a linear changeset graph.
1542 1542
1543 1543 $ cd ..
1544 1544 $ hg init follow
1545 1545 $ cd follow
1546 1546 $ testlog --follow
1547 1547 []
1548 1548 []
1549 1549 $ testlog -rnull
1550 1550 ['null']
1551 1551 []
1552 1552 $ echo a > a
1553 1553 $ echo aa > aa
1554 1554 $ echo f > f
1555 1555 $ hg ci -Am "add a" a aa f
1556 1556 $ hg cp a b
1557 1557 $ hg cp f g
1558 1558 $ hg ci -m "copy a b"
1559 1559 $ mkdir dir
1560 1560 $ hg mv b dir
1561 1561 $ echo g >> g
1562 1562 $ echo f >> f
1563 1563 $ hg ci -m "mv b dir/b"
1564 1564 $ hg mv a b
1565 1565 $ hg cp -f f g
1566 1566 $ echo a > d
1567 1567 $ hg add d
1568 1568 $ hg ci -m "mv a b; add d"
1569 1569 $ hg mv dir/b e
1570 1570 $ hg ci -m "mv dir/b e"
1571 1571 $ hg log -G --template '({rev}) {desc|firstline}\n'
1572 1572 @ (4) mv dir/b e
1573 1573 |
1574 1574 o (3) mv a b; add d
1575 1575 |
1576 1576 o (2) mv b dir/b
1577 1577 |
1578 1578 o (1) copy a b
1579 1579 |
1580 1580 o (0) add a
1581 1581
1582 1582
1583 1583 $ testlog a
1584 1584 []
1585 1585 (group
1586 1586 (group
1587 1587 (func
1588 1588 ('symbol', 'filelog')
1589 1589 ('string', 'a'))))
1590 1590 $ testlog a b
1591 1591 []
1592 1592 (group
1593 1593 (group
1594 1594 (or
1595 1595 (list
1596 1596 (func
1597 1597 ('symbol', 'filelog')
1598 1598 ('string', 'a'))
1599 1599 (func
1600 1600 ('symbol', 'filelog')
1601 1601 ('string', 'b'))))))
1602 1602
1603 1603 Test falling back to slow path for non-existing files
1604 1604
1605 1605 $ testlog a c
1606 1606 []
1607 1607 (group
1608 1608 (func
1609 1609 ('symbol', '_matchfiles')
1610 1610 (list
1611 1611 ('string', 'r:')
1612 1612 ('string', 'd:relpath')
1613 1613 ('string', 'p:a')
1614 1614 ('string', 'p:c'))))
1615 1615
1616 1616 Test multiple --include/--exclude/paths
1617 1617
1618 1618 $ testlog --include a --include e --exclude b --exclude e a e
1619 1619 []
1620 1620 (group
1621 1621 (func
1622 1622 ('symbol', '_matchfiles')
1623 1623 (list
1624 1624 ('string', 'r:')
1625 1625 ('string', 'd:relpath')
1626 1626 ('string', 'p:a')
1627 1627 ('string', 'p:e')
1628 1628 ('string', 'i:a')
1629 1629 ('string', 'i:e')
1630 1630 ('string', 'x:b')
1631 1631 ('string', 'x:e'))))
1632 1632
1633 1633 Test glob expansion of pats
1634 1634
1635 1635 $ expandglobs=`$PYTHON -c "import mercurial.util; \
1636 1636 > print mercurial.util.expandglobs and 'true' or 'false'"`
1637 1637 $ if [ $expandglobs = "true" ]; then
1638 1638 > testlog 'a*';
1639 1639 > else
1640 1640 > testlog a*;
1641 1641 > fi;
1642 1642 []
1643 1643 (group
1644 1644 (group
1645 1645 (func
1646 1646 ('symbol', 'filelog')
1647 1647 ('string', 'aa'))))
1648 1648
1649 1649 Test --follow on a non-existent directory
1650 1650
1651 1651 $ testlog -f dir
1652 1652 abort: cannot follow file not in parent revision: "dir"
1653 1653 abort: cannot follow file not in parent revision: "dir"
1654 1654 abort: cannot follow file not in parent revision: "dir"
1655 1655
1656 1656 Test --follow on a directory
1657 1657
1658 1658 $ hg up -q '.^'
1659 1659 $ testlog -f dir
1660 1660 []
1661 1661 (group
1662 1662 (and
1663 1663 (func
1664 1664 ('symbol', 'ancestors')
1665 1665 ('symbol', '.'))
1666 1666 (func
1667 1667 ('symbol', '_matchfiles')
1668 1668 (list
1669 1669 ('string', 'r:')
1670 1670 ('string', 'd:relpath')
1671 1671 ('string', 'p:dir')))))
1672 1672 $ hg up -q tip
1673 1673
1674 1674 Test --follow on file not in parent revision
1675 1675
1676 1676 $ testlog -f a
1677 1677 abort: cannot follow file not in parent revision: "a"
1678 1678 abort: cannot follow file not in parent revision: "a"
1679 1679 abort: cannot follow file not in parent revision: "a"
1680 1680
1681 1681 Test --follow and patterns
1682 1682
1683 1683 $ testlog -f 'glob:*'
1684 1684 []
1685 1685 (group
1686 1686 (and
1687 1687 (func
1688 1688 ('symbol', 'ancestors')
1689 1689 ('symbol', '.'))
1690 1690 (func
1691 1691 ('symbol', '_matchfiles')
1692 1692 (list
1693 1693 ('string', 'r:')
1694 1694 ('string', 'd:relpath')
1695 1695 ('string', 'p:glob:*')))))
1696 1696
1697 1697 Test --follow on a single rename
1698 1698
1699 1699 $ hg up -q 2
1700 1700 $ testlog -f a
1701 1701 []
1702 1702 (group
1703 1703 (group
1704 1704 (func
1705 1705 ('symbol', 'follow')
1706 1706 ('string', 'a'))))
1707 1707
1708 1708 Test --follow and multiple renames
1709 1709
1710 1710 $ hg up -q tip
1711 1711 $ testlog -f e
1712 1712 []
1713 1713 (group
1714 1714 (group
1715 1715 (func
1716 1716 ('symbol', 'follow')
1717 1717 ('string', 'e'))))
1718 1718
1719 1719 Test --follow and multiple filelog heads
1720 1720
1721 1721 $ hg up -q 2
1722 1722 $ testlog -f g
1723 1723 []
1724 1724 (group
1725 1725 (group
1726 1726 (func
1727 1727 ('symbol', 'follow')
1728 1728 ('string', 'g'))))
1729 1729 $ cat log.nodes
1730 1730 nodetag 2
1731 1731 nodetag 1
1732 1732 nodetag 0
1733 1733 $ hg up -q tip
1734 1734 $ testlog -f g
1735 1735 []
1736 1736 (group
1737 1737 (group
1738 1738 (func
1739 1739 ('symbol', 'follow')
1740 1740 ('string', 'g'))))
1741 1741 $ cat log.nodes
1742 1742 nodetag 3
1743 1743 nodetag 2
1744 1744 nodetag 0
1745 1745
1746 1746 Test --follow and multiple files
1747 1747
1748 1748 $ testlog -f g e
1749 1749 []
1750 1750 (group
1751 1751 (group
1752 1752 (or
1753 1753 (list
1754 1754 (func
1755 1755 ('symbol', 'follow')
1756 1756 ('string', 'g'))
1757 1757 (func
1758 1758 ('symbol', 'follow')
1759 1759 ('string', 'e'))))))
1760 1760 $ cat log.nodes
1761 1761 nodetag 4
1762 1762 nodetag 3
1763 1763 nodetag 2
1764 1764 nodetag 1
1765 1765 nodetag 0
1766 1766
1767 1767 Test --follow null parent
1768 1768
1769 1769 $ hg up -q null
1770 1770 $ testlog -f
1771 1771 []
1772 1772 []
1773 1773
1774 1774 Test --follow-first
1775 1775
1776 1776 $ hg up -q 3
1777 1777 $ echo ee > e
1778 1778 $ hg ci -Am "add another e" e
1779 1779 created new head
1780 1780 $ hg merge --tool internal:other 4
1781 1781 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
1782 1782 (branch merge, don't forget to commit)
1783 1783 $ echo merge > e
1784 1784 $ hg ci -m "merge 5 and 4"
1785 1785 $ testlog --follow-first
1786 1786 []
1787 1787 (group
1788 1788 (func
1789 1789 ('symbol', '_firstancestors')
1790 1790 (func
1791 1791 ('symbol', 'rev')
1792 1792 ('symbol', '6'))))
1793 1793
1794 1794 Cannot compare with log --follow-first FILE as it never worked
1795 1795
1796 1796 $ hg log -G --print-revset --follow-first e
1797 1797 []
1798 1798 (group
1799 1799 (group
1800 1800 (func
1801 1801 ('symbol', '_followfirst')
1802 1802 ('string', 'e'))))
1803 1803 $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
1804 1804 @ 6 merge 5 and 4
1805 1805 |\
1806 1806 | ~
1807 1807 o 5 add another e
1808 1808 |
1809 1809 ~
1810 1810
1811 1811 Test --copies
1812 1812
1813 1813 $ hg log -G --copies --template "{rev} {desc|firstline} \
1814 1814 > copies: {file_copies_switch}\n"
1815 1815 @ 6 merge 5 and 4 copies:
1816 1816 |\
1817 1817 | o 5 add another e copies:
1818 1818 | |
1819 1819 o | 4 mv dir/b e copies: e (dir/b)
1820 1820 |/
1821 1821 o 3 mv a b; add d copies: b (a)g (f)
1822 1822 |
1823 1823 o 2 mv b dir/b copies: dir/b (b)
1824 1824 |
1825 1825 o 1 copy a b copies: b (a)g (f)
1826 1826 |
1827 1827 o 0 add a copies:
1828 1828
1829 1829 Test "set:..." and parent revision
1830 1830
1831 1831 $ hg up -q 4
1832 1832 $ testlog "set:copied()"
1833 1833 []
1834 1834 (group
1835 1835 (func
1836 1836 ('symbol', '_matchfiles')
1837 1837 (list
1838 1838 ('string', 'r:')
1839 1839 ('string', 'd:relpath')
1840 1840 ('string', 'p:set:copied()'))))
1841 1841 $ testlog --include "set:copied()"
1842 1842 []
1843 1843 (group
1844 1844 (func
1845 1845 ('symbol', '_matchfiles')
1846 1846 (list
1847 1847 ('string', 'r:')
1848 1848 ('string', 'd:relpath')
1849 1849 ('string', 'i:set:copied()'))))
1850 1850 $ testlog -r "sort(file('set:copied()'), -rev)"
1851 1851 ["sort(file('set:copied()'), -rev)"]
1852 1852 []
1853 1853
1854 1854 Test --removed
1855 1855
1856 1856 $ testlog --removed
1857 1857 []
1858 1858 []
1859 1859 $ testlog --removed a
1860 1860 []
1861 1861 (group
1862 1862 (func
1863 1863 ('symbol', '_matchfiles')
1864 1864 (list
1865 1865 ('string', 'r:')
1866 1866 ('string', 'd:relpath')
1867 1867 ('string', 'p:a'))))
1868 1868 $ testlog --removed --follow a
1869 1869 []
1870 1870 (group
1871 1871 (and
1872 1872 (func
1873 1873 ('symbol', 'ancestors')
1874 1874 ('symbol', '.'))
1875 1875 (func
1876 1876 ('symbol', '_matchfiles')
1877 1877 (list
1878 1878 ('string', 'r:')
1879 1879 ('string', 'd:relpath')
1880 1880 ('string', 'p:a')))))
1881 1881
1882 1882 Test --patch and --stat with --follow and --follow-first
1883 1883
1884 1884 $ hg up -q 3
1885 1885 $ hg log -G --git --patch b
1886 1886 o changeset: 1:216d4c92cf98
1887 1887 | user: test
1888 1888 ~ date: Thu Jan 01 00:00:00 1970 +0000
1889 1889 summary: copy a b
1890 1890
1891 1891 diff --git a/a b/b
1892 1892 copy from a
1893 1893 copy to b
1894 1894
1895 1895
1896 1896 $ hg log -G --git --stat b
1897 1897 o changeset: 1:216d4c92cf98
1898 1898 | user: test
1899 1899 ~ date: Thu Jan 01 00:00:00 1970 +0000
1900 1900 summary: copy a b
1901 1901
1902 1902 b | 0
1903 1903 1 files changed, 0 insertions(+), 0 deletions(-)
1904 1904
1905 1905
1906 1906 $ hg log -G --git --patch --follow b
1907 1907 o changeset: 1:216d4c92cf98
1908 1908 | user: test
1909 1909 | date: Thu Jan 01 00:00:00 1970 +0000
1910 1910 | summary: copy a b
1911 1911 |
1912 1912 | diff --git a/a b/b
1913 1913 | copy from a
1914 1914 | copy to b
1915 1915 |
1916 1916 o changeset: 0:f8035bb17114
1917 1917 user: test
1918 1918 date: Thu Jan 01 00:00:00 1970 +0000
1919 1919 summary: add a
1920 1920
1921 1921 diff --git a/a b/a
1922 1922 new file mode 100644
1923 1923 --- /dev/null
1924 1924 +++ b/a
1925 1925 @@ -0,0 +1,1 @@
1926 1926 +a
1927 1927
1928 1928
1929 1929 $ hg log -G --git --stat --follow b
1930 1930 o changeset: 1:216d4c92cf98
1931 1931 | user: test
1932 1932 | date: Thu Jan 01 00:00:00 1970 +0000
1933 1933 | summary: copy a b
1934 1934 |
1935 1935 | b | 0
1936 1936 | 1 files changed, 0 insertions(+), 0 deletions(-)
1937 1937 |
1938 1938 o changeset: 0:f8035bb17114
1939 1939 user: test
1940 1940 date: Thu Jan 01 00:00:00 1970 +0000
1941 1941 summary: add a
1942 1942
1943 1943 a | 1 +
1944 1944 1 files changed, 1 insertions(+), 0 deletions(-)
1945 1945
1946 1946
1947 1947 $ hg up -q 6
1948 1948 $ hg log -G --git --patch --follow-first e
1949 1949 @ changeset: 6:fc281d8ff18d
1950 1950 |\ tag: tip
1951 1951 | ~ parent: 5:99b31f1c2782
1952 1952 | parent: 4:17d952250a9d
1953 1953 | user: test
1954 1954 | date: Thu Jan 01 00:00:00 1970 +0000
1955 1955 | summary: merge 5 and 4
1956 1956 |
1957 1957 | diff --git a/e b/e
1958 1958 | --- a/e
1959 1959 | +++ b/e
1960 1960 | @@ -1,1 +1,1 @@
1961 1961 | -ee
1962 1962 | +merge
1963 1963 |
1964 1964 o changeset: 5:99b31f1c2782
1965 1965 | parent: 3:5918b8d165d1
1966 1966 ~ user: test
1967 1967 date: Thu Jan 01 00:00:00 1970 +0000
1968 1968 summary: add another e
1969 1969
1970 1970 diff --git a/e b/e
1971 1971 new file mode 100644
1972 1972 --- /dev/null
1973 1973 +++ b/e
1974 1974 @@ -0,0 +1,1 @@
1975 1975 +ee
1976 1976
1977 1977
1978 1978 Test old-style --rev
1979 1979
1980 1980 $ hg tag 'foo-bar'
1981 1981 $ testlog -r 'foo-bar'
1982 1982 ['foo-bar']
1983 1983 []
1984 1984
1985 1985 Test --follow and forward --rev
1986 1986
1987 1987 $ hg up -q 6
1988 1988 $ echo g > g
1989 1989 $ hg ci -Am 'add g' g
1990 1990 created new head
1991 1991 $ hg up -q 2
1992 1992 $ hg log -G --template "{rev} {desc|firstline}\n"
1993 1993 o 8 add g
1994 1994 |
1995 1995 | o 7 Added tag foo-bar for changeset fc281d8ff18d
1996 1996 |/
1997 1997 o 6 merge 5 and 4
1998 1998 |\
1999 1999 | o 5 add another e
2000 2000 | |
2001 2001 o | 4 mv dir/b e
2002 2002 |/
2003 2003 o 3 mv a b; add d
2004 2004 |
2005 2005 @ 2 mv b dir/b
2006 2006 |
2007 2007 o 1 copy a b
2008 2008 |
2009 2009 o 0 add a
2010 2010
2011 2011 $ hg archive -r 7 archive
2012 2012 $ grep changessincelatesttag archive/.hg_archival.txt
2013 2013 changessincelatesttag: 1
2014 2014 $ rm -r archive
2015 2015
2016 2016 changessincelatesttag with no prior tag
2017 2017 $ hg archive -r 4 archive
2018 2018 $ grep changessincelatesttag archive/.hg_archival.txt
2019 2019 changessincelatesttag: 5
2020 2020
2021 2021 $ hg export 'all()'
2022 2022 # HG changeset patch
2023 2023 # User test
2024 2024 # Date 0 0
2025 2025 # Thu Jan 01 00:00:00 1970 +0000
2026 2026 # Node ID f8035bb17114da16215af3436ec5222428ace8ee
2027 2027 # Parent 0000000000000000000000000000000000000000
2028 2028 add a
2029 2029
2030 2030 diff -r 000000000000 -r f8035bb17114 a
2031 2031 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2032 2032 +++ b/a Thu Jan 01 00:00:00 1970 +0000
2033 2033 @@ -0,0 +1,1 @@
2034 2034 +a
2035 2035 diff -r 000000000000 -r f8035bb17114 aa
2036 2036 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2037 2037 +++ b/aa Thu Jan 01 00:00:00 1970 +0000
2038 2038 @@ -0,0 +1,1 @@
2039 2039 +aa
2040 2040 diff -r 000000000000 -r f8035bb17114 f
2041 2041 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2042 2042 +++ b/f Thu Jan 01 00:00:00 1970 +0000
2043 2043 @@ -0,0 +1,1 @@
2044 2044 +f
2045 2045 # HG changeset patch
2046 2046 # User test
2047 2047 # Date 0 0
2048 2048 # Thu Jan 01 00:00:00 1970 +0000
2049 2049 # Node ID 216d4c92cf98ff2b4641d508b76b529f3d424c92
2050 2050 # Parent f8035bb17114da16215af3436ec5222428ace8ee
2051 2051 copy a b
2052 2052
2053 2053 diff -r f8035bb17114 -r 216d4c92cf98 b
2054 2054 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2055 2055 +++ b/b Thu Jan 01 00:00:00 1970 +0000
2056 2056 @@ -0,0 +1,1 @@
2057 2057 +a
2058 2058 diff -r f8035bb17114 -r 216d4c92cf98 g
2059 2059 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2060 2060 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2061 2061 @@ -0,0 +1,1 @@
2062 2062 +f
2063 2063 # HG changeset patch
2064 2064 # User test
2065 2065 # Date 0 0
2066 2066 # Thu Jan 01 00:00:00 1970 +0000
2067 2067 # Node ID bb573313a9e8349099b6ea2b2fb1fc7f424446f3
2068 2068 # Parent 216d4c92cf98ff2b4641d508b76b529f3d424c92
2069 2069 mv b dir/b
2070 2070
2071 2071 diff -r 216d4c92cf98 -r bb573313a9e8 b
2072 2072 --- a/b Thu Jan 01 00:00:00 1970 +0000
2073 2073 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2074 2074 @@ -1,1 +0,0 @@
2075 2075 -a
2076 2076 diff -r 216d4c92cf98 -r bb573313a9e8 dir/b
2077 2077 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2078 2078 +++ b/dir/b Thu Jan 01 00:00:00 1970 +0000
2079 2079 @@ -0,0 +1,1 @@
2080 2080 +a
2081 2081 diff -r 216d4c92cf98 -r bb573313a9e8 f
2082 2082 --- a/f Thu Jan 01 00:00:00 1970 +0000
2083 2083 +++ b/f Thu Jan 01 00:00:00 1970 +0000
2084 2084 @@ -1,1 +1,2 @@
2085 2085 f
2086 2086 +f
2087 2087 diff -r 216d4c92cf98 -r bb573313a9e8 g
2088 2088 --- a/g Thu Jan 01 00:00:00 1970 +0000
2089 2089 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2090 2090 @@ -1,1 +1,2 @@
2091 2091 f
2092 2092 +g
2093 2093 # HG changeset patch
2094 2094 # User test
2095 2095 # Date 0 0
2096 2096 # Thu Jan 01 00:00:00 1970 +0000
2097 2097 # Node ID 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2098 2098 # Parent bb573313a9e8349099b6ea2b2fb1fc7f424446f3
2099 2099 mv a b; add d
2100 2100
2101 2101 diff -r bb573313a9e8 -r 5918b8d165d1 a
2102 2102 --- a/a Thu Jan 01 00:00:00 1970 +0000
2103 2103 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2104 2104 @@ -1,1 +0,0 @@
2105 2105 -a
2106 2106 diff -r bb573313a9e8 -r 5918b8d165d1 b
2107 2107 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2108 2108 +++ b/b Thu Jan 01 00:00:00 1970 +0000
2109 2109 @@ -0,0 +1,1 @@
2110 2110 +a
2111 2111 diff -r bb573313a9e8 -r 5918b8d165d1 d
2112 2112 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2113 2113 +++ b/d Thu Jan 01 00:00:00 1970 +0000
2114 2114 @@ -0,0 +1,1 @@
2115 2115 +a
2116 2116 diff -r bb573313a9e8 -r 5918b8d165d1 g
2117 2117 --- a/g Thu Jan 01 00:00:00 1970 +0000
2118 2118 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2119 2119 @@ -1,2 +1,2 @@
2120 2120 f
2121 2121 -g
2122 2122 +f
2123 2123 # HG changeset patch
2124 2124 # User test
2125 2125 # Date 0 0
2126 2126 # Thu Jan 01 00:00:00 1970 +0000
2127 2127 # Node ID 17d952250a9d03cc3dc77b199ab60e959b9b0260
2128 2128 # Parent 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2129 2129 mv dir/b e
2130 2130
2131 2131 diff -r 5918b8d165d1 -r 17d952250a9d dir/b
2132 2132 --- a/dir/b Thu Jan 01 00:00:00 1970 +0000
2133 2133 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2134 2134 @@ -1,1 +0,0 @@
2135 2135 -a
2136 2136 diff -r 5918b8d165d1 -r 17d952250a9d e
2137 2137 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2138 2138 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2139 2139 @@ -0,0 +1,1 @@
2140 2140 +a
2141 2141 # HG changeset patch
2142 2142 # User test
2143 2143 # Date 0 0
2144 2144 # Thu Jan 01 00:00:00 1970 +0000
2145 2145 # Node ID 99b31f1c2782e2deb1723cef08930f70fc84b37b
2146 2146 # Parent 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2147 2147 add another e
2148 2148
2149 2149 diff -r 5918b8d165d1 -r 99b31f1c2782 e
2150 2150 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2151 2151 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2152 2152 @@ -0,0 +1,1 @@
2153 2153 +ee
2154 2154 # HG changeset patch
2155 2155 # User test
2156 2156 # Date 0 0
2157 2157 # Thu Jan 01 00:00:00 1970 +0000
2158 2158 # Node ID fc281d8ff18d999ad6497b3d27390bcd695dcc73
2159 2159 # Parent 99b31f1c2782e2deb1723cef08930f70fc84b37b
2160 2160 # Parent 17d952250a9d03cc3dc77b199ab60e959b9b0260
2161 2161 merge 5 and 4
2162 2162
2163 2163 diff -r 99b31f1c2782 -r fc281d8ff18d dir/b
2164 2164 --- a/dir/b Thu Jan 01 00:00:00 1970 +0000
2165 2165 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2166 2166 @@ -1,1 +0,0 @@
2167 2167 -a
2168 2168 diff -r 99b31f1c2782 -r fc281d8ff18d e
2169 2169 --- a/e Thu Jan 01 00:00:00 1970 +0000
2170 2170 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2171 2171 @@ -1,1 +1,1 @@
2172 2172 -ee
2173 2173 +merge
2174 2174 # HG changeset patch
2175 2175 # User test
2176 2176 # Date 0 0
2177 2177 # Thu Jan 01 00:00:00 1970 +0000
2178 2178 # Node ID 02dbb8e276b8ab7abfd07cab50c901647e75c2dd
2179 2179 # Parent fc281d8ff18d999ad6497b3d27390bcd695dcc73
2180 2180 Added tag foo-bar for changeset fc281d8ff18d
2181 2181
2182 2182 diff -r fc281d8ff18d -r 02dbb8e276b8 .hgtags
2183 2183 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2184 2184 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
2185 2185 @@ -0,0 +1,1 @@
2186 2186 +fc281d8ff18d999ad6497b3d27390bcd695dcc73 foo-bar
2187 2187 # HG changeset patch
2188 2188 # User test
2189 2189 # Date 0 0
2190 2190 # Thu Jan 01 00:00:00 1970 +0000
2191 2191 # Node ID 24c2e826ddebf80f9dcd60b856bdb8e6715c5449
2192 2192 # Parent fc281d8ff18d999ad6497b3d27390bcd695dcc73
2193 2193 add g
2194 2194
2195 2195 diff -r fc281d8ff18d -r 24c2e826ddeb g
2196 2196 --- a/g Thu Jan 01 00:00:00 1970 +0000
2197 2197 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2198 2198 @@ -1,2 +1,1 @@
2199 2199 -f
2200 2200 -f
2201 2201 +g
2202 2202 $ testlog --follow -r6 -r8 -r5 -r7 -r4
2203 2203 ['6', '8', '5', '7', '4']
2204 2204 (group
2205 2205 (func
2206 2206 ('symbol', 'descendants')
2207 2207 (func
2208 2208 ('symbol', 'rev')
2209 2209 ('symbol', '6'))))
2210 2210
2211 2211 Test --follow-first and forward --rev
2212 2212
2213 2213 $ testlog --follow-first -r6 -r8 -r5 -r7 -r4
2214 2214 ['6', '8', '5', '7', '4']
2215 2215 (group
2216 2216 (func
2217 2217 ('symbol', '_firstdescendants')
2218 2218 (func
2219 2219 ('symbol', 'rev')
2220 2220 ('symbol', '6'))))
2221 2221 --- log.nodes * (glob)
2222 2222 +++ glog.nodes * (glob)
2223 2223 @@ -1,3 +1,3 @@
2224 2224 -nodetag 6
2225 2225 nodetag 8
2226 2226 nodetag 7
2227 2227 +nodetag 6
2228 2228
2229 2229 Test --follow and backward --rev
2230 2230
2231 2231 $ testlog --follow -r6 -r5 -r7 -r8 -r4
2232 2232 ['6', '5', '7', '8', '4']
2233 2233 (group
2234 2234 (func
2235 2235 ('symbol', 'ancestors')
2236 2236 (func
2237 2237 ('symbol', 'rev')
2238 2238 ('symbol', '6'))))
2239 2239
2240 2240 Test --follow-first and backward --rev
2241 2241
2242 2242 $ testlog --follow-first -r6 -r5 -r7 -r8 -r4
2243 2243 ['6', '5', '7', '8', '4']
2244 2244 (group
2245 2245 (func
2246 2246 ('symbol', '_firstancestors')
2247 2247 (func
2248 2248 ('symbol', 'rev')
2249 2249 ('symbol', '6'))))
2250 2250
2251 2251 Test --follow with --rev of graphlog extension
2252 2252
2253 2253 $ hg --config extensions.graphlog= glog -qfr1
2254 2254 o 1:216d4c92cf98
2255 2255 |
2256 2256 o 0:f8035bb17114
2257 2257
2258 2258
2259 2259 Test subdir
2260 2260
2261 2261 $ hg up -q 3
2262 2262 $ cd dir
2263 2263 $ testlog .
2264 2264 []
2265 2265 (group
2266 2266 (func
2267 2267 ('symbol', '_matchfiles')
2268 2268 (list
2269 2269 ('string', 'r:')
2270 2270 ('string', 'd:relpath')
2271 2271 ('string', 'p:.'))))
2272 2272 $ testlog ../b
2273 2273 []
2274 2274 (group
2275 2275 (group
2276 2276 (func
2277 2277 ('symbol', 'filelog')
2278 2278 ('string', '../b'))))
2279 2279 $ testlog -f ../b
2280 2280 []
2281 2281 (group
2282 2282 (group
2283 2283 (func
2284 2284 ('symbol', 'follow')
2285 2285 ('string', 'b'))))
2286 2286 $ cd ..
2287 2287
2288 2288 Test --hidden
2289 2289 (enable obsolete)
2290 2290
2291 2291 $ cat >> $HGRCPATH << EOF
2292 2292 > [experimental]
2293 2293 > evolution=createmarkers
2294 2294 > EOF
2295 2295
2296 2296 $ hg debugobsolete `hg id --debug -i -r 8`
2297 2297 $ testlog
2298 2298 []
2299 2299 []
2300 2300 $ testlog --hidden
2301 2301 []
2302 2302 []
2303 2303 $ hg log -G --template '{rev} {desc}\n'
2304 2304 o 7 Added tag foo-bar for changeset fc281d8ff18d
2305 2305 |
2306 2306 o 6 merge 5 and 4
2307 2307 |\
2308 2308 | o 5 add another e
2309 2309 | |
2310 2310 o | 4 mv dir/b e
2311 2311 |/
2312 2312 @ 3 mv a b; add d
2313 2313 |
2314 2314 o 2 mv b dir/b
2315 2315 |
2316 2316 o 1 copy a b
2317 2317 |
2318 2318 o 0 add a
2319 2319
2320 2320
2321 2321 A template without trailing newline should do something sane
2322 2322
2323 2323 $ hg log -G -r ::2 --template '{rev} {desc}'
2324 2324 o 2 mv b dir/b
2325 2325 |
2326 2326 o 1 copy a b
2327 2327 |
2328 2328 o 0 add a
2329 2329
2330 2330
2331 2331 Extra newlines must be preserved
2332 2332
2333 2333 $ hg log -G -r ::2 --template '\n{rev} {desc}\n\n'
2334 2334 o
2335 2335 | 2 mv b dir/b
2336 2336 |
2337 2337 o
2338 2338 | 1 copy a b
2339 2339 |
2340 2340 o
2341 2341 0 add a
2342 2342
2343 2343
2344 2344 The almost-empty template should do something sane too ...
2345 2345
2346 2346 $ hg log -G -r ::2 --template '\n'
2347 2347 o
2348 2348 |
2349 2349 o
2350 2350 |
2351 2351 o
2352 2352
2353 2353
2354 2354 issue3772
2355 2355
2356 2356 $ hg log -G -r :null
2357 2357 o changeset: 0:f8035bb17114
2358 2358 | user: test
2359 2359 | date: Thu Jan 01 00:00:00 1970 +0000
2360 2360 | summary: add a
2361 2361 |
2362 2362 o changeset: -1:000000000000
2363 2363 user:
2364 2364 date: Thu Jan 01 00:00:00 1970 +0000
2365 2365
2366 2366 $ hg log -G -r null:null
2367 2367 o changeset: -1:000000000000
2368 2368 user:
2369 2369 date: Thu Jan 01 00:00:00 1970 +0000
2370 2370
2371 2371
2372 2372 should not draw line down to null due to the magic of fullreposet
2373 2373
2374 2374 $ hg log -G -r 'all()' | tail -6
2375 2375 |
2376 2376 o changeset: 0:f8035bb17114
2377 2377 user: test
2378 2378 date: Thu Jan 01 00:00:00 1970 +0000
2379 2379 summary: add a
2380 2380
2381 2381
2382 2382 $ hg log -G -r 'branch(default)' | tail -6
2383 2383 |
2384 2384 o changeset: 0:f8035bb17114
2385 2385 user: test
2386 2386 date: Thu Jan 01 00:00:00 1970 +0000
2387 2387 summary: add a
2388 2388
2389 2389
2390 2390 working-directory revision
2391 2391
2392 2392 $ hg log -G -qr '. + wdir()'
2393 2393 o 2147483647:ffffffffffff
2394 2394 |
2395 2395 @ 3:5918b8d165d1
2396 2396 |
2397 2397 ~
2398 2398
2399 2399 node template with changeset_printer:
2400 2400
2401 2401 $ hg log -Gqr 5:7 --config ui.graphnodetemplate='"{rev}"'
2402 2402 7 7:02dbb8e276b8
2403 2403 |
2404 2404 6 6:fc281d8ff18d
2405 2405 |\
2406 2406 | ~
2407 2407 5 5:99b31f1c2782
2408 2408 |
2409 2409 ~
2410 2410
2411 2411 node template with changeset_templater (shared cache variable):
2412 2412
2413 2413 $ hg log -Gr 5:7 -T '{latesttag % "{rev} {tag}+{distance}"}\n' \
2414 2414 > --config ui.graphnodetemplate='{ifeq(latesttagdistance, 0, "#", graphnode)}'
2415 2415 o 7 foo-bar+1
2416 2416 |
2417 2417 # 6 foo-bar+0
2418 2418 |\
2419 2419 | ~
2420 2420 o 5 null+5
2421 2421 |
2422 2422 ~
2423 2423
2424 2424 label() should just work in node template:
2425 2425
2426 2426 $ hg log -Gqr 7 --config extensions.color= --color=debug \
2427 2427 > --config ui.graphnodetemplate='{label("branch.{branch}", rev)}'
2428 2428 [branch.default|7] [log.node|7:02dbb8e276b8]
2429 2429 |
2430 2430 ~
2431 2431
2432 2432 $ cd ..
2433 2433
2434 2434 change graph edge styling
2435 2435
2436 2436 $ cd repo
2437 2437 $ cat << EOF >> $HGRCPATH
2438 2438 > [experimental]
2439 2439 > graphstyle.parent = |
2440 2440 > graphstyle.grandparent = :
2441 2441 > graphstyle.missing =
2442 2442 > EOF
2443 2443 $ hg log -G -r 'file("a")' -m
2444 2444 @ changeset: 36:08a19a744424
2445 2445 : branch: branch
2446 2446 : tag: tip
2447 2447 : parent: 35:9159c3644c5e
2448 2448 : parent: 35:9159c3644c5e
2449 2449 : user: test
2450 2450 : date: Thu Jan 01 00:00:36 1970 +0000
2451 2451 : summary: (36) buggy merge: identical parents
2452 2452 :
2453 2453 o changeset: 32:d06dffa21a31
2454 2454 |\ parent: 27:886ed638191b
2455 2455 | : parent: 31:621d83e11f67
2456 2456 | : user: test
2457 2457 | : date: Thu Jan 01 00:00:32 1970 +0000
2458 2458 | : summary: (32) expand
2459 2459 | :
2460 2460 o : changeset: 31:621d83e11f67
2461 2461 |\: parent: 21:d42a756af44d
2462 2462 | : parent: 30:6e11cd4b648f
2463 2463 | : user: test
2464 2464 | : date: Thu Jan 01 00:00:31 1970 +0000
2465 2465 | : summary: (31) expand
2466 2466 | :
2467 2467 o : changeset: 30:6e11cd4b648f
2468 2468 |\ \ parent: 28:44ecd0b9ae99
2469 2469 | ~ : parent: 29:cd9bb2be7593
2470 2470 | : user: test
2471 2471 | : date: Thu Jan 01 00:00:30 1970 +0000
2472 2472 | : summary: (30) expand
2473 2473 | /
2474 2474 o : changeset: 28:44ecd0b9ae99
2475 2475 |\ \ parent: 1:6db2ef61d156
2476 2476 | ~ : parent: 26:7f25b6c2f0b9
2477 2477 | : user: test
2478 2478 | : date: Thu Jan 01 00:00:28 1970 +0000
2479 2479 | : summary: (28) merge zero known
2480 2480 | /
2481 2481 o : changeset: 26:7f25b6c2f0b9
2482 2482 |\ \ parent: 18:1aa84d96232a
2483 2483 | | : parent: 25:91da8ed57247
2484 2484 | | : user: test
2485 2485 | | : date: Thu Jan 01 00:00:26 1970 +0000
2486 2486 | | : summary: (26) merge one known; far right
2487 2487 | | :
2488 2488 | o : changeset: 25:91da8ed57247
2489 2489 | |\: parent: 21:d42a756af44d
2490 2490 | | : parent: 24:a9c19a3d96b7
2491 2491 | | : user: test
2492 2492 | | : date: Thu Jan 01 00:00:25 1970 +0000
2493 2493 | | : summary: (25) merge one known; far left
2494 2494 | | :
2495 2495 | o : changeset: 24:a9c19a3d96b7
2496 2496 | |\ \ parent: 0:e6eb3150255d
2497 2497 | | ~ : parent: 23:a01cddf0766d
2498 2498 | | : user: test
2499 2499 | | : date: Thu Jan 01 00:00:24 1970 +0000
2500 2500 | | : summary: (24) merge one known; immediate right
2501 2501 | | /
2502 2502 | o : changeset: 23:a01cddf0766d
2503 2503 | |\ \ parent: 1:6db2ef61d156
2504 2504 | | ~ : parent: 22:e0d9cccacb5d
2505 2505 | | : user: test
2506 2506 | | : date: Thu Jan 01 00:00:23 1970 +0000
2507 2507 | | : summary: (23) merge one known; immediate left
2508 2508 | | /
2509 2509 | o : changeset: 22:e0d9cccacb5d
2510 2510 |/:/ parent: 18:1aa84d96232a
2511 2511 | : parent: 21:d42a756af44d
2512 2512 | : user: test
2513 2513 | : date: Thu Jan 01 00:00:22 1970 +0000
2514 2514 | : summary: (22) merge two known; one far left, one far right
2515 2515 | :
2516 2516 | o changeset: 21:d42a756af44d
2517 2517 | |\ parent: 19:31ddc2c1573b
2518 2518 | | | parent: 20:d30ed6450e32
2519 2519 | | | user: test
2520 2520 | | | date: Thu Jan 01 00:00:21 1970 +0000
2521 2521 | | | summary: (21) expand
2522 2522 | | |
2523 2523 +---o changeset: 20:d30ed6450e32
2524 2524 | | | parent: 0:e6eb3150255d
2525 2525 | | ~ parent: 18:1aa84d96232a
2526 2526 | | user: test
2527 2527 | | date: Thu Jan 01 00:00:20 1970 +0000
2528 2528 | | summary: (20) merge two known; two far right
2529 2529 | |
2530 2530 | o changeset: 19:31ddc2c1573b
2531 2531 | |\ parent: 15:1dda3f72782d
2532 2532 | | | parent: 17:44765d7c06e0
2533 2533 | | | user: test
2534 2534 | | | date: Thu Jan 01 00:00:19 1970 +0000
2535 2535 | | | summary: (19) expand
2536 2536 | | |
2537 2537 o | | changeset: 18:1aa84d96232a
2538 2538 |\| | parent: 1:6db2ef61d156
2539 2539 ~ | | parent: 15:1dda3f72782d
2540 2540 | | user: test
2541 2541 | | date: Thu Jan 01 00:00:18 1970 +0000
2542 2542 | | summary: (18) merge two known; two far left
2543 2543 / /
2544 2544 | o changeset: 17:44765d7c06e0
2545 2545 | |\ parent: 12:86b91144a6e9
2546 2546 | | | parent: 16:3677d192927d
2547 2547 | | | user: test
2548 2548 | | | date: Thu Jan 01 00:00:17 1970 +0000
2549 2549 | | | summary: (17) expand
2550 2550 | | |
2551 2551 | | o changeset: 16:3677d192927d
2552 2552 | | |\ parent: 0:e6eb3150255d
2553 2553 | | ~ ~ parent: 1:6db2ef61d156
2554 2554 | | user: test
2555 2555 | | date: Thu Jan 01 00:00:16 1970 +0000
2556 2556 | | summary: (16) merge two known; one immediate right, one near right
2557 2557 | |
2558 2558 o | changeset: 15:1dda3f72782d
2559 2559 |\ \ parent: 13:22d8966a97e3
2560 2560 | | | parent: 14:8eac370358ef
2561 2561 | | | user: test
2562 2562 | | | date: Thu Jan 01 00:00:15 1970 +0000
2563 2563 | | | summary: (15) expand
2564 2564 | | |
2565 2565 | o | changeset: 14:8eac370358ef
2566 2566 | |\| parent: 0:e6eb3150255d
2567 2567 | ~ | parent: 12:86b91144a6e9
2568 2568 | | user: test
2569 2569 | | date: Thu Jan 01 00:00:14 1970 +0000
2570 2570 | | summary: (14) merge two known; one immediate right, one far right
2571 2571 | /
2572 2572 o | changeset: 13:22d8966a97e3
2573 2573 |\ \ parent: 9:7010c0af0a35
2574 2574 | | | parent: 11:832d76e6bdf2
2575 2575 | | | user: test
2576 2576 | | | date: Thu Jan 01 00:00:13 1970 +0000
2577 2577 | | | summary: (13) expand
2578 2578 | | |
2579 2579 +---o changeset: 12:86b91144a6e9
2580 2580 | | | parent: 1:6db2ef61d156
2581 2581 | | ~ parent: 9:7010c0af0a35
2582 2582 | | user: test
2583 2583 | | date: Thu Jan 01 00:00:12 1970 +0000
2584 2584 | | summary: (12) merge two known; one immediate right, one far left
2585 2585 | |
2586 2586 | o changeset: 11:832d76e6bdf2
2587 2587 | |\ parent: 6:b105a072e251
2588 2588 | | | parent: 10:74c64d036d72
2589 2589 | | | user: test
2590 2590 | | | date: Thu Jan 01 00:00:11 1970 +0000
2591 2591 | | | summary: (11) expand
2592 2592 | | |
2593 2593 | | o changeset: 10:74c64d036d72
2594 2594 | |/| parent: 0:e6eb3150255d
2595 2595 | | ~ parent: 6:b105a072e251
2596 2596 | | user: test
2597 2597 | | date: Thu Jan 01 00:00:10 1970 +0000
2598 2598 | | summary: (10) merge two known; one immediate left, one near right
2599 2599 | |
2600 2600 o | changeset: 9:7010c0af0a35
2601 2601 |\ \ parent: 7:b632bb1b1224
2602 2602 | | | parent: 8:7a0b11f71937
2603 2603 | | | user: test
2604 2604 | | | date: Thu Jan 01 00:00:09 1970 +0000
2605 2605 | | | summary: (9) expand
2606 2606 | | |
2607 2607 | o | changeset: 8:7a0b11f71937
2608 2608 |/| | parent: 0:e6eb3150255d
2609 2609 | ~ | parent: 7:b632bb1b1224
2610 2610 | | user: test
2611 2611 | | date: Thu Jan 01 00:00:08 1970 +0000
2612 2612 | | summary: (8) merge two known; one immediate left, one far right
2613 2613 | /
2614 2614 o | changeset: 7:b632bb1b1224
2615 2615 |\ \ parent: 2:3d9a33b8d1e1
2616 2616 | ~ | parent: 5:4409d547b708
2617 2617 | | user: test
2618 2618 | | date: Thu Jan 01 00:00:07 1970 +0000
2619 2619 | | summary: (7) expand
2620 2620 | /
2621 2621 | o changeset: 6:b105a072e251
2622 2622 |/| parent: 2:3d9a33b8d1e1
2623 2623 | ~ parent: 5:4409d547b708
2624 2624 | user: test
2625 2625 | date: Thu Jan 01 00:00:06 1970 +0000
2626 2626 | summary: (6) merge two known; one immediate left, one far left
2627 2627 |
2628 2628 o changeset: 5:4409d547b708
2629 2629 |\ parent: 3:27eef8ed80b4
2630 2630 | ~ parent: 4:26a8bac39d9f
2631 2631 | user: test
2632 2632 | date: Thu Jan 01 00:00:05 1970 +0000
2633 2633 | summary: (5) expand
2634 2634 |
2635 2635 o changeset: 4:26a8bac39d9f
2636 2636 |\ parent: 1:6db2ef61d156
2637 2637 ~ ~ parent: 3:27eef8ed80b4
2638 2638 user: test
2639 2639 date: Thu Jan 01 00:00:04 1970 +0000
2640 2640 summary: (4) merge two known; one immediate left, one immediate right
2641 2641
2642 2642
2643 2643 Setting HGPLAIN ignores graphmod styling:
2644 2644
2645 2645 $ HGPLAIN=1 hg log -G -r 'file("a")' -m
2646 2646 @ changeset: 36:08a19a744424
2647 2647 | branch: branch
2648 2648 | tag: tip
2649 2649 | parent: 35:9159c3644c5e
2650 2650 | parent: 35:9159c3644c5e
2651 2651 | user: test
2652 2652 | date: Thu Jan 01 00:00:36 1970 +0000
2653 2653 | summary: (36) buggy merge: identical parents
2654 2654 |
2655 2655 o changeset: 32:d06dffa21a31
2656 2656 |\ parent: 27:886ed638191b
2657 2657 | | parent: 31:621d83e11f67
2658 2658 | | user: test
2659 2659 | | date: Thu Jan 01 00:00:32 1970 +0000
2660 2660 | | summary: (32) expand
2661 2661 | |
2662 2662 o | changeset: 31:621d83e11f67
2663 2663 |\| parent: 21:d42a756af44d
2664 2664 | | parent: 30:6e11cd4b648f
2665 2665 | | user: test
2666 2666 | | date: Thu Jan 01 00:00:31 1970 +0000
2667 2667 | | summary: (31) expand
2668 2668 | |
2669 2669 o | changeset: 30:6e11cd4b648f
2670 2670 |\ \ parent: 28:44ecd0b9ae99
2671 2671 | | | parent: 29:cd9bb2be7593
2672 2672 | | | user: test
2673 2673 | | | date: Thu Jan 01 00:00:30 1970 +0000
2674 2674 | | | summary: (30) expand
2675 2675 | | |
2676 2676 o | | changeset: 28:44ecd0b9ae99
2677 2677 |\ \ \ parent: 1:6db2ef61d156
2678 2678 | | | | parent: 26:7f25b6c2f0b9
2679 2679 | | | | user: test
2680 2680 | | | | date: Thu Jan 01 00:00:28 1970 +0000
2681 2681 | | | | summary: (28) merge zero known
2682 2682 | | | |
2683 2683 o | | | changeset: 26:7f25b6c2f0b9
2684 2684 |\ \ \ \ parent: 18:1aa84d96232a
2685 2685 | | | | | parent: 25:91da8ed57247
2686 2686 | | | | | user: test
2687 2687 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
2688 2688 | | | | | summary: (26) merge one known; far right
2689 2689 | | | | |
2690 2690 | o-----+ changeset: 25:91da8ed57247
2691 2691 | | | | | parent: 21:d42a756af44d
2692 2692 | | | | | parent: 24:a9c19a3d96b7
2693 2693 | | | | | user: test
2694 2694 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
2695 2695 | | | | | summary: (25) merge one known; far left
2696 2696 | | | | |
2697 2697 | o | | | changeset: 24:a9c19a3d96b7
2698 2698 | |\ \ \ \ parent: 0:e6eb3150255d
2699 2699 | | | | | | parent: 23:a01cddf0766d
2700 2700 | | | | | | user: test
2701 2701 | | | | | | date: Thu Jan 01 00:00:24 1970 +0000
2702 2702 | | | | | | summary: (24) merge one known; immediate right
2703 2703 | | | | | |
2704 2704 | o---+ | | changeset: 23:a01cddf0766d
2705 2705 | | | | | | parent: 1:6db2ef61d156
2706 2706 | | | | | | parent: 22:e0d9cccacb5d
2707 2707 | | | | | | user: test
2708 2708 | | | | | | date: Thu Jan 01 00:00:23 1970 +0000
2709 2709 | | | | | | summary: (23) merge one known; immediate left
2710 2710 | | | | | |
2711 2711 | o-------+ changeset: 22:e0d9cccacb5d
2712 2712 | | | | | | parent: 18:1aa84d96232a
2713 2713 |/ / / / / parent: 21:d42a756af44d
2714 2714 | | | | | user: test
2715 2715 | | | | | date: Thu Jan 01 00:00:22 1970 +0000
2716 2716 | | | | | summary: (22) merge two known; one far left, one far right
2717 2717 | | | | |
2718 2718 | | | | o changeset: 21:d42a756af44d
2719 2719 | | | | |\ parent: 19:31ddc2c1573b
2720 2720 | | | | | | parent: 20:d30ed6450e32
2721 2721 | | | | | | user: test
2722 2722 | | | | | | date: Thu Jan 01 00:00:21 1970 +0000
2723 2723 | | | | | | summary: (21) expand
2724 2724 | | | | | |
2725 2725 +-+-------o changeset: 20:d30ed6450e32
2726 2726 | | | | | parent: 0:e6eb3150255d
2727 2727 | | | | | parent: 18:1aa84d96232a
2728 2728 | | | | | user: test
2729 2729 | | | | | date: Thu Jan 01 00:00:20 1970 +0000
2730 2730 | | | | | summary: (20) merge two known; two far right
2731 2731 | | | | |
2732 2732 | | | | o changeset: 19:31ddc2c1573b
2733 2733 | | | | |\ parent: 15:1dda3f72782d
2734 2734 | | | | | | parent: 17:44765d7c06e0
2735 2735 | | | | | | user: test
2736 2736 | | | | | | date: Thu Jan 01 00:00:19 1970 +0000
2737 2737 | | | | | | summary: (19) expand
2738 2738 | | | | | |
2739 2739 o---+---+ | changeset: 18:1aa84d96232a
2740 2740 | | | | | parent: 1:6db2ef61d156
2741 2741 / / / / / parent: 15:1dda3f72782d
2742 2742 | | | | | user: test
2743 2743 | | | | | date: Thu Jan 01 00:00:18 1970 +0000
2744 2744 | | | | | summary: (18) merge two known; two far left
2745 2745 | | | | |
2746 2746 | | | | o changeset: 17:44765d7c06e0
2747 2747 | | | | |\ parent: 12:86b91144a6e9
2748 2748 | | | | | | parent: 16:3677d192927d
2749 2749 | | | | | | user: test
2750 2750 | | | | | | date: Thu Jan 01 00:00:17 1970 +0000
2751 2751 | | | | | | summary: (17) expand
2752 2752 | | | | | |
2753 2753 +-+-------o changeset: 16:3677d192927d
2754 2754 | | | | | parent: 0:e6eb3150255d
2755 2755 | | | | | parent: 1:6db2ef61d156
2756 2756 | | | | | user: test
2757 2757 | | | | | date: Thu Jan 01 00:00:16 1970 +0000
2758 2758 | | | | | summary: (16) merge two known; one immediate right, one near right
2759 2759 | | | | |
2760 2760 | | | o | changeset: 15:1dda3f72782d
2761 2761 | | | |\ \ parent: 13:22d8966a97e3
2762 2762 | | | | | | parent: 14:8eac370358ef
2763 2763 | | | | | | user: test
2764 2764 | | | | | | date: Thu Jan 01 00:00:15 1970 +0000
2765 2765 | | | | | | summary: (15) expand
2766 2766 | | | | | |
2767 2767 +-------o | changeset: 14:8eac370358ef
2768 2768 | | | | |/ parent: 0:e6eb3150255d
2769 2769 | | | | | parent: 12:86b91144a6e9
2770 2770 | | | | | user: test
2771 2771 | | | | | date: Thu Jan 01 00:00:14 1970 +0000
2772 2772 | | | | | summary: (14) merge two known; one immediate right, one far right
2773 2773 | | | | |
2774 2774 | | | o | changeset: 13:22d8966a97e3
2775 2775 | | | |\ \ parent: 9:7010c0af0a35
2776 2776 | | | | | | parent: 11:832d76e6bdf2
2777 2777 | | | | | | user: test
2778 2778 | | | | | | date: Thu Jan 01 00:00:13 1970 +0000
2779 2779 | | | | | | summary: (13) expand
2780 2780 | | | | | |
2781 2781 | +---+---o changeset: 12:86b91144a6e9
2782 2782 | | | | | parent: 1:6db2ef61d156
2783 2783 | | | | | parent: 9:7010c0af0a35
2784 2784 | | | | | user: test
2785 2785 | | | | | date: Thu Jan 01 00:00:12 1970 +0000
2786 2786 | | | | | summary: (12) merge two known; one immediate right, one far left
2787 2787 | | | | |
2788 2788 | | | | o changeset: 11:832d76e6bdf2
2789 2789 | | | | |\ parent: 6:b105a072e251
2790 2790 | | | | | | parent: 10:74c64d036d72
2791 2791 | | | | | | user: test
2792 2792 | | | | | | date: Thu Jan 01 00:00:11 1970 +0000
2793 2793 | | | | | | summary: (11) expand
2794 2794 | | | | | |
2795 2795 +---------o changeset: 10:74c64d036d72
2796 2796 | | | | |/ parent: 0:e6eb3150255d
2797 2797 | | | | | parent: 6:b105a072e251
2798 2798 | | | | | user: test
2799 2799 | | | | | date: Thu Jan 01 00:00:10 1970 +0000
2800 2800 | | | | | summary: (10) merge two known; one immediate left, one near right
2801 2801 | | | | |
2802 2802 | | | o | changeset: 9:7010c0af0a35
2803 2803 | | | |\ \ parent: 7:b632bb1b1224
2804 2804 | | | | | | parent: 8:7a0b11f71937
2805 2805 | | | | | | user: test
2806 2806 | | | | | | date: Thu Jan 01 00:00:09 1970 +0000
2807 2807 | | | | | | summary: (9) expand
2808 2808 | | | | | |
2809 2809 +-------o | changeset: 8:7a0b11f71937
2810 2810 | | | |/ / parent: 0:e6eb3150255d
2811 2811 | | | | | parent: 7:b632bb1b1224
2812 2812 | | | | | user: test
2813 2813 | | | | | date: Thu Jan 01 00:00:08 1970 +0000
2814 2814 | | | | | summary: (8) merge two known; one immediate left, one far right
2815 2815 | | | | |
2816 2816 | | | o | changeset: 7:b632bb1b1224
2817 2817 | | | |\ \ parent: 2:3d9a33b8d1e1
2818 2818 | | | | | | parent: 5:4409d547b708
2819 2819 | | | | | | user: test
2820 2820 | | | | | | date: Thu Jan 01 00:00:07 1970 +0000
2821 2821 | | | | | | summary: (7) expand
2822 2822 | | | | | |
2823 2823 | | | +---o changeset: 6:b105a072e251
2824 2824 | | | | |/ parent: 2:3d9a33b8d1e1
2825 2825 | | | | | parent: 5:4409d547b708
2826 2826 | | | | | user: test
2827 2827 | | | | | date: Thu Jan 01 00:00:06 1970 +0000
2828 2828 | | | | | summary: (6) merge two known; one immediate left, one far left
2829 2829 | | | | |
2830 2830 | | | o | changeset: 5:4409d547b708
2831 2831 | | | |\ \ parent: 3:27eef8ed80b4
2832 2832 | | | | | | parent: 4:26a8bac39d9f
2833 2833 | | | | | | user: test
2834 2834 | | | | | | date: Thu Jan 01 00:00:05 1970 +0000
2835 2835 | | | | | | summary: (5) expand
2836 2836 | | | | | |
2837 2837 | +---o | | changeset: 4:26a8bac39d9f
2838 2838 | | | |/ / parent: 1:6db2ef61d156
2839 2839 | | | | | parent: 3:27eef8ed80b4
2840 2840 | | | | | user: test
2841 2841 | | | | | date: Thu Jan 01 00:00:04 1970 +0000
2842 2842 | | | | | summary: (4) merge two known; one immediate left, one immediate right
2843 2843 | | | | |
2844 2844
2845 2845 .. unless HGPLAINEXCEPT=graph is set:
2846 2846
2847 2847 $ HGPLAIN=1 HGPLAINEXCEPT=graph hg log -G -r 'file("a")' -m
2848 2848 @ changeset: 36:08a19a744424
2849 2849 : branch: branch
2850 2850 : tag: tip
2851 2851 : parent: 35:9159c3644c5e
2852 2852 : parent: 35:9159c3644c5e
2853 2853 : user: test
2854 2854 : date: Thu Jan 01 00:00:36 1970 +0000
2855 2855 : summary: (36) buggy merge: identical parents
2856 2856 :
2857 2857 o changeset: 32:d06dffa21a31
2858 2858 |\ parent: 27:886ed638191b
2859 2859 | : parent: 31:621d83e11f67
2860 2860 | : user: test
2861 2861 | : date: Thu Jan 01 00:00:32 1970 +0000
2862 2862 | : summary: (32) expand
2863 2863 | :
2864 2864 o : changeset: 31:621d83e11f67
2865 2865 |\: parent: 21:d42a756af44d
2866 2866 | : parent: 30:6e11cd4b648f
2867 2867 | : user: test
2868 2868 | : date: Thu Jan 01 00:00:31 1970 +0000
2869 2869 | : summary: (31) expand
2870 2870 | :
2871 2871 o : changeset: 30:6e11cd4b648f
2872 2872 |\ \ parent: 28:44ecd0b9ae99
2873 2873 | ~ : parent: 29:cd9bb2be7593
2874 2874 | : user: test
2875 2875 | : date: Thu Jan 01 00:00:30 1970 +0000
2876 2876 | : summary: (30) expand
2877 2877 | /
2878 2878 o : changeset: 28:44ecd0b9ae99
2879 2879 |\ \ parent: 1:6db2ef61d156
2880 2880 | ~ : parent: 26:7f25b6c2f0b9
2881 2881 | : user: test
2882 2882 | : date: Thu Jan 01 00:00:28 1970 +0000
2883 2883 | : summary: (28) merge zero known
2884 2884 | /
2885 2885 o : changeset: 26:7f25b6c2f0b9
2886 2886 |\ \ parent: 18:1aa84d96232a
2887 2887 | | : parent: 25:91da8ed57247
2888 2888 | | : user: test
2889 2889 | | : date: Thu Jan 01 00:00:26 1970 +0000
2890 2890 | | : summary: (26) merge one known; far right
2891 2891 | | :
2892 2892 | o : changeset: 25:91da8ed57247
2893 2893 | |\: parent: 21:d42a756af44d
2894 2894 | | : parent: 24:a9c19a3d96b7
2895 2895 | | : user: test
2896 2896 | | : date: Thu Jan 01 00:00:25 1970 +0000
2897 2897 | | : summary: (25) merge one known; far left
2898 2898 | | :
2899 2899 | o : changeset: 24:a9c19a3d96b7
2900 2900 | |\ \ parent: 0:e6eb3150255d
2901 2901 | | ~ : parent: 23:a01cddf0766d
2902 2902 | | : user: test
2903 2903 | | : date: Thu Jan 01 00:00:24 1970 +0000
2904 2904 | | : summary: (24) merge one known; immediate right
2905 2905 | | /
2906 2906 | o : changeset: 23:a01cddf0766d
2907 2907 | |\ \ parent: 1:6db2ef61d156
2908 2908 | | ~ : parent: 22:e0d9cccacb5d
2909 2909 | | : user: test
2910 2910 | | : date: Thu Jan 01 00:00:23 1970 +0000
2911 2911 | | : summary: (23) merge one known; immediate left
2912 2912 | | /
2913 2913 | o : changeset: 22:e0d9cccacb5d
2914 2914 |/:/ parent: 18:1aa84d96232a
2915 2915 | : parent: 21:d42a756af44d
2916 2916 | : user: test
2917 2917 | : date: Thu Jan 01 00:00:22 1970 +0000
2918 2918 | : summary: (22) merge two known; one far left, one far right
2919 2919 | :
2920 2920 | o changeset: 21:d42a756af44d
2921 2921 | |\ parent: 19:31ddc2c1573b
2922 2922 | | | parent: 20:d30ed6450e32
2923 2923 | | | user: test
2924 2924 | | | date: Thu Jan 01 00:00:21 1970 +0000
2925 2925 | | | summary: (21) expand
2926 2926 | | |
2927 2927 +---o changeset: 20:d30ed6450e32
2928 2928 | | | parent: 0:e6eb3150255d
2929 2929 | | ~ parent: 18:1aa84d96232a
2930 2930 | | user: test
2931 2931 | | date: Thu Jan 01 00:00:20 1970 +0000
2932 2932 | | summary: (20) merge two known; two far right
2933 2933 | |
2934 2934 | o changeset: 19:31ddc2c1573b
2935 2935 | |\ parent: 15:1dda3f72782d
2936 2936 | | | parent: 17:44765d7c06e0
2937 2937 | | | user: test
2938 2938 | | | date: Thu Jan 01 00:00:19 1970 +0000
2939 2939 | | | summary: (19) expand
2940 2940 | | |
2941 2941 o | | changeset: 18:1aa84d96232a
2942 2942 |\| | parent: 1:6db2ef61d156
2943 2943 ~ | | parent: 15:1dda3f72782d
2944 2944 | | user: test
2945 2945 | | date: Thu Jan 01 00:00:18 1970 +0000
2946 2946 | | summary: (18) merge two known; two far left
2947 2947 / /
2948 2948 | o changeset: 17:44765d7c06e0
2949 2949 | |\ parent: 12:86b91144a6e9
2950 2950 | | | parent: 16:3677d192927d
2951 2951 | | | user: test
2952 2952 | | | date: Thu Jan 01 00:00:17 1970 +0000
2953 2953 | | | summary: (17) expand
2954 2954 | | |
2955 2955 | | o changeset: 16:3677d192927d
2956 2956 | | |\ parent: 0:e6eb3150255d
2957 2957 | | ~ ~ parent: 1:6db2ef61d156
2958 2958 | | user: test
2959 2959 | | date: Thu Jan 01 00:00:16 1970 +0000
2960 2960 | | summary: (16) merge two known; one immediate right, one near right
2961 2961 | |
2962 2962 o | changeset: 15:1dda3f72782d
2963 2963 |\ \ parent: 13:22d8966a97e3
2964 2964 | | | parent: 14:8eac370358ef
2965 2965 | | | user: test
2966 2966 | | | date: Thu Jan 01 00:00:15 1970 +0000
2967 2967 | | | summary: (15) expand
2968 2968 | | |
2969 2969 | o | changeset: 14:8eac370358ef
2970 2970 | |\| parent: 0:e6eb3150255d
2971 2971 | ~ | parent: 12:86b91144a6e9
2972 2972 | | user: test
2973 2973 | | date: Thu Jan 01 00:00:14 1970 +0000
2974 2974 | | summary: (14) merge two known; one immediate right, one far right
2975 2975 | /
2976 2976 o | changeset: 13:22d8966a97e3
2977 2977 |\ \ parent: 9:7010c0af0a35
2978 2978 | | | parent: 11:832d76e6bdf2
2979 2979 | | | user: test
2980 2980 | | | date: Thu Jan 01 00:00:13 1970 +0000
2981 2981 | | | summary: (13) expand
2982 2982 | | |
2983 2983 +---o changeset: 12:86b91144a6e9
2984 2984 | | | parent: 1:6db2ef61d156
2985 2985 | | ~ parent: 9:7010c0af0a35
2986 2986 | | user: test
2987 2987 | | date: Thu Jan 01 00:00:12 1970 +0000
2988 2988 | | summary: (12) merge two known; one immediate right, one far left
2989 2989 | |
2990 2990 | o changeset: 11:832d76e6bdf2
2991 2991 | |\ parent: 6:b105a072e251
2992 2992 | | | parent: 10:74c64d036d72
2993 2993 | | | user: test
2994 2994 | | | date: Thu Jan 01 00:00:11 1970 +0000
2995 2995 | | | summary: (11) expand
2996 2996 | | |
2997 2997 | | o changeset: 10:74c64d036d72
2998 2998 | |/| parent: 0:e6eb3150255d
2999 2999 | | ~ parent: 6:b105a072e251
3000 3000 | | user: test
3001 3001 | | date: Thu Jan 01 00:00:10 1970 +0000
3002 3002 | | summary: (10) merge two known; one immediate left, one near right
3003 3003 | |
3004 3004 o | changeset: 9:7010c0af0a35
3005 3005 |\ \ parent: 7:b632bb1b1224
3006 3006 | | | parent: 8:7a0b11f71937
3007 3007 | | | user: test
3008 3008 | | | date: Thu Jan 01 00:00:09 1970 +0000
3009 3009 | | | summary: (9) expand
3010 3010 | | |
3011 3011 | o | changeset: 8:7a0b11f71937
3012 3012 |/| | parent: 0:e6eb3150255d
3013 3013 | ~ | parent: 7:b632bb1b1224
3014 3014 | | user: test
3015 3015 | | date: Thu Jan 01 00:00:08 1970 +0000
3016 3016 | | summary: (8) merge two known; one immediate left, one far right
3017 3017 | /
3018 3018 o | changeset: 7:b632bb1b1224
3019 3019 |\ \ parent: 2:3d9a33b8d1e1
3020 3020 | ~ | parent: 5:4409d547b708
3021 3021 | | user: test
3022 3022 | | date: Thu Jan 01 00:00:07 1970 +0000
3023 3023 | | summary: (7) expand
3024 3024 | /
3025 3025 | o changeset: 6:b105a072e251
3026 3026 |/| parent: 2:3d9a33b8d1e1
3027 3027 | ~ parent: 5:4409d547b708
3028 3028 | user: test
3029 3029 | date: Thu Jan 01 00:00:06 1970 +0000
3030 3030 | summary: (6) merge two known; one immediate left, one far left
3031 3031 |
3032 3032 o changeset: 5:4409d547b708
3033 3033 |\ parent: 3:27eef8ed80b4
3034 3034 | ~ parent: 4:26a8bac39d9f
3035 3035 | user: test
3036 3036 | date: Thu Jan 01 00:00:05 1970 +0000
3037 3037 | summary: (5) expand
3038 3038 |
3039 3039 o changeset: 4:26a8bac39d9f
3040 3040 |\ parent: 1:6db2ef61d156
3041 3041 ~ ~ parent: 3:27eef8ed80b4
3042 3042 user: test
3043 3043 date: Thu Jan 01 00:00:04 1970 +0000
3044 3044 summary: (4) merge two known; one immediate left, one immediate right
3045 3045
3046 3046 Draw only part of a grandparent line differently with "<N><char>"; only the
3047 3047 last N lines (for positive N) or everything but the first N lines (for
3048 3048 negative N) along the current node use the style, the rest of the edge uses
3049 3049 the parent edge styling.
3050 3050
3051 3051 Last 3 lines:
3052 3052
3053 3053 $ cat << EOF >> $HGRCPATH
3054 3054 > [experimental]
3055 3055 > graphstyle.parent = !
3056 3056 > graphstyle.grandparent = 3.
3057 3057 > graphstyle.missing =
3058 3058 > EOF
3059 3059 $ hg log -G -r '36:18 & file("a")' -m
3060 3060 @ changeset: 36:08a19a744424
3061 3061 ! branch: branch
3062 3062 ! tag: tip
3063 3063 ! parent: 35:9159c3644c5e
3064 3064 ! parent: 35:9159c3644c5e
3065 3065 ! user: test
3066 3066 . date: Thu Jan 01 00:00:36 1970 +0000
3067 3067 . summary: (36) buggy merge: identical parents
3068 3068 .
3069 3069 o changeset: 32:d06dffa21a31
3070 3070 !\ parent: 27:886ed638191b
3071 3071 ! ! parent: 31:621d83e11f67
3072 3072 ! ! user: test
3073 3073 ! . date: Thu Jan 01 00:00:32 1970 +0000
3074 3074 ! . summary: (32) expand
3075 3075 ! .
3076 3076 o ! changeset: 31:621d83e11f67
3077 3077 !\! parent: 21:d42a756af44d
3078 3078 ! ! parent: 30:6e11cd4b648f
3079 3079 ! ! user: test
3080 3080 ! ! date: Thu Jan 01 00:00:31 1970 +0000
3081 3081 ! ! summary: (31) expand
3082 3082 ! !
3083 3083 o ! changeset: 30:6e11cd4b648f
3084 3084 !\ \ parent: 28:44ecd0b9ae99
3085 3085 ! ~ ! parent: 29:cd9bb2be7593
3086 3086 ! ! user: test
3087 3087 ! ! date: Thu Jan 01 00:00:30 1970 +0000
3088 3088 ! ! summary: (30) expand
3089 3089 ! /
3090 3090 o ! changeset: 28:44ecd0b9ae99
3091 3091 !\ \ parent: 1:6db2ef61d156
3092 3092 ! ~ ! parent: 26:7f25b6c2f0b9
3093 3093 ! ! user: test
3094 3094 ! ! date: Thu Jan 01 00:00:28 1970 +0000
3095 3095 ! ! summary: (28) merge zero known
3096 3096 ! /
3097 3097 o ! changeset: 26:7f25b6c2f0b9
3098 3098 !\ \ parent: 18:1aa84d96232a
3099 3099 ! ! ! parent: 25:91da8ed57247
3100 3100 ! ! ! user: test
3101 3101 ! ! ! date: Thu Jan 01 00:00:26 1970 +0000
3102 3102 ! ! ! summary: (26) merge one known; far right
3103 3103 ! ! !
3104 3104 ! o ! changeset: 25:91da8ed57247
3105 3105 ! !\! parent: 21:d42a756af44d
3106 3106 ! ! ! parent: 24:a9c19a3d96b7
3107 3107 ! ! ! user: test
3108 3108 ! ! ! date: Thu Jan 01 00:00:25 1970 +0000
3109 3109 ! ! ! summary: (25) merge one known; far left
3110 3110 ! ! !
3111 3111 ! o ! changeset: 24:a9c19a3d96b7
3112 3112 ! !\ \ parent: 0:e6eb3150255d
3113 3113 ! ! ~ ! parent: 23:a01cddf0766d
3114 3114 ! ! ! user: test
3115 3115 ! ! ! date: Thu Jan 01 00:00:24 1970 +0000
3116 3116 ! ! ! summary: (24) merge one known; immediate right
3117 3117 ! ! /
3118 3118 ! o ! changeset: 23:a01cddf0766d
3119 3119 ! !\ \ parent: 1:6db2ef61d156
3120 3120 ! ! ~ ! parent: 22:e0d9cccacb5d
3121 3121 ! ! ! user: test
3122 3122 ! ! ! date: Thu Jan 01 00:00:23 1970 +0000
3123 3123 ! ! ! summary: (23) merge one known; immediate left
3124 3124 ! ! /
3125 3125 ! o ! changeset: 22:e0d9cccacb5d
3126 3126 !/!/ parent: 18:1aa84d96232a
3127 3127 ! ! parent: 21:d42a756af44d
3128 3128 ! ! user: test
3129 3129 ! ! date: Thu Jan 01 00:00:22 1970 +0000
3130 3130 ! ! summary: (22) merge two known; one far left, one far right
3131 3131 ! !
3132 3132 ! o changeset: 21:d42a756af44d
3133 3133 ! !\ parent: 19:31ddc2c1573b
3134 3134 ! ! ! parent: 20:d30ed6450e32
3135 3135 ! ! ! user: test
3136 3136 ! ! ! date: Thu Jan 01 00:00:21 1970 +0000
3137 3137 ! ! ! summary: (21) expand
3138 3138 ! ! !
3139 3139 +---o changeset: 20:d30ed6450e32
3140 3140 ! ! | parent: 0:e6eb3150255d
3141 3141 ! ! ~ parent: 18:1aa84d96232a
3142 3142 ! ! user: test
3143 3143 ! ! date: Thu Jan 01 00:00:20 1970 +0000
3144 3144 ! ! summary: (20) merge two known; two far right
3145 3145 ! !
3146 3146 ! o changeset: 19:31ddc2c1573b
3147 3147 ! |\ parent: 15:1dda3f72782d
3148 3148 ! ~ ~ parent: 17:44765d7c06e0
3149 3149 ! user: test
3150 3150 ! date: Thu Jan 01 00:00:19 1970 +0000
3151 3151 ! summary: (19) expand
3152 3152 !
3153 3153 o changeset: 18:1aa84d96232a
3154 3154 |\ parent: 1:6db2ef61d156
3155 3155 ~ ~ parent: 15:1dda3f72782d
3156 3156 user: test
3157 3157 date: Thu Jan 01 00:00:18 1970 +0000
3158 3158 summary: (18) merge two known; two far left
3159 3159
3160 3160 All but the first 3 lines:
3161 3161
3162 3162 $ cat << EOF >> $HGRCPATH
3163 3163 > [experimental]
3164 3164 > graphstyle.parent = !
3165 3165 > graphstyle.grandparent = -3.
3166 3166 > graphstyle.missing =
3167 3167 > EOF
3168 3168 $ hg log -G -r '36:18 & file("a")' -m
3169 3169 @ changeset: 36:08a19a744424
3170 3170 ! branch: branch
3171 3171 ! tag: tip
3172 3172 . parent: 35:9159c3644c5e
3173 3173 . parent: 35:9159c3644c5e
3174 3174 . user: test
3175 3175 . date: Thu Jan 01 00:00:36 1970 +0000
3176 3176 . summary: (36) buggy merge: identical parents
3177 3177 .
3178 3178 o changeset: 32:d06dffa21a31
3179 3179 !\ parent: 27:886ed638191b
3180 3180 ! ! parent: 31:621d83e11f67
3181 3181 ! . user: test
3182 3182 ! . date: Thu Jan 01 00:00:32 1970 +0000
3183 3183 ! . summary: (32) expand
3184 3184 ! .
3185 3185 o ! changeset: 31:621d83e11f67
3186 3186 !\! parent: 21:d42a756af44d
3187 3187 ! ! parent: 30:6e11cd4b648f
3188 3188 ! ! user: test
3189 3189 ! ! date: Thu Jan 01 00:00:31 1970 +0000
3190 3190 ! ! summary: (31) expand
3191 3191 ! !
3192 3192 o ! changeset: 30:6e11cd4b648f
3193 3193 !\ \ parent: 28:44ecd0b9ae99
3194 3194 ! ~ ! parent: 29:cd9bb2be7593
3195 3195 ! ! user: test
3196 3196 ! ! date: Thu Jan 01 00:00:30 1970 +0000
3197 3197 ! ! summary: (30) expand
3198 3198 ! /
3199 3199 o ! changeset: 28:44ecd0b9ae99
3200 3200 !\ \ parent: 1:6db2ef61d156
3201 3201 ! ~ ! parent: 26:7f25b6c2f0b9
3202 3202 ! ! user: test
3203 3203 ! ! date: Thu Jan 01 00:00:28 1970 +0000
3204 3204 ! ! summary: (28) merge zero known
3205 3205 ! /
3206 3206 o ! changeset: 26:7f25b6c2f0b9
3207 3207 !\ \ parent: 18:1aa84d96232a
3208 3208 ! ! ! parent: 25:91da8ed57247
3209 3209 ! ! ! user: test
3210 3210 ! ! ! date: Thu Jan 01 00:00:26 1970 +0000
3211 3211 ! ! ! summary: (26) merge one known; far right
3212 3212 ! ! !
3213 3213 ! o ! changeset: 25:91da8ed57247
3214 3214 ! !\! parent: 21:d42a756af44d
3215 3215 ! ! ! parent: 24:a9c19a3d96b7
3216 3216 ! ! ! user: test
3217 3217 ! ! ! date: Thu Jan 01 00:00:25 1970 +0000
3218 3218 ! ! ! summary: (25) merge one known; far left
3219 3219 ! ! !
3220 3220 ! o ! changeset: 24:a9c19a3d96b7
3221 3221 ! !\ \ parent: 0:e6eb3150255d
3222 3222 ! ! ~ ! parent: 23:a01cddf0766d
3223 3223 ! ! ! user: test
3224 3224 ! ! ! date: Thu Jan 01 00:00:24 1970 +0000
3225 3225 ! ! ! summary: (24) merge one known; immediate right
3226 3226 ! ! /
3227 3227 ! o ! changeset: 23:a01cddf0766d
3228 3228 ! !\ \ parent: 1:6db2ef61d156
3229 3229 ! ! ~ ! parent: 22:e0d9cccacb5d
3230 3230 ! ! ! user: test
3231 3231 ! ! ! date: Thu Jan 01 00:00:23 1970 +0000
3232 3232 ! ! ! summary: (23) merge one known; immediate left
3233 3233 ! ! /
3234 3234 ! o ! changeset: 22:e0d9cccacb5d
3235 3235 !/!/ parent: 18:1aa84d96232a
3236 3236 ! ! parent: 21:d42a756af44d
3237 3237 ! ! user: test
3238 3238 ! ! date: Thu Jan 01 00:00:22 1970 +0000
3239 3239 ! ! summary: (22) merge two known; one far left, one far right
3240 3240 ! !
3241 3241 ! o changeset: 21:d42a756af44d
3242 3242 ! !\ parent: 19:31ddc2c1573b
3243 3243 ! ! ! parent: 20:d30ed6450e32
3244 3244 ! ! ! user: test
3245 3245 ! ! ! date: Thu Jan 01 00:00:21 1970 +0000
3246 3246 ! ! ! summary: (21) expand
3247 3247 ! ! !
3248 3248 +---o changeset: 20:d30ed6450e32
3249 3249 ! ! | parent: 0:e6eb3150255d
3250 3250 ! ! ~ parent: 18:1aa84d96232a
3251 3251 ! ! user: test
3252 3252 ! ! date: Thu Jan 01 00:00:20 1970 +0000
3253 3253 ! ! summary: (20) merge two known; two far right
3254 3254 ! !
3255 3255 ! o changeset: 19:31ddc2c1573b
3256 3256 ! |\ parent: 15:1dda3f72782d
3257 3257 ! ~ ~ parent: 17:44765d7c06e0
3258 3258 ! user: test
3259 3259 ! date: Thu Jan 01 00:00:19 1970 +0000
3260 3260 ! summary: (19) expand
3261 3261 !
3262 3262 o changeset: 18:1aa84d96232a
3263 3263 |\ parent: 1:6db2ef61d156
3264 3264 ~ ~ parent: 15:1dda3f72782d
3265 3265 user: test
3266 3266 date: Thu Jan 01 00:00:18 1970 +0000
3267 3267 summary: (18) merge two known; two far left
3268 3268
3269 3269 $ cd ..
3270 3270
3271 3271 Change graph shorten, test better with graphstyle.missing not none
3272 3272
3273 3273 $ cd repo
3274 3274 $ cat << EOF >> $HGRCPATH
3275 3275 > [experimental]
3276 3276 > graphstyle.parent = |
3277 3277 > graphstyle.grandparent = :
3278 3278 > graphstyle.missing = '
3279 3279 > graphshorten = true
3280 3280 > EOF
3281 3281 $ hg log -G -r 'file("a")' -m -T '{rev} {desc}'
3282 3282 @ 36 (36) buggy merge: identical parents
3283 3283 o 32 (32) expand
3284 3284 |\
3285 3285 o : 31 (31) expand
3286 3286 |\:
3287 3287 o : 30 (30) expand
3288 3288 |\ \
3289 3289 o \ \ 28 (28) merge zero known
3290 3290 |\ \ \
3291 3291 o \ \ \ 26 (26) merge one known; far right
3292 3292 |\ \ \ \
3293 3293 | o-----+ 25 (25) merge one known; far left
3294 3294 | o ' ' : 24 (24) merge one known; immediate right
3295 3295 | |\ \ \ \
3296 3296 | o---+ ' : 23 (23) merge one known; immediate left
3297 3297 | o-------+ 22 (22) merge two known; one far left, one far right
3298 3298 |/ / / / /
3299 3299 | ' ' ' o 21 (21) expand
3300 3300 | ' ' ' |\
3301 3301 +-+-------o 20 (20) merge two known; two far right
3302 3302 | ' ' ' o 19 (19) expand
3303 3303 | ' ' ' |\
3304 3304 o---+---+ | 18 (18) merge two known; two far left
3305 3305 / / / / /
3306 3306 ' ' ' | o 17 (17) expand
3307 3307 ' ' ' | |\
3308 3308 +-+-------o 16 (16) merge two known; one immediate right, one near right
3309 3309 ' ' ' o | 15 (15) expand
3310 3310 ' ' ' |\ \
3311 3311 +-------o | 14 (14) merge two known; one immediate right, one far right
3312 3312 ' ' ' | |/
3313 3313 ' ' ' o | 13 (13) expand
3314 3314 ' ' ' |\ \
3315 3315 ' +---+---o 12 (12) merge two known; one immediate right, one far left
3316 3316 ' ' ' | o 11 (11) expand
3317 3317 ' ' ' | |\
3318 3318 +---------o 10 (10) merge two known; one immediate left, one near right
3319 3319 ' ' ' | |/
3320 3320 ' ' ' o | 9 (9) expand
3321 3321 ' ' ' |\ \
3322 3322 +-------o | 8 (8) merge two known; one immediate left, one far right
3323 3323 ' ' ' |/ /
3324 3324 ' ' ' o | 7 (7) expand
3325 3325 ' ' ' |\ \
3326 3326 ' ' ' +---o 6 (6) merge two known; one immediate left, one far left
3327 3327 ' ' ' | '/
3328 3328 ' ' ' o ' 5 (5) expand
3329 3329 ' ' ' |\ \
3330 3330 ' +---o ' ' 4 (4) merge two known; one immediate left, one immediate right
3331 3331 ' ' ' '/ /
3332 3332
3333 3333 behavior with newlines
3334 3334
3335 3335 $ hg log -G -r ::2 -T '{rev} {desc}'
3336 3336 o 2 (2) collapse
3337 3337 o 1 (1) collapse
3338 3338 o 0 (0) root
3339 3339
3340 3340 $ hg log -G -r ::2 -T '{rev} {desc}\n'
3341 3341 o 2 (2) collapse
3342 3342 o 1 (1) collapse
3343 3343 o 0 (0) root
3344 3344
3345 3345 $ hg log -G -r ::2 -T '{rev} {desc}\n\n'
3346 3346 o 2 (2) collapse
3347 3347 |
3348 3348 o 1 (1) collapse
3349 3349 |
3350 3350 o 0 (0) root
3351 3351
3352 3352
3353 3353 $ hg log -G -r ::2 -T '\n{rev} {desc}'
3354 3354 o
3355 3355 | 2 (2) collapse
3356 3356 o
3357 3357 | 1 (1) collapse
3358 3358 o
3359 3359 0 (0) root
3360 3360
3361 3361 $ hg log -G -r ::2 -T '{rev} {desc}\n\n\n'
3362 3362 o 2 (2) collapse
3363 3363 |
3364 3364 |
3365 3365 o 1 (1) collapse
3366 3366 |
3367 3367 |
3368 3368 o 0 (0) root
3369 3369
3370 3370
3371 3371 $ cd ..
3372 3372
3373 3373 When inserting extra line nodes to handle more than 2 parents, ensure that
3374 3374 the right node styles are used (issue5174):
3375 3375
3376 3376 $ hg init repo-issue5174
3377 3377 $ cd repo-issue5174
3378 3378 $ echo a > f0
3379 3379 $ hg ci -Aqm 0
3380 3380 $ echo a > f1
3381 3381 $ hg ci -Aqm 1
3382 3382 $ echo a > f2
3383 3383 $ hg ci -Aqm 2
3384 3384 $ hg co ".^"
3385 3385 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
3386 3386 $ echo a > f3
3387 3387 $ hg ci -Aqm 3
3388 3388 $ hg co ".^^"
3389 3389 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
3390 3390 $ echo a > f4
3391 3391 $ hg ci -Aqm 4
3392 3392 $ hg merge -r 2
3393 3393 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
3394 3394 (branch merge, don't forget to commit)
3395 3395 $ hg ci -qm 5
3396 3396 $ hg merge -r 3
3397 3397 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3398 3398 (branch merge, don't forget to commit)
3399 3399 $ hg ci -qm 6
3400 3400 $ hg log -G -r '0 | 1 | 2 | 6'
3401 3401 @ changeset: 6:851fe89689ad
3402 3402 :\ tag: tip
3403 3403 : : parent: 5:4f1e3cf15f5d
3404 3404 : : parent: 3:b74ba7084d2d
3405 3405 : : user: test
3406 3406 : : date: Thu Jan 01 00:00:00 1970 +0000
3407 3407 : : summary: 6
3408 3408 : :
3409 3409 : \
3410 3410 : :\
3411 3411 : o : changeset: 2:3e6599df4cce
3412 3412 : :/ user: test
3413 3413 : : date: Thu Jan 01 00:00:00 1970 +0000
3414 3414 : : summary: 2
3415 3415 : :
3416 3416 : o changeset: 1:bd9a55143933
3417 3417 :/ user: test
3418 3418 : date: Thu Jan 01 00:00:00 1970 +0000
3419 3419 : summary: 1
3420 3420 :
3421 3421 o changeset: 0:870a5edc339c
3422 3422 user: test
3423 3423 date: Thu Jan 01 00:00:00 1970 +0000
3424 3424 summary: 0
3425 3425
3426 3426
3427 3427 $ cd ..
3428 3428
3429 3429 Multiple roots (issue5440):
3430 3430
3431 3431 $ hg init multiroots
3432 3432 $ cd multiroots
3433 3433 $ cat <<EOF > .hg/hgrc
3434 3434 > [ui]
3435 3435 > logtemplate = '{rev} {desc}\n\n'
3436 3436 > EOF
3437 3437
3438 3438 $ touch foo
3439 3439 $ hg ci -Aqm foo
3440 3440 $ hg co -q null
3441 3441 $ touch bar
3442 3442 $ hg ci -Aqm bar
3443 3443
3444 3444 $ hg log -Gr null:
3445 3445 @ 1 bar
3446 3446 |
3447 3447 | o 0 foo
3448 3448 |/
3449 3449 o -1
3450 3450
3451 3451 $ hg log -Gr null+0
3452 3452 o 0 foo
3453 3453 |
3454 3454 o -1
3455 3455
3456 3456 $ hg log -Gr null+1
3457 3457 @ 1 bar
3458 3458 |
3459 3459 o -1
3460 3460
3461 3461
3462 3462 $ cd ..
@@ -1,288 +1,288 b''
1 1 This runs with TZ="GMT"
2 2
3 3 $ hg init
4 4 $ echo "test-parse-date" > a
5 5 $ hg add a
6 6 $ hg ci -d "2006-02-01 13:00:30" -m "rev 0"
7 7 $ echo "hi!" >> a
8 8 $ hg ci -d "2006-02-01 13:00:30 -0500" -m "rev 1"
9 9 $ hg tag -d "2006-04-15 13:30" "Hi"
10 10 $ hg backout --merge -d "2006-04-15 13:30 +0200" -m "rev 3" 1
11 11 reverting a
12 12 created new head
13 13 changeset 3:107ce1ee2b43 backs out changeset 1:25a1420a55f8
14 14 merging with changeset 3:107ce1ee2b43
15 15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 16 (branch merge, don't forget to commit)
17 17 $ hg ci -d "1150000000 14400" -m "rev 4 (merge)"
18 18 $ echo "fail" >> a
19 19 $ hg ci -d "should fail" -m "fail"
20 abort: invalid date: 'should fail'
20 hg: parse error: invalid date: 'should fail'
21 21 [255]
22 22 $ hg ci -d "100000000000000000 1400" -m "fail"
23 abort: date exceeds 32 bits: 100000000000000000
23 hg: parse error: date exceeds 32 bits: 100000000000000000
24 24 [255]
25 25 $ hg ci -d "100000 1400000" -m "fail"
26 abort: impossible time zone offset: 1400000
26 hg: parse error: impossible time zone offset: 1400000
27 27 [255]
28 28
29 29 Check with local timezone other than GMT and with DST
30 30
31 31 $ TZ="PST+8PDT+7,M4.1.0/02:00:00,M10.5.0/02:00:00"
32 32 $ export TZ
33 33
34 34 PST=UTC-8 / PDT=UTC-7
35 35 Summer time begins on April's first Sunday at 2:00am,
36 36 and ends on October's last Sunday at 2:00am.
37 37
38 38 $ hg debugrebuildstate
39 39 $ echo "a" > a
40 40 $ hg ci -d "2006-07-15 13:30" -m "summer@UTC-7"
41 41 $ hg debugrebuildstate
42 42 $ echo "b" > a
43 43 $ hg ci -d "2006-07-15 13:30 +0500" -m "summer@UTC+5"
44 44 $ hg debugrebuildstate
45 45 $ echo "c" > a
46 46 $ hg ci -d "2006-01-15 13:30" -m "winter@UTC-8"
47 47 $ hg debugrebuildstate
48 48 $ echo "d" > a
49 49 $ hg ci -d "2006-01-15 13:30 +0500" -m "winter@UTC+5"
50 50 $ hg log --template '{date|date}\n'
51 51 Sun Jan 15 13:30:00 2006 +0500
52 52 Sun Jan 15 13:30:00 2006 -0800
53 53 Sat Jul 15 13:30:00 2006 +0500
54 54 Sat Jul 15 13:30:00 2006 -0700
55 55 Sun Jun 11 00:26:40 2006 -0400
56 56 Sat Apr 15 13:30:00 2006 +0200
57 57 Sat Apr 15 13:30:00 2006 +0000
58 58 Wed Feb 01 13:00:30 2006 -0500
59 59 Wed Feb 01 13:00:30 2006 +0000
60 60
61 61 Test issue1014 (fractional timezones)
62 62
63 63 $ hg debugdate "1000000000 -16200" # 0430
64 64 internal: 1000000000 -16200
65 65 standard: Sun Sep 09 06:16:40 2001 +0430
66 66 $ hg debugdate "1000000000 -15300" # 0415
67 67 internal: 1000000000 -15300
68 68 standard: Sun Sep 09 06:01:40 2001 +0415
69 69 $ hg debugdate "1000000000 -14400" # 0400
70 70 internal: 1000000000 -14400
71 71 standard: Sun Sep 09 05:46:40 2001 +0400
72 72 $ hg debugdate "1000000000 0" # GMT
73 73 internal: 1000000000 0
74 74 standard: Sun Sep 09 01:46:40 2001 +0000
75 75 $ hg debugdate "1000000000 14400" # -0400
76 76 internal: 1000000000 14400
77 77 standard: Sat Sep 08 21:46:40 2001 -0400
78 78 $ hg debugdate "1000000000 15300" # -0415
79 79 internal: 1000000000 15300
80 80 standard: Sat Sep 08 21:31:40 2001 -0415
81 81 $ hg debugdate "1000000000 16200" # -0430
82 82 internal: 1000000000 16200
83 83 standard: Sat Sep 08 21:16:40 2001 -0430
84 84 $ hg debugdate "Sat Sep 08 21:16:40 2001 +0430"
85 85 internal: 999967600 -16200
86 86 standard: Sat Sep 08 21:16:40 2001 +0430
87 87 $ hg debugdate "Sat Sep 08 21:16:40 2001 -0430"
88 88 internal: 1000000000 16200
89 89 standard: Sat Sep 08 21:16:40 2001 -0430
90 90
91 91 Test 12-hours times
92 92
93 93 $ hg debugdate "2006-02-01 1:00:30PM +0000"
94 94 internal: 1138798830 0
95 95 standard: Wed Feb 01 13:00:30 2006 +0000
96 96 $ hg debugdate "1:00:30PM" > /dev/null
97 97
98 98 Normal range
99 99
100 100 $ hg log -d -1
101 101
102 102 Negative range
103 103
104 104 $ hg log -d "--2"
105 105 abort: -2 must be nonnegative (see 'hg help dates')
106 106 [255]
107 107
108 108 Whitespace only
109 109
110 110 $ hg log -d " "
111 111 abort: dates cannot consist entirely of whitespace
112 112 [255]
113 113
114 114 Test date formats with '>' or '<' accompanied by space characters
115 115
116 116 $ hg log -d '>' --template '{date|date}\n'
117 117 abort: invalid day spec, use '>DATE'
118 118 [255]
119 119 $ hg log -d '<' --template '{date|date}\n'
120 120 abort: invalid day spec, use '<DATE'
121 121 [255]
122 122
123 123 $ hg log -d ' >' --template '{date|date}\n'
124 124 abort: invalid day spec, use '>DATE'
125 125 [255]
126 126 $ hg log -d ' <' --template '{date|date}\n'
127 127 abort: invalid day spec, use '<DATE'
128 128 [255]
129 129
130 130 $ hg log -d '> ' --template '{date|date}\n'
131 131 abort: invalid day spec, use '>DATE'
132 132 [255]
133 133 $ hg log -d '< ' --template '{date|date}\n'
134 134 abort: invalid day spec, use '<DATE'
135 135 [255]
136 136
137 137 $ hg log -d ' > ' --template '{date|date}\n'
138 138 abort: invalid day spec, use '>DATE'
139 139 [255]
140 140 $ hg log -d ' < ' --template '{date|date}\n'
141 141 abort: invalid day spec, use '<DATE'
142 142 [255]
143 143
144 144 $ hg log -d '>02/01' --template '{date|date}\n'
145 145 $ hg log -d '<02/01' --template '{date|date}\n'
146 146 Sun Jan 15 13:30:00 2006 +0500
147 147 Sun Jan 15 13:30:00 2006 -0800
148 148 Sat Jul 15 13:30:00 2006 +0500
149 149 Sat Jul 15 13:30:00 2006 -0700
150 150 Sun Jun 11 00:26:40 2006 -0400
151 151 Sat Apr 15 13:30:00 2006 +0200
152 152 Sat Apr 15 13:30:00 2006 +0000
153 153 Wed Feb 01 13:00:30 2006 -0500
154 154 Wed Feb 01 13:00:30 2006 +0000
155 155
156 156 $ hg log -d ' >02/01' --template '{date|date}\n'
157 157 $ hg log -d ' <02/01' --template '{date|date}\n'
158 158 Sun Jan 15 13:30:00 2006 +0500
159 159 Sun Jan 15 13:30:00 2006 -0800
160 160 Sat Jul 15 13:30:00 2006 +0500
161 161 Sat Jul 15 13:30:00 2006 -0700
162 162 Sun Jun 11 00:26:40 2006 -0400
163 163 Sat Apr 15 13:30:00 2006 +0200
164 164 Sat Apr 15 13:30:00 2006 +0000
165 165 Wed Feb 01 13:00:30 2006 -0500
166 166 Wed Feb 01 13:00:30 2006 +0000
167 167
168 168 $ hg log -d '> 02/01' --template '{date|date}\n'
169 169 $ hg log -d '< 02/01' --template '{date|date}\n'
170 170 Sun Jan 15 13:30:00 2006 +0500
171 171 Sun Jan 15 13:30:00 2006 -0800
172 172 Sat Jul 15 13:30:00 2006 +0500
173 173 Sat Jul 15 13:30:00 2006 -0700
174 174 Sun Jun 11 00:26:40 2006 -0400
175 175 Sat Apr 15 13:30:00 2006 +0200
176 176 Sat Apr 15 13:30:00 2006 +0000
177 177 Wed Feb 01 13:00:30 2006 -0500
178 178 Wed Feb 01 13:00:30 2006 +0000
179 179
180 180 $ hg log -d ' > 02/01' --template '{date|date}\n'
181 181 $ hg log -d ' < 02/01' --template '{date|date}\n'
182 182 Sun Jan 15 13:30:00 2006 +0500
183 183 Sun Jan 15 13:30:00 2006 -0800
184 184 Sat Jul 15 13:30:00 2006 +0500
185 185 Sat Jul 15 13:30:00 2006 -0700
186 186 Sun Jun 11 00:26:40 2006 -0400
187 187 Sat Apr 15 13:30:00 2006 +0200
188 188 Sat Apr 15 13:30:00 2006 +0000
189 189 Wed Feb 01 13:00:30 2006 -0500
190 190 Wed Feb 01 13:00:30 2006 +0000
191 191
192 192 $ hg log -d '>02/01 ' --template '{date|date}\n'
193 193 $ hg log -d '<02/01 ' --template '{date|date}\n'
194 194 Sun Jan 15 13:30:00 2006 +0500
195 195 Sun Jan 15 13:30:00 2006 -0800
196 196 Sat Jul 15 13:30:00 2006 +0500
197 197 Sat Jul 15 13:30:00 2006 -0700
198 198 Sun Jun 11 00:26:40 2006 -0400
199 199 Sat Apr 15 13:30:00 2006 +0200
200 200 Sat Apr 15 13:30:00 2006 +0000
201 201 Wed Feb 01 13:00:30 2006 -0500
202 202 Wed Feb 01 13:00:30 2006 +0000
203 203
204 204 $ hg log -d ' >02/01 ' --template '{date|date}\n'
205 205 $ hg log -d ' <02/01 ' --template '{date|date}\n'
206 206 Sun Jan 15 13:30:00 2006 +0500
207 207 Sun Jan 15 13:30:00 2006 -0800
208 208 Sat Jul 15 13:30:00 2006 +0500
209 209 Sat Jul 15 13:30:00 2006 -0700
210 210 Sun Jun 11 00:26:40 2006 -0400
211 211 Sat Apr 15 13:30:00 2006 +0200
212 212 Sat Apr 15 13:30:00 2006 +0000
213 213 Wed Feb 01 13:00:30 2006 -0500
214 214 Wed Feb 01 13:00:30 2006 +0000
215 215
216 216 $ hg log -d '> 02/01 ' --template '{date|date}\n'
217 217 $ hg log -d '< 02/01 ' --template '{date|date}\n'
218 218 Sun Jan 15 13:30:00 2006 +0500
219 219 Sun Jan 15 13:30:00 2006 -0800
220 220 Sat Jul 15 13:30:00 2006 +0500
221 221 Sat Jul 15 13:30:00 2006 -0700
222 222 Sun Jun 11 00:26:40 2006 -0400
223 223 Sat Apr 15 13:30:00 2006 +0200
224 224 Sat Apr 15 13:30:00 2006 +0000
225 225 Wed Feb 01 13:00:30 2006 -0500
226 226 Wed Feb 01 13:00:30 2006 +0000
227 227
228 228 $ hg log -d ' > 02/01 ' --template '{date|date}\n'
229 229 $ hg log -d ' < 02/01 ' --template '{date|date}\n'
230 230 Sun Jan 15 13:30:00 2006 +0500
231 231 Sun Jan 15 13:30:00 2006 -0800
232 232 Sat Jul 15 13:30:00 2006 +0500
233 233 Sat Jul 15 13:30:00 2006 -0700
234 234 Sun Jun 11 00:26:40 2006 -0400
235 235 Sat Apr 15 13:30:00 2006 +0200
236 236 Sat Apr 15 13:30:00 2006 +0000
237 237 Wed Feb 01 13:00:30 2006 -0500
238 238 Wed Feb 01 13:00:30 2006 +0000
239 239
240 240 Test issue 3764 (interpreting 'today' and 'yesterday')
241 241 $ echo "hello" >> a
242 242 >>> import datetime
243 243 >>> today = datetime.date.today().strftime("%b %d")
244 244 >>> yesterday = (datetime.date.today() - datetime.timedelta(days=1)).strftime("%b %d")
245 245 >>> dates = open('dates', 'w')
246 246 >>> dates.write(today + '\n')
247 247 >>> dates.write(yesterday + '\n')
248 248 >>> dates.close()
249 249 $ hg ci -d "`sed -n '1p' dates`" -m "today is a good day to code"
250 250 $ hg log -d today --template '{desc}\n'
251 251 today is a good day to code
252 252 $ echo "goodbye" >> a
253 253 $ hg ci -d "`sed -n '2p' dates`" -m "the time traveler's code"
254 254 $ hg log -d yesterday --template '{desc}\n'
255 255 the time traveler's code
256 256 $ echo "foo" >> a
257 257 $ hg commit -d now -m 'Explicitly committed now.'
258 258 $ hg log -d today --template '{desc}\n'
259 259 Explicitly committed now.
260 260 today is a good day to code
261 261
262 262 Test parsing various ISO8601 forms
263 263
264 264 $ hg debugdate "2016-07-27T12:10:21"
265 265 internal: 1469646621 * (glob)
266 266 standard: Wed Jul 27 12:10:21 2016 -0700
267 267 $ hg debugdate "2016-07-27T12:10:21Z"
268 268 internal: 1469621421 0
269 269 standard: Wed Jul 27 12:10:21 2016 +0000
270 270 $ hg debugdate "2016-07-27T12:10:21+00:00"
271 271 internal: 1469621421 0
272 272 standard: Wed Jul 27 12:10:21 2016 +0000
273 273 $ hg debugdate "2016-07-27T121021Z"
274 274 internal: 1469621421 0
275 275 standard: Wed Jul 27 12:10:21 2016 +0000
276 276
277 277 $ hg debugdate "2016-07-27 12:10:21"
278 278 internal: 1469646621 * (glob)
279 279 standard: Wed Jul 27 12:10:21 2016 -0700
280 280 $ hg debugdate "2016-07-27 12:10:21Z"
281 281 internal: 1469621421 0
282 282 standard: Wed Jul 27 12:10:21 2016 +0000
283 283 $ hg debugdate "2016-07-27 12:10:21+00:00"
284 284 internal: 1469621421 0
285 285 standard: Wed Jul 27 12:10:21 2016 +0000
286 286 $ hg debugdate "2016-07-27 121021Z"
287 287 internal: 1469621421 0
288 288 standard: Wed Jul 27 12:10:21 2016 +0000
@@ -1,3756 +1,3756 b''
1 1 $ HGENCODING=utf-8
2 2 $ export HGENCODING
3 3 $ cat > testrevset.py << EOF
4 4 > import mercurial.revset
5 5 >
6 6 > baseset = mercurial.revset.baseset
7 7 >
8 8 > def r3232(repo, subset, x):
9 9 > """"simple revset that return [3,2,3,2]
10 10 >
11 11 > revisions duplicated on purpose.
12 12 > """
13 13 > if 3 not in subset:
14 14 > if 2 in subset:
15 15 > return baseset([2,2])
16 16 > return baseset()
17 17 > return baseset([3,3,2,2])
18 18 >
19 19 > mercurial.revset.symbols['r3232'] = r3232
20 20 > EOF
21 21 $ cat >> $HGRCPATH << EOF
22 22 > [extensions]
23 23 > testrevset=$TESTTMP/testrevset.py
24 24 > EOF
25 25
26 26 $ try() {
27 27 > hg debugrevspec --debug "$@"
28 28 > }
29 29
30 30 $ log() {
31 31 > hg log --template '{rev}\n' -r "$1"
32 32 > }
33 33
34 34 extension to build '_intlist()' and '_hexlist()', which is necessary because
35 35 these predicates use '\0' as a separator:
36 36
37 37 $ cat <<EOF > debugrevlistspec.py
38 38 > from __future__ import absolute_import
39 39 > from mercurial import (
40 40 > node as nodemod,
41 41 > registrar,
42 42 > revset,
43 43 > revsetlang,
44 44 > smartset,
45 45 > )
46 46 > cmdtable = {}
47 47 > command = registrar.command(cmdtable)
48 48 > @command('debugrevlistspec',
49 49 > [('', 'optimize', None, 'print parsed tree after optimizing'),
50 50 > ('', 'bin', None, 'unhexlify arguments')])
51 51 > def debugrevlistspec(ui, repo, fmt, *args, **opts):
52 52 > if opts['bin']:
53 53 > args = map(nodemod.bin, args)
54 54 > expr = revsetlang.formatspec(fmt, list(args))
55 55 > if ui.verbose:
56 56 > tree = revsetlang.parse(expr, lookup=repo.__contains__)
57 57 > ui.note(revsetlang.prettyformat(tree), "\n")
58 58 > if opts["optimize"]:
59 59 > opttree = revsetlang.optimize(revsetlang.analyze(tree))
60 60 > ui.note("* optimized:\n", revsetlang.prettyformat(opttree),
61 61 > "\n")
62 62 > func = revset.match(ui, expr, repo)
63 63 > revs = func(repo)
64 64 > if ui.verbose:
65 65 > ui.note("* set:\n", smartset.prettyformat(revs), "\n")
66 66 > for c in revs:
67 67 > ui.write("%s\n" % c)
68 68 > EOF
69 69 $ cat <<EOF >> $HGRCPATH
70 70 > [extensions]
71 71 > debugrevlistspec = $TESTTMP/debugrevlistspec.py
72 72 > EOF
73 73 $ trylist() {
74 74 > hg debugrevlistspec --debug "$@"
75 75 > }
76 76
77 77 $ hg init repo
78 78 $ cd repo
79 79
80 80 $ echo a > a
81 81 $ hg branch a
82 82 marked working directory as branch a
83 83 (branches are permanent and global, did you want a bookmark?)
84 84 $ hg ci -Aqm0
85 85
86 86 $ echo b > b
87 87 $ hg branch b
88 88 marked working directory as branch b
89 89 $ hg ci -Aqm1
90 90
91 91 $ rm a
92 92 $ hg branch a-b-c-
93 93 marked working directory as branch a-b-c-
94 94 $ hg ci -Aqm2 -u Bob
95 95
96 96 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
97 97 2
98 98 $ hg log -r "extra('branch')" --template '{rev}\n'
99 99 0
100 100 1
101 101 2
102 102 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
103 103 0 a
104 104 2 a-b-c-
105 105
106 106 $ hg co 1
107 107 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 108 $ hg branch +a+b+c+
109 109 marked working directory as branch +a+b+c+
110 110 $ hg ci -Aqm3
111 111
112 112 $ hg co 2 # interleave
113 113 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
114 114 $ echo bb > b
115 115 $ hg branch -- -a-b-c-
116 116 marked working directory as branch -a-b-c-
117 117 $ hg ci -Aqm4 -d "May 12 2005"
118 118
119 119 $ hg co 3
120 120 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 121 $ hg branch !a/b/c/
122 122 marked working directory as branch !a/b/c/
123 123 $ hg ci -Aqm"5 bug"
124 124
125 125 $ hg merge 4
126 126 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
127 127 (branch merge, don't forget to commit)
128 128 $ hg branch _a_b_c_
129 129 marked working directory as branch _a_b_c_
130 130 $ hg ci -Aqm"6 issue619"
131 131
132 132 $ hg branch .a.b.c.
133 133 marked working directory as branch .a.b.c.
134 134 $ hg ci -Aqm7
135 135
136 136 $ hg branch all
137 137 marked working directory as branch all
138 138
139 139 $ hg co 4
140 140 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 141 $ hg branch Γ©
142 142 marked working directory as branch \xc3\xa9 (esc)
143 143 $ hg ci -Aqm9
144 144
145 145 $ hg tag -r6 1.0
146 146 $ hg bookmark -r6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
147 147
148 148 $ hg clone --quiet -U -r 7 . ../remote1
149 149 $ hg clone --quiet -U -r 8 . ../remote2
150 150 $ echo "[paths]" >> .hg/hgrc
151 151 $ echo "default = ../remote1" >> .hg/hgrc
152 152
153 153 trivial
154 154
155 155 $ try 0:1
156 156 (range
157 157 ('symbol', '0')
158 158 ('symbol', '1'))
159 159 * set:
160 160 <spanset+ 0:1>
161 161 0
162 162 1
163 163 $ try --optimize :
164 164 (rangeall
165 165 None)
166 166 * optimized:
167 167 (rangeall
168 168 None
169 169 define)
170 170 * set:
171 171 <spanset+ 0:9>
172 172 0
173 173 1
174 174 2
175 175 3
176 176 4
177 177 5
178 178 6
179 179 7
180 180 8
181 181 9
182 182 $ try 3::6
183 183 (dagrange
184 184 ('symbol', '3')
185 185 ('symbol', '6'))
186 186 * set:
187 187 <baseset+ [3, 5, 6]>
188 188 3
189 189 5
190 190 6
191 191 $ try '0|1|2'
192 192 (or
193 193 (list
194 194 ('symbol', '0')
195 195 ('symbol', '1')
196 196 ('symbol', '2')))
197 197 * set:
198 198 <baseset [0, 1, 2]>
199 199 0
200 200 1
201 201 2
202 202
203 203 names that should work without quoting
204 204
205 205 $ try a
206 206 ('symbol', 'a')
207 207 * set:
208 208 <baseset [0]>
209 209 0
210 210 $ try b-a
211 211 (minus
212 212 ('symbol', 'b')
213 213 ('symbol', 'a'))
214 214 * set:
215 215 <filteredset
216 216 <baseset [1]>,
217 217 <not
218 218 <baseset [0]>>>
219 219 1
220 220 $ try _a_b_c_
221 221 ('symbol', '_a_b_c_')
222 222 * set:
223 223 <baseset [6]>
224 224 6
225 225 $ try _a_b_c_-a
226 226 (minus
227 227 ('symbol', '_a_b_c_')
228 228 ('symbol', 'a'))
229 229 * set:
230 230 <filteredset
231 231 <baseset [6]>,
232 232 <not
233 233 <baseset [0]>>>
234 234 6
235 235 $ try .a.b.c.
236 236 ('symbol', '.a.b.c.')
237 237 * set:
238 238 <baseset [7]>
239 239 7
240 240 $ try .a.b.c.-a
241 241 (minus
242 242 ('symbol', '.a.b.c.')
243 243 ('symbol', 'a'))
244 244 * set:
245 245 <filteredset
246 246 <baseset [7]>,
247 247 <not
248 248 <baseset [0]>>>
249 249 7
250 250
251 251 names that should be caught by fallback mechanism
252 252
253 253 $ try -- '-a-b-c-'
254 254 ('symbol', '-a-b-c-')
255 255 * set:
256 256 <baseset [4]>
257 257 4
258 258 $ log -a-b-c-
259 259 4
260 260 $ try '+a+b+c+'
261 261 ('symbol', '+a+b+c+')
262 262 * set:
263 263 <baseset [3]>
264 264 3
265 265 $ try '+a+b+c+:'
266 266 (rangepost
267 267 ('symbol', '+a+b+c+'))
268 268 * set:
269 269 <spanset+ 3:9>
270 270 3
271 271 4
272 272 5
273 273 6
274 274 7
275 275 8
276 276 9
277 277 $ try ':+a+b+c+'
278 278 (rangepre
279 279 ('symbol', '+a+b+c+'))
280 280 * set:
281 281 <spanset+ 0:3>
282 282 0
283 283 1
284 284 2
285 285 3
286 286 $ try -- '-a-b-c-:+a+b+c+'
287 287 (range
288 288 ('symbol', '-a-b-c-')
289 289 ('symbol', '+a+b+c+'))
290 290 * set:
291 291 <spanset- 3:4>
292 292 4
293 293 3
294 294 $ log '-a-b-c-:+a+b+c+'
295 295 4
296 296 3
297 297
298 298 $ try -- -a-b-c--a # complains
299 299 (minus
300 300 (minus
301 301 (minus
302 302 (negate
303 303 ('symbol', 'a'))
304 304 ('symbol', 'b'))
305 305 ('symbol', 'c'))
306 306 (negate
307 307 ('symbol', 'a')))
308 308 abort: unknown revision '-a'!
309 309 [255]
310 310 $ try Γ©
311 311 ('symbol', '\xc3\xa9')
312 312 * set:
313 313 <baseset [9]>
314 314 9
315 315
316 316 no quoting needed
317 317
318 318 $ log ::a-b-c-
319 319 0
320 320 1
321 321 2
322 322
323 323 quoting needed
324 324
325 325 $ try '"-a-b-c-"-a'
326 326 (minus
327 327 ('string', '-a-b-c-')
328 328 ('symbol', 'a'))
329 329 * set:
330 330 <filteredset
331 331 <baseset [4]>,
332 332 <not
333 333 <baseset [0]>>>
334 334 4
335 335
336 336 $ log '1 or 2'
337 337 1
338 338 2
339 339 $ log '1|2'
340 340 1
341 341 2
342 342 $ log '1 and 2'
343 343 $ log '1&2'
344 344 $ try '1&2|3' # precedence - and is higher
345 345 (or
346 346 (list
347 347 (and
348 348 ('symbol', '1')
349 349 ('symbol', '2'))
350 350 ('symbol', '3')))
351 351 * set:
352 352 <addset
353 353 <baseset []>,
354 354 <baseset [3]>>
355 355 3
356 356 $ try '1|2&3'
357 357 (or
358 358 (list
359 359 ('symbol', '1')
360 360 (and
361 361 ('symbol', '2')
362 362 ('symbol', '3'))))
363 363 * set:
364 364 <addset
365 365 <baseset [1]>,
366 366 <baseset []>>
367 367 1
368 368 $ try '1&2&3' # associativity
369 369 (and
370 370 (and
371 371 ('symbol', '1')
372 372 ('symbol', '2'))
373 373 ('symbol', '3'))
374 374 * set:
375 375 <baseset []>
376 376 $ try '1|(2|3)'
377 377 (or
378 378 (list
379 379 ('symbol', '1')
380 380 (group
381 381 (or
382 382 (list
383 383 ('symbol', '2')
384 384 ('symbol', '3'))))))
385 385 * set:
386 386 <addset
387 387 <baseset [1]>,
388 388 <baseset [2, 3]>>
389 389 1
390 390 2
391 391 3
392 392 $ log '1.0' # tag
393 393 6
394 394 $ log 'a' # branch
395 395 0
396 396 $ log '2785f51ee'
397 397 0
398 398 $ log 'date(2005)'
399 399 4
400 400 $ log 'date(this is a test)'
401 401 hg: parse error at 10: unexpected token: symbol
402 402 [255]
403 403 $ log 'date()'
404 404 hg: parse error: date requires a string
405 405 [255]
406 406 $ log 'date'
407 407 abort: unknown revision 'date'!
408 408 [255]
409 409 $ log 'date('
410 410 hg: parse error at 5: not a prefix: end
411 411 [255]
412 412 $ log 'date("\xy")'
413 413 hg: parse error: invalid \x escape
414 414 [255]
415 415 $ log 'date(tip)'
416 abort: invalid date: 'tip'
416 hg: parse error: invalid date: 'tip'
417 417 [255]
418 418 $ log '0:date'
419 419 abort: unknown revision 'date'!
420 420 [255]
421 421 $ log '::"date"'
422 422 abort: unknown revision 'date'!
423 423 [255]
424 424 $ hg book date -r 4
425 425 $ log '0:date'
426 426 0
427 427 1
428 428 2
429 429 3
430 430 4
431 431 $ log '::date'
432 432 0
433 433 1
434 434 2
435 435 4
436 436 $ log '::"date"'
437 437 0
438 438 1
439 439 2
440 440 4
441 441 $ log 'date(2005) and 1::'
442 442 4
443 443 $ hg book -d date
444 444
445 445 function name should be a symbol
446 446
447 447 $ log '"date"(2005)'
448 448 hg: parse error: not a symbol
449 449 [255]
450 450
451 451 keyword arguments
452 452
453 453 $ log 'extra(branch, value=a)'
454 454 0
455 455
456 456 $ log 'extra(branch, a, b)'
457 457 hg: parse error: extra takes at most 2 positional arguments
458 458 [255]
459 459 $ log 'extra(a, label=b)'
460 460 hg: parse error: extra got multiple values for keyword argument 'label'
461 461 [255]
462 462 $ log 'extra(label=branch, default)'
463 463 hg: parse error: extra got an invalid argument
464 464 [255]
465 465 $ log 'extra(branch, foo+bar=baz)'
466 466 hg: parse error: extra got an invalid argument
467 467 [255]
468 468 $ log 'extra(unknown=branch)'
469 469 hg: parse error: extra got an unexpected keyword argument 'unknown'
470 470 [255]
471 471
472 472 $ try 'foo=bar|baz'
473 473 (keyvalue
474 474 ('symbol', 'foo')
475 475 (or
476 476 (list
477 477 ('symbol', 'bar')
478 478 ('symbol', 'baz'))))
479 479 hg: parse error: can't use a key-value pair in this context
480 480 [255]
481 481
482 482 right-hand side should be optimized recursively
483 483
484 484 $ try --optimize 'foo=(not public())'
485 485 (keyvalue
486 486 ('symbol', 'foo')
487 487 (group
488 488 (not
489 489 (func
490 490 ('symbol', 'public')
491 491 None))))
492 492 * optimized:
493 493 (keyvalue
494 494 ('symbol', 'foo')
495 495 (func
496 496 ('symbol', '_notpublic')
497 497 None
498 498 any))
499 499 hg: parse error: can't use a key-value pair in this context
500 500 [255]
501 501
502 502 parsed tree at stages:
503 503
504 504 $ hg debugrevspec -p all '()'
505 505 * parsed:
506 506 (group
507 507 None)
508 508 * expanded:
509 509 (group
510 510 None)
511 511 * concatenated:
512 512 (group
513 513 None)
514 514 * analyzed:
515 515 None
516 516 * optimized:
517 517 None
518 518 hg: parse error: missing argument
519 519 [255]
520 520
521 521 $ hg debugrevspec --no-optimized -p all '()'
522 522 * parsed:
523 523 (group
524 524 None)
525 525 * expanded:
526 526 (group
527 527 None)
528 528 * concatenated:
529 529 (group
530 530 None)
531 531 * analyzed:
532 532 None
533 533 hg: parse error: missing argument
534 534 [255]
535 535
536 536 $ hg debugrevspec -p parsed -p analyzed -p optimized '(0|1)-1'
537 537 * parsed:
538 538 (minus
539 539 (group
540 540 (or
541 541 (list
542 542 ('symbol', '0')
543 543 ('symbol', '1'))))
544 544 ('symbol', '1'))
545 545 * analyzed:
546 546 (and
547 547 (or
548 548 (list
549 549 ('symbol', '0')
550 550 ('symbol', '1'))
551 551 define)
552 552 (not
553 553 ('symbol', '1')
554 554 follow)
555 555 define)
556 556 * optimized:
557 557 (difference
558 558 (func
559 559 ('symbol', '_list')
560 560 ('string', '0\x001')
561 561 define)
562 562 ('symbol', '1')
563 563 define)
564 564 0
565 565
566 566 $ hg debugrevspec -p unknown '0'
567 567 abort: invalid stage name: unknown
568 568 [255]
569 569
570 570 $ hg debugrevspec -p all --optimize '0'
571 571 abort: cannot use --optimize with --show-stage
572 572 [255]
573 573
574 574 verify optimized tree:
575 575
576 576 $ hg debugrevspec --verify '0|1'
577 577
578 578 $ hg debugrevspec --verify -v -p analyzed -p optimized 'r3232() & 2'
579 579 * analyzed:
580 580 (and
581 581 (func
582 582 ('symbol', 'r3232')
583 583 None
584 584 define)
585 585 ('symbol', '2')
586 586 define)
587 587 * optimized:
588 588 (and
589 589 ('symbol', '2')
590 590 (func
591 591 ('symbol', 'r3232')
592 592 None
593 593 define)
594 594 define)
595 595 * analyzed set:
596 596 <baseset [2]>
597 597 * optimized set:
598 598 <baseset [2, 2]>
599 599 --- analyzed
600 600 +++ optimized
601 601 2
602 602 +2
603 603 [1]
604 604
605 605 $ hg debugrevspec --no-optimized --verify-optimized '0'
606 606 abort: cannot use --verify-optimized with --no-optimized
607 607 [255]
608 608
609 609 Test that symbols only get parsed as functions if there's an opening
610 610 parenthesis.
611 611
612 612 $ hg book only -r 9
613 613 $ log 'only(only)' # Outer "only" is a function, inner "only" is the bookmark
614 614 8
615 615 9
616 616
617 617 ':y' behaves like '0:y', but can't be rewritten as such since the revision '0'
618 618 may be hidden (issue5385)
619 619
620 620 $ try -p parsed -p analyzed ':'
621 621 * parsed:
622 622 (rangeall
623 623 None)
624 624 * analyzed:
625 625 (rangeall
626 626 None
627 627 define)
628 628 * set:
629 629 <spanset+ 0:9>
630 630 0
631 631 1
632 632 2
633 633 3
634 634 4
635 635 5
636 636 6
637 637 7
638 638 8
639 639 9
640 640 $ try -p analyzed ':1'
641 641 * analyzed:
642 642 (rangepre
643 643 ('symbol', '1')
644 644 define)
645 645 * set:
646 646 <spanset+ 0:1>
647 647 0
648 648 1
649 649 $ try -p analyzed ':(1|2)'
650 650 * analyzed:
651 651 (rangepre
652 652 (or
653 653 (list
654 654 ('symbol', '1')
655 655 ('symbol', '2'))
656 656 define)
657 657 define)
658 658 * set:
659 659 <spanset+ 0:2>
660 660 0
661 661 1
662 662 2
663 663 $ try -p analyzed ':(1&2)'
664 664 * analyzed:
665 665 (rangepre
666 666 (and
667 667 ('symbol', '1')
668 668 ('symbol', '2')
669 669 define)
670 670 define)
671 671 * set:
672 672 <baseset []>
673 673
674 674 infix/suffix resolution of ^ operator (issue2884):
675 675
676 676 x^:y means (x^):y
677 677
678 678 $ try '1^:2'
679 679 (range
680 680 (parentpost
681 681 ('symbol', '1'))
682 682 ('symbol', '2'))
683 683 * set:
684 684 <spanset+ 0:2>
685 685 0
686 686 1
687 687 2
688 688
689 689 $ try '1^::2'
690 690 (dagrange
691 691 (parentpost
692 692 ('symbol', '1'))
693 693 ('symbol', '2'))
694 694 * set:
695 695 <baseset+ [0, 1, 2]>
696 696 0
697 697 1
698 698 2
699 699
700 700 $ try '9^:'
701 701 (rangepost
702 702 (parentpost
703 703 ('symbol', '9')))
704 704 * set:
705 705 <spanset+ 8:9>
706 706 8
707 707 9
708 708
709 709 x^:y should be resolved before omitting group operators
710 710
711 711 $ try '1^(:2)'
712 712 (parent
713 713 ('symbol', '1')
714 714 (group
715 715 (rangepre
716 716 ('symbol', '2'))))
717 717 hg: parse error: ^ expects a number 0, 1, or 2
718 718 [255]
719 719
720 720 x^:y should be resolved recursively
721 721
722 722 $ try 'sort(1^:2)'
723 723 (func
724 724 ('symbol', 'sort')
725 725 (range
726 726 (parentpost
727 727 ('symbol', '1'))
728 728 ('symbol', '2')))
729 729 * set:
730 730 <spanset+ 0:2>
731 731 0
732 732 1
733 733 2
734 734
735 735 $ try '(3^:4)^:2'
736 736 (range
737 737 (parentpost
738 738 (group
739 739 (range
740 740 (parentpost
741 741 ('symbol', '3'))
742 742 ('symbol', '4'))))
743 743 ('symbol', '2'))
744 744 * set:
745 745 <spanset+ 0:2>
746 746 0
747 747 1
748 748 2
749 749
750 750 $ try '(3^::4)^::2'
751 751 (dagrange
752 752 (parentpost
753 753 (group
754 754 (dagrange
755 755 (parentpost
756 756 ('symbol', '3'))
757 757 ('symbol', '4'))))
758 758 ('symbol', '2'))
759 759 * set:
760 760 <baseset+ [0, 1, 2]>
761 761 0
762 762 1
763 763 2
764 764
765 765 $ try '(9^:)^:'
766 766 (rangepost
767 767 (parentpost
768 768 (group
769 769 (rangepost
770 770 (parentpost
771 771 ('symbol', '9'))))))
772 772 * set:
773 773 <spanset+ 4:9>
774 774 4
775 775 5
776 776 6
777 777 7
778 778 8
779 779 9
780 780
781 781 x^ in alias should also be resolved
782 782
783 783 $ try 'A' --config 'revsetalias.A=1^:2'
784 784 ('symbol', 'A')
785 785 * expanded:
786 786 (range
787 787 (parentpost
788 788 ('symbol', '1'))
789 789 ('symbol', '2'))
790 790 * set:
791 791 <spanset+ 0:2>
792 792 0
793 793 1
794 794 2
795 795
796 796 $ try 'A:2' --config 'revsetalias.A=1^'
797 797 (range
798 798 ('symbol', 'A')
799 799 ('symbol', '2'))
800 800 * expanded:
801 801 (range
802 802 (parentpost
803 803 ('symbol', '1'))
804 804 ('symbol', '2'))
805 805 * set:
806 806 <spanset+ 0:2>
807 807 0
808 808 1
809 809 2
810 810
811 811 but not beyond the boundary of alias expansion, because the resolution should
812 812 be made at the parsing stage
813 813
814 814 $ try '1^A' --config 'revsetalias.A=:2'
815 815 (parent
816 816 ('symbol', '1')
817 817 ('symbol', 'A'))
818 818 * expanded:
819 819 (parent
820 820 ('symbol', '1')
821 821 (rangepre
822 822 ('symbol', '2')))
823 823 hg: parse error: ^ expects a number 0, 1, or 2
824 824 [255]
825 825
826 826 ancestor can accept 0 or more arguments
827 827
828 828 $ log 'ancestor()'
829 829 $ log 'ancestor(1)'
830 830 1
831 831 $ log 'ancestor(4,5)'
832 832 1
833 833 $ log 'ancestor(4,5) and 4'
834 834 $ log 'ancestor(0,0,1,3)'
835 835 0
836 836 $ log 'ancestor(3,1,5,3,5,1)'
837 837 1
838 838 $ log 'ancestor(0,1,3,5)'
839 839 0
840 840 $ log 'ancestor(1,2,3,4,5)'
841 841 1
842 842
843 843 test ancestors
844 844
845 845 $ log 'ancestors(5)'
846 846 0
847 847 1
848 848 3
849 849 5
850 850 $ log 'ancestor(ancestors(5))'
851 851 0
852 852 $ log '::r3232()'
853 853 0
854 854 1
855 855 2
856 856 3
857 857
858 858 $ log 'author(bob)'
859 859 2
860 860 $ log 'author("re:bob|test")'
861 861 0
862 862 1
863 863 2
864 864 3
865 865 4
866 866 5
867 867 6
868 868 7
869 869 8
870 870 9
871 871 $ log 'author(r"re:\S")'
872 872 0
873 873 1
874 874 2
875 875 3
876 876 4
877 877 5
878 878 6
879 879 7
880 880 8
881 881 9
882 882 $ log 'branch(Γ©)'
883 883 8
884 884 9
885 885 $ log 'branch(a)'
886 886 0
887 887 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
888 888 0 a
889 889 2 a-b-c-
890 890 3 +a+b+c+
891 891 4 -a-b-c-
892 892 5 !a/b/c/
893 893 6 _a_b_c_
894 894 7 .a.b.c.
895 895 $ log 'children(ancestor(4,5))'
896 896 2
897 897 3
898 898
899 899 $ log 'children(4)'
900 900 6
901 901 8
902 902 $ log 'children(null)'
903 903 0
904 904
905 905 $ log 'closed()'
906 906 $ log 'contains(a)'
907 907 0
908 908 1
909 909 3
910 910 5
911 911 $ log 'contains("../repo/a")'
912 912 0
913 913 1
914 914 3
915 915 5
916 916 $ log 'desc(B)'
917 917 5
918 918 $ hg log -r 'desc(r"re:S?u")' --template "{rev} {desc|firstline}\n"
919 919 5 5 bug
920 920 6 6 issue619
921 921 $ log 'descendants(2 or 3)'
922 922 2
923 923 3
924 924 4
925 925 5
926 926 6
927 927 7
928 928 8
929 929 9
930 930 $ log 'file("b*")'
931 931 1
932 932 4
933 933 $ log 'filelog("b")'
934 934 1
935 935 4
936 936 $ log 'filelog("../repo/b")'
937 937 1
938 938 4
939 939 $ log 'follow()'
940 940 0
941 941 1
942 942 2
943 943 4
944 944 8
945 945 9
946 946 $ log 'grep("issue\d+")'
947 947 6
948 948 $ try 'grep("(")' # invalid regular expression
949 949 (func
950 950 ('symbol', 'grep')
951 951 ('string', '('))
952 952 hg: parse error: invalid match pattern: unbalanced parenthesis
953 953 [255]
954 954 $ try 'grep("\bissue\d+")'
955 955 (func
956 956 ('symbol', 'grep')
957 957 ('string', '\x08issue\\d+'))
958 958 * set:
959 959 <filteredset
960 960 <fullreposet+ 0:9>,
961 961 <grep '\x08issue\\d+'>>
962 962 $ try 'grep(r"\bissue\d+")'
963 963 (func
964 964 ('symbol', 'grep')
965 965 ('string', '\\bissue\\d+'))
966 966 * set:
967 967 <filteredset
968 968 <fullreposet+ 0:9>,
969 969 <grep '\\bissue\\d+'>>
970 970 6
971 971 $ try 'grep(r"\")'
972 972 hg: parse error at 7: unterminated string
973 973 [255]
974 974 $ log 'head()'
975 975 0
976 976 1
977 977 2
978 978 3
979 979 4
980 980 5
981 981 6
982 982 7
983 983 9
984 984 $ log 'heads(6::)'
985 985 7
986 986 $ log 'keyword(issue)'
987 987 6
988 988 $ log 'keyword("test a")'
989 989 $ log 'limit(head(), 1)'
990 990 0
991 991 $ log 'limit(author("re:bob|test"), 3, 5)'
992 992 5
993 993 6
994 994 7
995 995 $ log 'limit(author("re:bob|test"), offset=6)'
996 996 6
997 997 $ log 'limit(author("re:bob|test"), offset=10)'
998 998 $ log 'limit(all(), 1, -1)'
999 999 hg: parse error: negative offset
1000 1000 [255]
1001 1001 $ log 'matching(6)'
1002 1002 6
1003 1003 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
1004 1004 6
1005 1005 7
1006 1006
1007 1007 Testing min and max
1008 1008
1009 1009 max: simple
1010 1010
1011 1011 $ log 'max(contains(a))'
1012 1012 5
1013 1013
1014 1014 max: simple on unordered set)
1015 1015
1016 1016 $ log 'max((4+0+2+5+7) and contains(a))'
1017 1017 5
1018 1018
1019 1019 max: no result
1020 1020
1021 1021 $ log 'max(contains(stringthatdoesnotappearanywhere))'
1022 1022
1023 1023 max: no result on unordered set
1024 1024
1025 1025 $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1026 1026
1027 1027 min: simple
1028 1028
1029 1029 $ log 'min(contains(a))'
1030 1030 0
1031 1031
1032 1032 min: simple on unordered set
1033 1033
1034 1034 $ log 'min((4+0+2+5+7) and contains(a))'
1035 1035 0
1036 1036
1037 1037 min: empty
1038 1038
1039 1039 $ log 'min(contains(stringthatdoesnotappearanywhere))'
1040 1040
1041 1041 min: empty on unordered set
1042 1042
1043 1043 $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1044 1044
1045 1045
1046 1046 $ log 'merge()'
1047 1047 6
1048 1048 $ log 'branchpoint()'
1049 1049 1
1050 1050 4
1051 1051 $ log 'modifies(b)'
1052 1052 4
1053 1053 $ log 'modifies("path:b")'
1054 1054 4
1055 1055 $ log 'modifies("*")'
1056 1056 4
1057 1057 6
1058 1058 $ log 'modifies("set:modified()")'
1059 1059 4
1060 1060 $ log 'id(5)'
1061 1061 2
1062 1062 $ log 'only(9)'
1063 1063 8
1064 1064 9
1065 1065 $ log 'only(8)'
1066 1066 8
1067 1067 $ log 'only(9, 5)'
1068 1068 2
1069 1069 4
1070 1070 8
1071 1071 9
1072 1072 $ log 'only(7 + 9, 5 + 2)'
1073 1073 4
1074 1074 6
1075 1075 7
1076 1076 8
1077 1077 9
1078 1078
1079 1079 Test empty set input
1080 1080 $ log 'only(p2())'
1081 1081 $ log 'only(p1(), p2())'
1082 1082 0
1083 1083 1
1084 1084 2
1085 1085 4
1086 1086 8
1087 1087 9
1088 1088
1089 1089 Test '%' operator
1090 1090
1091 1091 $ log '9%'
1092 1092 8
1093 1093 9
1094 1094 $ log '9%5'
1095 1095 2
1096 1096 4
1097 1097 8
1098 1098 9
1099 1099 $ log '(7 + 9)%(5 + 2)'
1100 1100 4
1101 1101 6
1102 1102 7
1103 1103 8
1104 1104 9
1105 1105
1106 1106 Test operand of '%' is optimized recursively (issue4670)
1107 1107
1108 1108 $ try --optimize '8:9-8%'
1109 1109 (onlypost
1110 1110 (minus
1111 1111 (range
1112 1112 ('symbol', '8')
1113 1113 ('symbol', '9'))
1114 1114 ('symbol', '8')))
1115 1115 * optimized:
1116 1116 (func
1117 1117 ('symbol', 'only')
1118 1118 (difference
1119 1119 (range
1120 1120 ('symbol', '8')
1121 1121 ('symbol', '9')
1122 1122 define)
1123 1123 ('symbol', '8')
1124 1124 define)
1125 1125 define)
1126 1126 * set:
1127 1127 <baseset+ [8, 9]>
1128 1128 8
1129 1129 9
1130 1130 $ try --optimize '(9)%(5)'
1131 1131 (only
1132 1132 (group
1133 1133 ('symbol', '9'))
1134 1134 (group
1135 1135 ('symbol', '5')))
1136 1136 * optimized:
1137 1137 (func
1138 1138 ('symbol', 'only')
1139 1139 (list
1140 1140 ('symbol', '9')
1141 1141 ('symbol', '5'))
1142 1142 define)
1143 1143 * set:
1144 1144 <baseset+ [2, 4, 8, 9]>
1145 1145 2
1146 1146 4
1147 1147 8
1148 1148 9
1149 1149
1150 1150 Test the order of operations
1151 1151
1152 1152 $ log '7 + 9%5 + 2'
1153 1153 7
1154 1154 2
1155 1155 4
1156 1156 8
1157 1157 9
1158 1158
1159 1159 Test explicit numeric revision
1160 1160 $ log 'rev(-2)'
1161 1161 $ log 'rev(-1)'
1162 1162 -1
1163 1163 $ log 'rev(0)'
1164 1164 0
1165 1165 $ log 'rev(9)'
1166 1166 9
1167 1167 $ log 'rev(10)'
1168 1168 $ log 'rev(tip)'
1169 1169 hg: parse error: rev expects a number
1170 1170 [255]
1171 1171
1172 1172 Test hexadecimal revision
1173 1173 $ log 'id(2)'
1174 1174 abort: 00changelog.i@2: ambiguous identifier!
1175 1175 [255]
1176 1176 $ log 'id(23268)'
1177 1177 4
1178 1178 $ log 'id(2785f51eece)'
1179 1179 0
1180 1180 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532c)'
1181 1181 8
1182 1182 $ log 'id(d5d0dcbdc4a)'
1183 1183 $ log 'id(d5d0dcbdc4w)'
1184 1184 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532d)'
1185 1185 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532q)'
1186 1186 $ log 'id(1.0)'
1187 1187 $ log 'id(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)'
1188 1188
1189 1189 Test null revision
1190 1190 $ log '(null)'
1191 1191 -1
1192 1192 $ log '(null:0)'
1193 1193 -1
1194 1194 0
1195 1195 $ log '(0:null)'
1196 1196 0
1197 1197 -1
1198 1198 $ log 'null::0'
1199 1199 -1
1200 1200 0
1201 1201 $ log 'null:tip - 0:'
1202 1202 -1
1203 1203 $ log 'null: and null::' | head -1
1204 1204 -1
1205 1205 $ log 'null: or 0:' | head -2
1206 1206 -1
1207 1207 0
1208 1208 $ log 'ancestors(null)'
1209 1209 -1
1210 1210 $ log 'reverse(null:)' | tail -2
1211 1211 0
1212 1212 -1
1213 1213 BROKEN: should be '-1'
1214 1214 $ log 'first(null:)'
1215 1215 BROKEN: should be '-1'
1216 1216 $ log 'min(null:)'
1217 1217 $ log 'tip:null and all()' | tail -2
1218 1218 1
1219 1219 0
1220 1220
1221 1221 Test working-directory revision
1222 1222 $ hg debugrevspec 'wdir()'
1223 1223 2147483647
1224 1224 $ hg debugrevspec 'wdir()^'
1225 1225 9
1226 1226 $ hg up 7
1227 1227 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1228 1228 $ hg debugrevspec 'wdir()^'
1229 1229 7
1230 1230 $ hg debugrevspec 'wdir()^0'
1231 1231 2147483647
1232 1232 $ hg debugrevspec 'wdir()~3'
1233 1233 5
1234 1234 $ hg debugrevspec 'ancestors(wdir())'
1235 1235 0
1236 1236 1
1237 1237 2
1238 1238 3
1239 1239 4
1240 1240 5
1241 1241 6
1242 1242 7
1243 1243 2147483647
1244 1244 $ hg debugrevspec 'wdir()~0'
1245 1245 2147483647
1246 1246 $ hg debugrevspec 'p1(wdir())'
1247 1247 7
1248 1248 $ hg debugrevspec 'p2(wdir())'
1249 1249 $ hg debugrevspec 'parents(wdir())'
1250 1250 7
1251 1251 $ hg debugrevspec 'wdir()^1'
1252 1252 7
1253 1253 $ hg debugrevspec 'wdir()^2'
1254 1254 $ hg debugrevspec 'wdir()^3'
1255 1255 hg: parse error: ^ expects a number 0, 1, or 2
1256 1256 [255]
1257 1257 For tests consistency
1258 1258 $ hg up 9
1259 1259 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1260 1260 $ hg debugrevspec 'tip or wdir()'
1261 1261 9
1262 1262 2147483647
1263 1263 $ hg debugrevspec '0:tip and wdir()'
1264 1264 $ log '0:wdir()' | tail -3
1265 1265 8
1266 1266 9
1267 1267 2147483647
1268 1268 $ log 'wdir():0' | head -3
1269 1269 2147483647
1270 1270 9
1271 1271 8
1272 1272 $ log 'wdir():wdir()'
1273 1273 2147483647
1274 1274 $ log '(all() + wdir()) & min(. + wdir())'
1275 1275 9
1276 1276 $ log '(all() + wdir()) & max(. + wdir())'
1277 1277 2147483647
1278 1278 $ log '(all() + wdir()) & first(wdir() + .)'
1279 1279 2147483647
1280 1280 $ log '(all() + wdir()) & last(. + wdir())'
1281 1281 2147483647
1282 1282
1283 1283 $ log 'outgoing()'
1284 1284 8
1285 1285 9
1286 1286 $ log 'outgoing("../remote1")'
1287 1287 8
1288 1288 9
1289 1289 $ log 'outgoing("../remote2")'
1290 1290 3
1291 1291 5
1292 1292 6
1293 1293 7
1294 1294 9
1295 1295 $ log 'p1(merge())'
1296 1296 5
1297 1297 $ log 'p2(merge())'
1298 1298 4
1299 1299 $ log 'parents(merge())'
1300 1300 4
1301 1301 5
1302 1302 $ log 'p1(branchpoint())'
1303 1303 0
1304 1304 2
1305 1305 $ log 'p2(branchpoint())'
1306 1306 $ log 'parents(branchpoint())'
1307 1307 0
1308 1308 2
1309 1309 $ log 'removes(a)'
1310 1310 2
1311 1311 6
1312 1312 $ log 'roots(all())'
1313 1313 0
1314 1314 $ log 'reverse(2 or 3 or 4 or 5)'
1315 1315 5
1316 1316 4
1317 1317 3
1318 1318 2
1319 1319 $ log 'reverse(all())'
1320 1320 9
1321 1321 8
1322 1322 7
1323 1323 6
1324 1324 5
1325 1325 4
1326 1326 3
1327 1327 2
1328 1328 1
1329 1329 0
1330 1330 $ log 'reverse(all()) & filelog(b)'
1331 1331 4
1332 1332 1
1333 1333 $ log 'rev(5)'
1334 1334 5
1335 1335 $ log 'sort(limit(reverse(all()), 3))'
1336 1336 7
1337 1337 8
1338 1338 9
1339 1339 $ log 'sort(2 or 3 or 4 or 5, date)'
1340 1340 2
1341 1341 3
1342 1342 5
1343 1343 4
1344 1344 $ log 'tagged()'
1345 1345 6
1346 1346 $ log 'tag()'
1347 1347 6
1348 1348 $ log 'tag(1.0)'
1349 1349 6
1350 1350 $ log 'tag(tip)'
1351 1351 9
1352 1352
1353 1353 Test order of revisions in compound expression
1354 1354 ----------------------------------------------
1355 1355
1356 1356 The general rule is that only the outermost (= leftmost) predicate can
1357 1357 enforce its ordering requirement. The other predicates should take the
1358 1358 ordering defined by it.
1359 1359
1360 1360 'A & B' should follow the order of 'A':
1361 1361
1362 1362 $ log '2:0 & 0::2'
1363 1363 2
1364 1364 1
1365 1365 0
1366 1366
1367 1367 'head()' combines sets in right order:
1368 1368
1369 1369 $ log '2:0 & head()'
1370 1370 2
1371 1371 1
1372 1372 0
1373 1373
1374 1374 'x:y' takes ordering parameter into account:
1375 1375
1376 1376 $ try -p optimized '3:0 & 0:3 & not 2:1'
1377 1377 * optimized:
1378 1378 (difference
1379 1379 (and
1380 1380 (range
1381 1381 ('symbol', '3')
1382 1382 ('symbol', '0')
1383 1383 define)
1384 1384 (range
1385 1385 ('symbol', '0')
1386 1386 ('symbol', '3')
1387 1387 follow)
1388 1388 define)
1389 1389 (range
1390 1390 ('symbol', '2')
1391 1391 ('symbol', '1')
1392 1392 any)
1393 1393 define)
1394 1394 * set:
1395 1395 <filteredset
1396 1396 <filteredset
1397 1397 <spanset- 0:3>,
1398 1398 <spanset+ 0:3>>,
1399 1399 <not
1400 1400 <spanset+ 1:2>>>
1401 1401 3
1402 1402 0
1403 1403
1404 1404 'a + b', which is optimized to '_list(a b)', should take the ordering of
1405 1405 the left expression:
1406 1406
1407 1407 $ try --optimize '2:0 & (0 + 1 + 2)'
1408 1408 (and
1409 1409 (range
1410 1410 ('symbol', '2')
1411 1411 ('symbol', '0'))
1412 1412 (group
1413 1413 (or
1414 1414 (list
1415 1415 ('symbol', '0')
1416 1416 ('symbol', '1')
1417 1417 ('symbol', '2')))))
1418 1418 * optimized:
1419 1419 (and
1420 1420 (range
1421 1421 ('symbol', '2')
1422 1422 ('symbol', '0')
1423 1423 define)
1424 1424 (func
1425 1425 ('symbol', '_list')
1426 1426 ('string', '0\x001\x002')
1427 1427 follow)
1428 1428 define)
1429 1429 * set:
1430 1430 <filteredset
1431 1431 <spanset- 0:2>,
1432 1432 <baseset [0, 1, 2]>>
1433 1433 2
1434 1434 1
1435 1435 0
1436 1436
1437 1437 'A + B' should take the ordering of the left expression:
1438 1438
1439 1439 $ try --optimize '2:0 & (0:1 + 2)'
1440 1440 (and
1441 1441 (range
1442 1442 ('symbol', '2')
1443 1443 ('symbol', '0'))
1444 1444 (group
1445 1445 (or
1446 1446 (list
1447 1447 (range
1448 1448 ('symbol', '0')
1449 1449 ('symbol', '1'))
1450 1450 ('symbol', '2')))))
1451 1451 * optimized:
1452 1452 (and
1453 1453 (range
1454 1454 ('symbol', '2')
1455 1455 ('symbol', '0')
1456 1456 define)
1457 1457 (or
1458 1458 (list
1459 1459 ('symbol', '2')
1460 1460 (range
1461 1461 ('symbol', '0')
1462 1462 ('symbol', '1')
1463 1463 follow))
1464 1464 follow)
1465 1465 define)
1466 1466 * set:
1467 1467 <filteredset
1468 1468 <spanset- 0:2>,
1469 1469 <addset
1470 1470 <baseset [2]>,
1471 1471 <spanset+ 0:1>>>
1472 1472 2
1473 1473 1
1474 1474 0
1475 1475
1476 1476 '_intlist(a b)' should behave like 'a + b':
1477 1477
1478 1478 $ trylist --optimize '2:0 & %ld' 0 1 2
1479 1479 (and
1480 1480 (range
1481 1481 ('symbol', '2')
1482 1482 ('symbol', '0'))
1483 1483 (func
1484 1484 ('symbol', '_intlist')
1485 1485 ('string', '0\x001\x002')))
1486 1486 * optimized:
1487 1487 (and
1488 1488 (func
1489 1489 ('symbol', '_intlist')
1490 1490 ('string', '0\x001\x002')
1491 1491 follow)
1492 1492 (range
1493 1493 ('symbol', '2')
1494 1494 ('symbol', '0')
1495 1495 define)
1496 1496 define)
1497 1497 * set:
1498 1498 <filteredset
1499 1499 <spanset- 0:2>,
1500 1500 <baseset+ [0, 1, 2]>>
1501 1501 2
1502 1502 1
1503 1503 0
1504 1504
1505 1505 $ trylist --optimize '%ld & 2:0' 0 2 1
1506 1506 (and
1507 1507 (func
1508 1508 ('symbol', '_intlist')
1509 1509 ('string', '0\x002\x001'))
1510 1510 (range
1511 1511 ('symbol', '2')
1512 1512 ('symbol', '0')))
1513 1513 * optimized:
1514 1514 (and
1515 1515 (func
1516 1516 ('symbol', '_intlist')
1517 1517 ('string', '0\x002\x001')
1518 1518 define)
1519 1519 (range
1520 1520 ('symbol', '2')
1521 1521 ('symbol', '0')
1522 1522 follow)
1523 1523 define)
1524 1524 * set:
1525 1525 <filteredset
1526 1526 <baseset [0, 2, 1]>,
1527 1527 <spanset- 0:2>>
1528 1528 0
1529 1529 2
1530 1530 1
1531 1531
1532 1532 '_hexlist(a b)' should behave like 'a + b':
1533 1533
1534 1534 $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
1535 1535 (and
1536 1536 (range
1537 1537 ('symbol', '2')
1538 1538 ('symbol', '0'))
1539 1539 (func
1540 1540 ('symbol', '_hexlist')
1541 1541 ('string', '*'))) (glob)
1542 1542 * optimized:
1543 1543 (and
1544 1544 (range
1545 1545 ('symbol', '2')
1546 1546 ('symbol', '0')
1547 1547 define)
1548 1548 (func
1549 1549 ('symbol', '_hexlist')
1550 1550 ('string', '*') (glob)
1551 1551 follow)
1552 1552 define)
1553 1553 * set:
1554 1554 <filteredset
1555 1555 <spanset- 0:2>,
1556 1556 <baseset [0, 1, 2]>>
1557 1557 2
1558 1558 1
1559 1559 0
1560 1560
1561 1561 $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
1562 1562 (and
1563 1563 (func
1564 1564 ('symbol', '_hexlist')
1565 1565 ('string', '*')) (glob)
1566 1566 (range
1567 1567 ('symbol', '2')
1568 1568 ('symbol', '0')))
1569 1569 * optimized:
1570 1570 (and
1571 1571 (range
1572 1572 ('symbol', '2')
1573 1573 ('symbol', '0')
1574 1574 follow)
1575 1575 (func
1576 1576 ('symbol', '_hexlist')
1577 1577 ('string', '*') (glob)
1578 1578 define)
1579 1579 define)
1580 1580 * set:
1581 1581 <baseset [0, 2, 1]>
1582 1582 0
1583 1583 2
1584 1584 1
1585 1585
1586 1586 '_list' should not go through the slow follow-order path if order doesn't
1587 1587 matter:
1588 1588
1589 1589 $ try -p optimized '2:0 & not (0 + 1)'
1590 1590 * optimized:
1591 1591 (difference
1592 1592 (range
1593 1593 ('symbol', '2')
1594 1594 ('symbol', '0')
1595 1595 define)
1596 1596 (func
1597 1597 ('symbol', '_list')
1598 1598 ('string', '0\x001')
1599 1599 any)
1600 1600 define)
1601 1601 * set:
1602 1602 <filteredset
1603 1603 <spanset- 0:2>,
1604 1604 <not
1605 1605 <baseset [0, 1]>>>
1606 1606 2
1607 1607
1608 1608 $ try -p optimized '2:0 & not (0:2 & (0 + 1))'
1609 1609 * optimized:
1610 1610 (difference
1611 1611 (range
1612 1612 ('symbol', '2')
1613 1613 ('symbol', '0')
1614 1614 define)
1615 1615 (and
1616 1616 (range
1617 1617 ('symbol', '0')
1618 1618 ('symbol', '2')
1619 1619 any)
1620 1620 (func
1621 1621 ('symbol', '_list')
1622 1622 ('string', '0\x001')
1623 1623 any)
1624 1624 any)
1625 1625 define)
1626 1626 * set:
1627 1627 <filteredset
1628 1628 <spanset- 0:2>,
1629 1629 <not
1630 1630 <baseset [0, 1]>>>
1631 1631 2
1632 1632
1633 1633 because 'present()' does nothing other than suppressing an error, the
1634 1634 ordering requirement should be forwarded to the nested expression
1635 1635
1636 1636 $ try -p optimized 'present(2 + 0 + 1)'
1637 1637 * optimized:
1638 1638 (func
1639 1639 ('symbol', 'present')
1640 1640 (func
1641 1641 ('symbol', '_list')
1642 1642 ('string', '2\x000\x001')
1643 1643 define)
1644 1644 define)
1645 1645 * set:
1646 1646 <baseset [2, 0, 1]>
1647 1647 2
1648 1648 0
1649 1649 1
1650 1650
1651 1651 $ try --optimize '2:0 & present(0 + 1 + 2)'
1652 1652 (and
1653 1653 (range
1654 1654 ('symbol', '2')
1655 1655 ('symbol', '0'))
1656 1656 (func
1657 1657 ('symbol', 'present')
1658 1658 (or
1659 1659 (list
1660 1660 ('symbol', '0')
1661 1661 ('symbol', '1')
1662 1662 ('symbol', '2')))))
1663 1663 * optimized:
1664 1664 (and
1665 1665 (range
1666 1666 ('symbol', '2')
1667 1667 ('symbol', '0')
1668 1668 define)
1669 1669 (func
1670 1670 ('symbol', 'present')
1671 1671 (func
1672 1672 ('symbol', '_list')
1673 1673 ('string', '0\x001\x002')
1674 1674 follow)
1675 1675 follow)
1676 1676 define)
1677 1677 * set:
1678 1678 <filteredset
1679 1679 <spanset- 0:2>,
1680 1680 <baseset [0, 1, 2]>>
1681 1681 2
1682 1682 1
1683 1683 0
1684 1684
1685 1685 'reverse()' should take effect only if it is the outermost expression:
1686 1686
1687 1687 $ try --optimize '0:2 & reverse(all())'
1688 1688 (and
1689 1689 (range
1690 1690 ('symbol', '0')
1691 1691 ('symbol', '2'))
1692 1692 (func
1693 1693 ('symbol', 'reverse')
1694 1694 (func
1695 1695 ('symbol', 'all')
1696 1696 None)))
1697 1697 * optimized:
1698 1698 (and
1699 1699 (range
1700 1700 ('symbol', '0')
1701 1701 ('symbol', '2')
1702 1702 define)
1703 1703 (func
1704 1704 ('symbol', 'reverse')
1705 1705 (func
1706 1706 ('symbol', 'all')
1707 1707 None
1708 1708 define)
1709 1709 follow)
1710 1710 define)
1711 1711 * set:
1712 1712 <filteredset
1713 1713 <spanset+ 0:2>,
1714 1714 <spanset+ 0:9>>
1715 1715 0
1716 1716 1
1717 1717 2
1718 1718
1719 1719 'sort()' should take effect only if it is the outermost expression:
1720 1720
1721 1721 $ try --optimize '0:2 & sort(all(), -rev)'
1722 1722 (and
1723 1723 (range
1724 1724 ('symbol', '0')
1725 1725 ('symbol', '2'))
1726 1726 (func
1727 1727 ('symbol', 'sort')
1728 1728 (list
1729 1729 (func
1730 1730 ('symbol', 'all')
1731 1731 None)
1732 1732 (negate
1733 1733 ('symbol', 'rev')))))
1734 1734 * optimized:
1735 1735 (and
1736 1736 (range
1737 1737 ('symbol', '0')
1738 1738 ('symbol', '2')
1739 1739 define)
1740 1740 (func
1741 1741 ('symbol', 'sort')
1742 1742 (list
1743 1743 (func
1744 1744 ('symbol', 'all')
1745 1745 None
1746 1746 define)
1747 1747 ('string', '-rev'))
1748 1748 follow)
1749 1749 define)
1750 1750 * set:
1751 1751 <filteredset
1752 1752 <spanset+ 0:2>,
1753 1753 <spanset+ 0:9>>
1754 1754 0
1755 1755 1
1756 1756 2
1757 1757
1758 1758 invalid argument passed to noop sort():
1759 1759
1760 1760 $ log '0:2 & sort()'
1761 1761 hg: parse error: sort requires one or two arguments
1762 1762 [255]
1763 1763 $ log '0:2 & sort(all(), -invalid)'
1764 1764 hg: parse error: unknown sort key '-invalid'
1765 1765 [255]
1766 1766
1767 1767 for 'A & f(B)', 'B' should not be affected by the order of 'A':
1768 1768
1769 1769 $ try --optimize '2:0 & first(1 + 0 + 2)'
1770 1770 (and
1771 1771 (range
1772 1772 ('symbol', '2')
1773 1773 ('symbol', '0'))
1774 1774 (func
1775 1775 ('symbol', 'first')
1776 1776 (or
1777 1777 (list
1778 1778 ('symbol', '1')
1779 1779 ('symbol', '0')
1780 1780 ('symbol', '2')))))
1781 1781 * optimized:
1782 1782 (and
1783 1783 (range
1784 1784 ('symbol', '2')
1785 1785 ('symbol', '0')
1786 1786 define)
1787 1787 (func
1788 1788 ('symbol', 'first')
1789 1789 (func
1790 1790 ('symbol', '_list')
1791 1791 ('string', '1\x000\x002')
1792 1792 define)
1793 1793 follow)
1794 1794 define)
1795 1795 * set:
1796 1796 <baseset
1797 1797 <limit n=1, offset=0,
1798 1798 <spanset- 0:2>,
1799 1799 <baseset [1, 0, 2]>>>
1800 1800 1
1801 1801
1802 1802 $ try --optimize '2:0 & not last(0 + 2 + 1)'
1803 1803 (and
1804 1804 (range
1805 1805 ('symbol', '2')
1806 1806 ('symbol', '0'))
1807 1807 (not
1808 1808 (func
1809 1809 ('symbol', 'last')
1810 1810 (or
1811 1811 (list
1812 1812 ('symbol', '0')
1813 1813 ('symbol', '2')
1814 1814 ('symbol', '1'))))))
1815 1815 * optimized:
1816 1816 (difference
1817 1817 (range
1818 1818 ('symbol', '2')
1819 1819 ('symbol', '0')
1820 1820 define)
1821 1821 (func
1822 1822 ('symbol', 'last')
1823 1823 (func
1824 1824 ('symbol', '_list')
1825 1825 ('string', '0\x002\x001')
1826 1826 define)
1827 1827 any)
1828 1828 define)
1829 1829 * set:
1830 1830 <filteredset
1831 1831 <spanset- 0:2>,
1832 1832 <not
1833 1833 <baseset
1834 1834 <last n=1,
1835 1835 <fullreposet+ 0:9>,
1836 1836 <baseset [1, 2, 0]>>>>>
1837 1837 2
1838 1838 0
1839 1839
1840 1840 for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
1841 1841
1842 1842 $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
1843 1843 (and
1844 1844 (range
1845 1845 ('symbol', '2')
1846 1846 ('symbol', '0'))
1847 1847 (range
1848 1848 (group
1849 1849 (or
1850 1850 (list
1851 1851 ('symbol', '1')
1852 1852 ('symbol', '0')
1853 1853 ('symbol', '2'))))
1854 1854 (group
1855 1855 (or
1856 1856 (list
1857 1857 ('symbol', '0')
1858 1858 ('symbol', '2')
1859 1859 ('symbol', '1'))))))
1860 1860 * optimized:
1861 1861 (and
1862 1862 (range
1863 1863 ('symbol', '2')
1864 1864 ('symbol', '0')
1865 1865 define)
1866 1866 (range
1867 1867 (func
1868 1868 ('symbol', '_list')
1869 1869 ('string', '1\x000\x002')
1870 1870 define)
1871 1871 (func
1872 1872 ('symbol', '_list')
1873 1873 ('string', '0\x002\x001')
1874 1874 define)
1875 1875 follow)
1876 1876 define)
1877 1877 * set:
1878 1878 <filteredset
1879 1879 <spanset- 0:2>,
1880 1880 <baseset [1]>>
1881 1881 1
1882 1882
1883 1883 'A & B' can be rewritten as 'B & A' by weight, but that's fine as long as
1884 1884 the ordering rule is determined before the rewrite; in this example,
1885 1885 'B' follows the order of the initial set, which is the same order as 'A'
1886 1886 since 'A' also follows the order:
1887 1887
1888 1888 $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
1889 1889 (and
1890 1890 (func
1891 1891 ('symbol', 'contains')
1892 1892 ('string', 'glob:*'))
1893 1893 (group
1894 1894 (or
1895 1895 (list
1896 1896 ('symbol', '2')
1897 1897 ('symbol', '0')
1898 1898 ('symbol', '1')))))
1899 1899 * optimized:
1900 1900 (and
1901 1901 (func
1902 1902 ('symbol', '_list')
1903 1903 ('string', '2\x000\x001')
1904 1904 follow)
1905 1905 (func
1906 1906 ('symbol', 'contains')
1907 1907 ('string', 'glob:*')
1908 1908 define)
1909 1909 define)
1910 1910 * set:
1911 1911 <filteredset
1912 1912 <baseset+ [0, 1, 2]>,
1913 1913 <contains 'glob:*'>>
1914 1914 0
1915 1915 1
1916 1916 2
1917 1917
1918 1918 and in this example, 'A & B' is rewritten as 'B & A', but 'A' overrides
1919 1919 the order appropriately:
1920 1920
1921 1921 $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
1922 1922 (and
1923 1923 (func
1924 1924 ('symbol', 'reverse')
1925 1925 (func
1926 1926 ('symbol', 'contains')
1927 1927 ('string', 'glob:*')))
1928 1928 (group
1929 1929 (or
1930 1930 (list
1931 1931 ('symbol', '0')
1932 1932 ('symbol', '2')
1933 1933 ('symbol', '1')))))
1934 1934 * optimized:
1935 1935 (and
1936 1936 (func
1937 1937 ('symbol', '_list')
1938 1938 ('string', '0\x002\x001')
1939 1939 follow)
1940 1940 (func
1941 1941 ('symbol', 'reverse')
1942 1942 (func
1943 1943 ('symbol', 'contains')
1944 1944 ('string', 'glob:*')
1945 1945 define)
1946 1946 define)
1947 1947 define)
1948 1948 * set:
1949 1949 <filteredset
1950 1950 <baseset- [0, 1, 2]>,
1951 1951 <contains 'glob:*'>>
1952 1952 2
1953 1953 1
1954 1954 0
1955 1955
1956 1956 'A + B' can be rewritten to 'B + A' by weight only when the order doesn't
1957 1957 matter (e.g. 'X & (A + B)' can be 'X & (B + A)', but '(A + B) & X' can't):
1958 1958
1959 1959 $ try -p optimized '0:2 & (reverse(contains("a")) + 2)'
1960 1960 * optimized:
1961 1961 (and
1962 1962 (range
1963 1963 ('symbol', '0')
1964 1964 ('symbol', '2')
1965 1965 define)
1966 1966 (or
1967 1967 (list
1968 1968 ('symbol', '2')
1969 1969 (func
1970 1970 ('symbol', 'reverse')
1971 1971 (func
1972 1972 ('symbol', 'contains')
1973 1973 ('string', 'a')
1974 1974 define)
1975 1975 follow))
1976 1976 follow)
1977 1977 define)
1978 1978 * set:
1979 1979 <filteredset
1980 1980 <spanset+ 0:2>,
1981 1981 <addset
1982 1982 <baseset [2]>,
1983 1983 <filteredset
1984 1984 <fullreposet+ 0:9>,
1985 1985 <contains 'a'>>>>
1986 1986 0
1987 1987 1
1988 1988 2
1989 1989
1990 1990 $ try -p optimized '(reverse(contains("a")) + 2) & 0:2'
1991 1991 * optimized:
1992 1992 (and
1993 1993 (range
1994 1994 ('symbol', '0')
1995 1995 ('symbol', '2')
1996 1996 follow)
1997 1997 (or
1998 1998 (list
1999 1999 (func
2000 2000 ('symbol', 'reverse')
2001 2001 (func
2002 2002 ('symbol', 'contains')
2003 2003 ('string', 'a')
2004 2004 define)
2005 2005 define)
2006 2006 ('symbol', '2'))
2007 2007 define)
2008 2008 define)
2009 2009 * set:
2010 2010 <addset
2011 2011 <filteredset
2012 2012 <spanset- 0:2>,
2013 2013 <contains 'a'>>,
2014 2014 <baseset [2]>>
2015 2015 1
2016 2016 0
2017 2017 2
2018 2018
2019 2019 test sort revset
2020 2020 --------------------------------------------
2021 2021
2022 2022 test when adding two unordered revsets
2023 2023
2024 2024 $ log 'sort(keyword(issue) or modifies(b))'
2025 2025 4
2026 2026 6
2027 2027
2028 2028 test when sorting a reversed collection in the same way it is
2029 2029
2030 2030 $ log 'sort(reverse(all()), -rev)'
2031 2031 9
2032 2032 8
2033 2033 7
2034 2034 6
2035 2035 5
2036 2036 4
2037 2037 3
2038 2038 2
2039 2039 1
2040 2040 0
2041 2041
2042 2042 test when sorting a reversed collection
2043 2043
2044 2044 $ log 'sort(reverse(all()), rev)'
2045 2045 0
2046 2046 1
2047 2047 2
2048 2048 3
2049 2049 4
2050 2050 5
2051 2051 6
2052 2052 7
2053 2053 8
2054 2054 9
2055 2055
2056 2056
2057 2057 test sorting two sorted collections in different orders
2058 2058
2059 2059 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
2060 2060 2
2061 2061 6
2062 2062 8
2063 2063 9
2064 2064
2065 2065 test sorting two sorted collections in different orders backwards
2066 2066
2067 2067 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
2068 2068 9
2069 2069 8
2070 2070 6
2071 2071 2
2072 2072
2073 2073 test empty sort key which is noop
2074 2074
2075 2075 $ log 'sort(0 + 2 + 1, "")'
2076 2076 0
2077 2077 2
2078 2078 1
2079 2079
2080 2080 test invalid sort keys
2081 2081
2082 2082 $ log 'sort(all(), -invalid)'
2083 2083 hg: parse error: unknown sort key '-invalid'
2084 2084 [255]
2085 2085
2086 2086 $ cd ..
2087 2087
2088 2088 test sorting by multiple keys including variable-length strings
2089 2089
2090 2090 $ hg init sorting
2091 2091 $ cd sorting
2092 2092 $ cat <<EOF >> .hg/hgrc
2093 2093 > [ui]
2094 2094 > logtemplate = '{rev} {branch|p5}{desc|p5}{author|p5}{date|hgdate}\n'
2095 2095 > [templatealias]
2096 2096 > p5(s) = pad(s, 5)
2097 2097 > EOF
2098 2098 $ hg branch -qf b12
2099 2099 $ hg ci -m m111 -u u112 -d '111 10800'
2100 2100 $ hg branch -qf b11
2101 2101 $ hg ci -m m12 -u u111 -d '112 7200'
2102 2102 $ hg branch -qf b111
2103 2103 $ hg ci -m m11 -u u12 -d '111 3600'
2104 2104 $ hg branch -qf b112
2105 2105 $ hg ci -m m111 -u u11 -d '120 0'
2106 2106 $ hg branch -qf b111
2107 2107 $ hg ci -m m112 -u u111 -d '110 14400'
2108 2108 created new head
2109 2109
2110 2110 compare revisions (has fast path):
2111 2111
2112 2112 $ hg log -r 'sort(all(), rev)'
2113 2113 0 b12 m111 u112 111 10800
2114 2114 1 b11 m12 u111 112 7200
2115 2115 2 b111 m11 u12 111 3600
2116 2116 3 b112 m111 u11 120 0
2117 2117 4 b111 m112 u111 110 14400
2118 2118
2119 2119 $ hg log -r 'sort(all(), -rev)'
2120 2120 4 b111 m112 u111 110 14400
2121 2121 3 b112 m111 u11 120 0
2122 2122 2 b111 m11 u12 111 3600
2123 2123 1 b11 m12 u111 112 7200
2124 2124 0 b12 m111 u112 111 10800
2125 2125
2126 2126 compare variable-length strings (issue5218):
2127 2127
2128 2128 $ hg log -r 'sort(all(), branch)'
2129 2129 1 b11 m12 u111 112 7200
2130 2130 2 b111 m11 u12 111 3600
2131 2131 4 b111 m112 u111 110 14400
2132 2132 3 b112 m111 u11 120 0
2133 2133 0 b12 m111 u112 111 10800
2134 2134
2135 2135 $ hg log -r 'sort(all(), -branch)'
2136 2136 0 b12 m111 u112 111 10800
2137 2137 3 b112 m111 u11 120 0
2138 2138 2 b111 m11 u12 111 3600
2139 2139 4 b111 m112 u111 110 14400
2140 2140 1 b11 m12 u111 112 7200
2141 2141
2142 2142 $ hg log -r 'sort(all(), desc)'
2143 2143 2 b111 m11 u12 111 3600
2144 2144 0 b12 m111 u112 111 10800
2145 2145 3 b112 m111 u11 120 0
2146 2146 4 b111 m112 u111 110 14400
2147 2147 1 b11 m12 u111 112 7200
2148 2148
2149 2149 $ hg log -r 'sort(all(), -desc)'
2150 2150 1 b11 m12 u111 112 7200
2151 2151 4 b111 m112 u111 110 14400
2152 2152 0 b12 m111 u112 111 10800
2153 2153 3 b112 m111 u11 120 0
2154 2154 2 b111 m11 u12 111 3600
2155 2155
2156 2156 $ hg log -r 'sort(all(), user)'
2157 2157 3 b112 m111 u11 120 0
2158 2158 1 b11 m12 u111 112 7200
2159 2159 4 b111 m112 u111 110 14400
2160 2160 0 b12 m111 u112 111 10800
2161 2161 2 b111 m11 u12 111 3600
2162 2162
2163 2163 $ hg log -r 'sort(all(), -user)'
2164 2164 2 b111 m11 u12 111 3600
2165 2165 0 b12 m111 u112 111 10800
2166 2166 1 b11 m12 u111 112 7200
2167 2167 4 b111 m112 u111 110 14400
2168 2168 3 b112 m111 u11 120 0
2169 2169
2170 2170 compare dates (tz offset should have no effect):
2171 2171
2172 2172 $ hg log -r 'sort(all(), date)'
2173 2173 4 b111 m112 u111 110 14400
2174 2174 0 b12 m111 u112 111 10800
2175 2175 2 b111 m11 u12 111 3600
2176 2176 1 b11 m12 u111 112 7200
2177 2177 3 b112 m111 u11 120 0
2178 2178
2179 2179 $ hg log -r 'sort(all(), -date)'
2180 2180 3 b112 m111 u11 120 0
2181 2181 1 b11 m12 u111 112 7200
2182 2182 0 b12 m111 u112 111 10800
2183 2183 2 b111 m11 u12 111 3600
2184 2184 4 b111 m112 u111 110 14400
2185 2185
2186 2186 be aware that 'sort(x, -k)' is not exactly the same as 'reverse(sort(x, k))'
2187 2187 because '-k' reverses the comparison, not the list itself:
2188 2188
2189 2189 $ hg log -r 'sort(0 + 2, date)'
2190 2190 0 b12 m111 u112 111 10800
2191 2191 2 b111 m11 u12 111 3600
2192 2192
2193 2193 $ hg log -r 'sort(0 + 2, -date)'
2194 2194 0 b12 m111 u112 111 10800
2195 2195 2 b111 m11 u12 111 3600
2196 2196
2197 2197 $ hg log -r 'reverse(sort(0 + 2, date))'
2198 2198 2 b111 m11 u12 111 3600
2199 2199 0 b12 m111 u112 111 10800
2200 2200
2201 2201 sort by multiple keys:
2202 2202
2203 2203 $ hg log -r 'sort(all(), "branch -rev")'
2204 2204 1 b11 m12 u111 112 7200
2205 2205 4 b111 m112 u111 110 14400
2206 2206 2 b111 m11 u12 111 3600
2207 2207 3 b112 m111 u11 120 0
2208 2208 0 b12 m111 u112 111 10800
2209 2209
2210 2210 $ hg log -r 'sort(all(), "-desc -date")'
2211 2211 1 b11 m12 u111 112 7200
2212 2212 4 b111 m112 u111 110 14400
2213 2213 3 b112 m111 u11 120 0
2214 2214 0 b12 m111 u112 111 10800
2215 2215 2 b111 m11 u12 111 3600
2216 2216
2217 2217 $ hg log -r 'sort(all(), "user -branch date rev")'
2218 2218 3 b112 m111 u11 120 0
2219 2219 4 b111 m112 u111 110 14400
2220 2220 1 b11 m12 u111 112 7200
2221 2221 0 b12 m111 u112 111 10800
2222 2222 2 b111 m11 u12 111 3600
2223 2223
2224 2224 toposort prioritises graph branches
2225 2225
2226 2226 $ hg up 2
2227 2227 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
2228 2228 $ touch a
2229 2229 $ hg addremove
2230 2230 adding a
2231 2231 $ hg ci -m 't1' -u 'tu' -d '130 0'
2232 2232 created new head
2233 2233 $ echo 'a' >> a
2234 2234 $ hg ci -m 't2' -u 'tu' -d '130 0'
2235 2235 $ hg book book1
2236 2236 $ hg up 4
2237 2237 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
2238 2238 (leaving bookmark book1)
2239 2239 $ touch a
2240 2240 $ hg addremove
2241 2241 adding a
2242 2242 $ hg ci -m 't3' -u 'tu' -d '130 0'
2243 2243
2244 2244 $ hg log -r 'sort(all(), topo)'
2245 2245 7 b111 t3 tu 130 0
2246 2246 4 b111 m112 u111 110 14400
2247 2247 3 b112 m111 u11 120 0
2248 2248 6 b111 t2 tu 130 0
2249 2249 5 b111 t1 tu 130 0
2250 2250 2 b111 m11 u12 111 3600
2251 2251 1 b11 m12 u111 112 7200
2252 2252 0 b12 m111 u112 111 10800
2253 2253
2254 2254 $ hg log -r 'sort(all(), -topo)'
2255 2255 0 b12 m111 u112 111 10800
2256 2256 1 b11 m12 u111 112 7200
2257 2257 2 b111 m11 u12 111 3600
2258 2258 5 b111 t1 tu 130 0
2259 2259 6 b111 t2 tu 130 0
2260 2260 3 b112 m111 u11 120 0
2261 2261 4 b111 m112 u111 110 14400
2262 2262 7 b111 t3 tu 130 0
2263 2263
2264 2264 $ hg log -r 'sort(all(), topo, topo.firstbranch=book1)'
2265 2265 6 b111 t2 tu 130 0
2266 2266 5 b111 t1 tu 130 0
2267 2267 7 b111 t3 tu 130 0
2268 2268 4 b111 m112 u111 110 14400
2269 2269 3 b112 m111 u11 120 0
2270 2270 2 b111 m11 u12 111 3600
2271 2271 1 b11 m12 u111 112 7200
2272 2272 0 b12 m111 u112 111 10800
2273 2273
2274 2274 topographical sorting can't be combined with other sort keys, and you can't
2275 2275 use the topo.firstbranch option when topo sort is not active:
2276 2276
2277 2277 $ hg log -r 'sort(all(), "topo user")'
2278 2278 hg: parse error: topo sort order cannot be combined with other sort keys
2279 2279 [255]
2280 2280
2281 2281 $ hg log -r 'sort(all(), user, topo.firstbranch=book1)'
2282 2282 hg: parse error: topo.firstbranch can only be used when using the topo sort key
2283 2283 [255]
2284 2284
2285 2285 topo.firstbranch should accept any kind of expressions:
2286 2286
2287 2287 $ hg log -r 'sort(0, topo, topo.firstbranch=(book1))'
2288 2288 0 b12 m111 u112 111 10800
2289 2289
2290 2290 $ cd ..
2291 2291 $ cd repo
2292 2292
2293 2293 test subtracting something from an addset
2294 2294
2295 2295 $ log '(outgoing() or removes(a)) - removes(a)'
2296 2296 8
2297 2297 9
2298 2298
2299 2299 test intersecting something with an addset
2300 2300
2301 2301 $ log 'parents(outgoing() or removes(a))'
2302 2302 1
2303 2303 4
2304 2304 5
2305 2305 8
2306 2306
2307 2307 test that `or` operation combines elements in the right order:
2308 2308
2309 2309 $ log '3:4 or 2:5'
2310 2310 3
2311 2311 4
2312 2312 2
2313 2313 5
2314 2314 $ log '3:4 or 5:2'
2315 2315 3
2316 2316 4
2317 2317 5
2318 2318 2
2319 2319 $ log 'sort(3:4 or 2:5)'
2320 2320 2
2321 2321 3
2322 2322 4
2323 2323 5
2324 2324 $ log 'sort(3:4 or 5:2)'
2325 2325 2
2326 2326 3
2327 2327 4
2328 2328 5
2329 2329
2330 2330 test that more than one `-r`s are combined in the right order and deduplicated:
2331 2331
2332 2332 $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)'
2333 2333 3
2334 2334 4
2335 2335 5
2336 2336 2
2337 2337 0
2338 2338 1
2339 2339
2340 2340 test that `or` operation skips duplicated revisions from right-hand side
2341 2341
2342 2342 $ try 'reverse(1::5) or ancestors(4)'
2343 2343 (or
2344 2344 (list
2345 2345 (func
2346 2346 ('symbol', 'reverse')
2347 2347 (dagrange
2348 2348 ('symbol', '1')
2349 2349 ('symbol', '5')))
2350 2350 (func
2351 2351 ('symbol', 'ancestors')
2352 2352 ('symbol', '4'))))
2353 2353 * set:
2354 2354 <addset
2355 2355 <baseset- [1, 3, 5]>,
2356 2356 <generatorset+>>
2357 2357 5
2358 2358 3
2359 2359 1
2360 2360 0
2361 2361 2
2362 2362 4
2363 2363 $ try 'sort(ancestors(4) or reverse(1::5))'
2364 2364 (func
2365 2365 ('symbol', 'sort')
2366 2366 (or
2367 2367 (list
2368 2368 (func
2369 2369 ('symbol', 'ancestors')
2370 2370 ('symbol', '4'))
2371 2371 (func
2372 2372 ('symbol', 'reverse')
2373 2373 (dagrange
2374 2374 ('symbol', '1')
2375 2375 ('symbol', '5'))))))
2376 2376 * set:
2377 2377 <addset+
2378 2378 <generatorset+>,
2379 2379 <baseset- [1, 3, 5]>>
2380 2380 0
2381 2381 1
2382 2382 2
2383 2383 3
2384 2384 4
2385 2385 5
2386 2386
2387 2387 test optimization of trivial `or` operation
2388 2388
2389 2389 $ try --optimize '0|(1)|"2"|-2|tip|null'
2390 2390 (or
2391 2391 (list
2392 2392 ('symbol', '0')
2393 2393 (group
2394 2394 ('symbol', '1'))
2395 2395 ('string', '2')
2396 2396 (negate
2397 2397 ('symbol', '2'))
2398 2398 ('symbol', 'tip')
2399 2399 ('symbol', 'null')))
2400 2400 * optimized:
2401 2401 (func
2402 2402 ('symbol', '_list')
2403 2403 ('string', '0\x001\x002\x00-2\x00tip\x00null')
2404 2404 define)
2405 2405 * set:
2406 2406 <baseset [0, 1, 2, 8, 9, -1]>
2407 2407 0
2408 2408 1
2409 2409 2
2410 2410 8
2411 2411 9
2412 2412 -1
2413 2413
2414 2414 $ try --optimize '0|1|2:3'
2415 2415 (or
2416 2416 (list
2417 2417 ('symbol', '0')
2418 2418 ('symbol', '1')
2419 2419 (range
2420 2420 ('symbol', '2')
2421 2421 ('symbol', '3'))))
2422 2422 * optimized:
2423 2423 (or
2424 2424 (list
2425 2425 (func
2426 2426 ('symbol', '_list')
2427 2427 ('string', '0\x001')
2428 2428 define)
2429 2429 (range
2430 2430 ('symbol', '2')
2431 2431 ('symbol', '3')
2432 2432 define))
2433 2433 define)
2434 2434 * set:
2435 2435 <addset
2436 2436 <baseset [0, 1]>,
2437 2437 <spanset+ 2:3>>
2438 2438 0
2439 2439 1
2440 2440 2
2441 2441 3
2442 2442
2443 2443 $ try --optimize '0:1|2|3:4|5|6'
2444 2444 (or
2445 2445 (list
2446 2446 (range
2447 2447 ('symbol', '0')
2448 2448 ('symbol', '1'))
2449 2449 ('symbol', '2')
2450 2450 (range
2451 2451 ('symbol', '3')
2452 2452 ('symbol', '4'))
2453 2453 ('symbol', '5')
2454 2454 ('symbol', '6')))
2455 2455 * optimized:
2456 2456 (or
2457 2457 (list
2458 2458 (range
2459 2459 ('symbol', '0')
2460 2460 ('symbol', '1')
2461 2461 define)
2462 2462 ('symbol', '2')
2463 2463 (range
2464 2464 ('symbol', '3')
2465 2465 ('symbol', '4')
2466 2466 define)
2467 2467 (func
2468 2468 ('symbol', '_list')
2469 2469 ('string', '5\x006')
2470 2470 define))
2471 2471 define)
2472 2472 * set:
2473 2473 <addset
2474 2474 <addset
2475 2475 <spanset+ 0:1>,
2476 2476 <baseset [2]>>,
2477 2477 <addset
2478 2478 <spanset+ 3:4>,
2479 2479 <baseset [5, 6]>>>
2480 2480 0
2481 2481 1
2482 2482 2
2483 2483 3
2484 2484 4
2485 2485 5
2486 2486 6
2487 2487
2488 2488 unoptimized `or` looks like this
2489 2489
2490 2490 $ try --no-optimized -p analyzed '0|1|2|3|4'
2491 2491 * analyzed:
2492 2492 (or
2493 2493 (list
2494 2494 ('symbol', '0')
2495 2495 ('symbol', '1')
2496 2496 ('symbol', '2')
2497 2497 ('symbol', '3')
2498 2498 ('symbol', '4'))
2499 2499 define)
2500 2500 * set:
2501 2501 <addset
2502 2502 <addset
2503 2503 <baseset [0]>,
2504 2504 <baseset [1]>>,
2505 2505 <addset
2506 2506 <baseset [2]>,
2507 2507 <addset
2508 2508 <baseset [3]>,
2509 2509 <baseset [4]>>>>
2510 2510 0
2511 2511 1
2512 2512 2
2513 2513 3
2514 2514 4
2515 2515
2516 2516 test that `_list` should be narrowed by provided `subset`
2517 2517
2518 2518 $ log '0:2 and (null|1|2|3)'
2519 2519 1
2520 2520 2
2521 2521
2522 2522 test that `_list` should remove duplicates
2523 2523
2524 2524 $ log '0|1|2|1|2|-1|tip'
2525 2525 0
2526 2526 1
2527 2527 2
2528 2528 9
2529 2529
2530 2530 test unknown revision in `_list`
2531 2531
2532 2532 $ log '0|unknown'
2533 2533 abort: unknown revision 'unknown'!
2534 2534 [255]
2535 2535
2536 2536 test integer range in `_list`
2537 2537
2538 2538 $ log '-1|-10'
2539 2539 9
2540 2540 0
2541 2541
2542 2542 $ log '-10|-11'
2543 2543 abort: unknown revision '-11'!
2544 2544 [255]
2545 2545
2546 2546 $ log '9|10'
2547 2547 abort: unknown revision '10'!
2548 2548 [255]
2549 2549
2550 2550 test '0000' != '0' in `_list`
2551 2551
2552 2552 $ log '0|0000'
2553 2553 0
2554 2554 -1
2555 2555
2556 2556 test ',' in `_list`
2557 2557 $ log '0,1'
2558 2558 hg: parse error: can't use a list in this context
2559 2559 (see hg help "revsets.x or y")
2560 2560 [255]
2561 2561 $ try '0,1,2'
2562 2562 (list
2563 2563 ('symbol', '0')
2564 2564 ('symbol', '1')
2565 2565 ('symbol', '2'))
2566 2566 hg: parse error: can't use a list in this context
2567 2567 (see hg help "revsets.x or y")
2568 2568 [255]
2569 2569
2570 2570 test that chained `or` operations make balanced addsets
2571 2571
2572 2572 $ try '0:1|1:2|2:3|3:4|4:5'
2573 2573 (or
2574 2574 (list
2575 2575 (range
2576 2576 ('symbol', '0')
2577 2577 ('symbol', '1'))
2578 2578 (range
2579 2579 ('symbol', '1')
2580 2580 ('symbol', '2'))
2581 2581 (range
2582 2582 ('symbol', '2')
2583 2583 ('symbol', '3'))
2584 2584 (range
2585 2585 ('symbol', '3')
2586 2586 ('symbol', '4'))
2587 2587 (range
2588 2588 ('symbol', '4')
2589 2589 ('symbol', '5'))))
2590 2590 * set:
2591 2591 <addset
2592 2592 <addset
2593 2593 <spanset+ 0:1>,
2594 2594 <spanset+ 1:2>>,
2595 2595 <addset
2596 2596 <spanset+ 2:3>,
2597 2597 <addset
2598 2598 <spanset+ 3:4>,
2599 2599 <spanset+ 4:5>>>>
2600 2600 0
2601 2601 1
2602 2602 2
2603 2603 3
2604 2604 4
2605 2605 5
2606 2606
2607 2607 no crash by empty group "()" while optimizing `or` operations
2608 2608
2609 2609 $ try --optimize '0|()'
2610 2610 (or
2611 2611 (list
2612 2612 ('symbol', '0')
2613 2613 (group
2614 2614 None)))
2615 2615 * optimized:
2616 2616 (or
2617 2617 (list
2618 2618 ('symbol', '0')
2619 2619 None)
2620 2620 define)
2621 2621 hg: parse error: missing argument
2622 2622 [255]
2623 2623
2624 2624 test that chained `or` operations never eat up stack (issue4624)
2625 2625 (uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
2626 2626
2627 2627 $ hg log -T '{rev}\n' -r `python -c "print '+'.join(['0:1'] * 500)"`
2628 2628 0
2629 2629 1
2630 2630
2631 2631 test that repeated `-r` options never eat up stack (issue4565)
2632 2632 (uses `-r 0::1` to avoid possible optimization at old-style parser)
2633 2633
2634 2634 $ hg log -T '{rev}\n' `python -c "for i in xrange(500): print '-r 0::1 ',"`
2635 2635 0
2636 2636 1
2637 2637
2638 2638 check that conversion to only works
2639 2639 $ try --optimize '::3 - ::1'
2640 2640 (minus
2641 2641 (dagrangepre
2642 2642 ('symbol', '3'))
2643 2643 (dagrangepre
2644 2644 ('symbol', '1')))
2645 2645 * optimized:
2646 2646 (func
2647 2647 ('symbol', 'only')
2648 2648 (list
2649 2649 ('symbol', '3')
2650 2650 ('symbol', '1'))
2651 2651 define)
2652 2652 * set:
2653 2653 <baseset+ [3]>
2654 2654 3
2655 2655 $ try --optimize 'ancestors(1) - ancestors(3)'
2656 2656 (minus
2657 2657 (func
2658 2658 ('symbol', 'ancestors')
2659 2659 ('symbol', '1'))
2660 2660 (func
2661 2661 ('symbol', 'ancestors')
2662 2662 ('symbol', '3')))
2663 2663 * optimized:
2664 2664 (func
2665 2665 ('symbol', 'only')
2666 2666 (list
2667 2667 ('symbol', '1')
2668 2668 ('symbol', '3'))
2669 2669 define)
2670 2670 * set:
2671 2671 <baseset+ []>
2672 2672 $ try --optimize 'not ::2 and ::6'
2673 2673 (and
2674 2674 (not
2675 2675 (dagrangepre
2676 2676 ('symbol', '2')))
2677 2677 (dagrangepre
2678 2678 ('symbol', '6')))
2679 2679 * optimized:
2680 2680 (func
2681 2681 ('symbol', 'only')
2682 2682 (list
2683 2683 ('symbol', '6')
2684 2684 ('symbol', '2'))
2685 2685 define)
2686 2686 * set:
2687 2687 <baseset+ [3, 4, 5, 6]>
2688 2688 3
2689 2689 4
2690 2690 5
2691 2691 6
2692 2692 $ try --optimize 'ancestors(6) and not ancestors(4)'
2693 2693 (and
2694 2694 (func
2695 2695 ('symbol', 'ancestors')
2696 2696 ('symbol', '6'))
2697 2697 (not
2698 2698 (func
2699 2699 ('symbol', 'ancestors')
2700 2700 ('symbol', '4'))))
2701 2701 * optimized:
2702 2702 (func
2703 2703 ('symbol', 'only')
2704 2704 (list
2705 2705 ('symbol', '6')
2706 2706 ('symbol', '4'))
2707 2707 define)
2708 2708 * set:
2709 2709 <baseset+ [3, 5, 6]>
2710 2710 3
2711 2711 5
2712 2712 6
2713 2713
2714 2714 no crash by empty group "()" while optimizing to "only()"
2715 2715
2716 2716 $ try --optimize '::1 and ()'
2717 2717 (and
2718 2718 (dagrangepre
2719 2719 ('symbol', '1'))
2720 2720 (group
2721 2721 None))
2722 2722 * optimized:
2723 2723 (and
2724 2724 None
2725 2725 (func
2726 2726 ('symbol', 'ancestors')
2727 2727 ('symbol', '1')
2728 2728 define)
2729 2729 define)
2730 2730 hg: parse error: missing argument
2731 2731 [255]
2732 2732
2733 2733 invalid function call should not be optimized to only()
2734 2734
2735 2735 $ log '"ancestors"(6) and not ancestors(4)'
2736 2736 hg: parse error: not a symbol
2737 2737 [255]
2738 2738
2739 2739 $ log 'ancestors(6) and not "ancestors"(4)'
2740 2740 hg: parse error: not a symbol
2741 2741 [255]
2742 2742
2743 2743 we can use patterns when searching for tags
2744 2744
2745 2745 $ log 'tag("1..*")'
2746 2746 abort: tag '1..*' does not exist!
2747 2747 [255]
2748 2748 $ log 'tag("re:1..*")'
2749 2749 6
2750 2750 $ log 'tag("re:[0-9].[0-9]")'
2751 2751 6
2752 2752 $ log 'tag("literal:1.0")'
2753 2753 6
2754 2754 $ log 'tag("re:0..*")'
2755 2755
2756 2756 $ log 'tag(unknown)'
2757 2757 abort: tag 'unknown' does not exist!
2758 2758 [255]
2759 2759 $ log 'tag("re:unknown")'
2760 2760 $ log 'present(tag("unknown"))'
2761 2761 $ log 'present(tag("re:unknown"))'
2762 2762 $ log 'branch(unknown)'
2763 2763 abort: unknown revision 'unknown'!
2764 2764 [255]
2765 2765 $ log 'branch("literal:unknown")'
2766 2766 abort: branch 'unknown' does not exist!
2767 2767 [255]
2768 2768 $ log 'branch("re:unknown")'
2769 2769 $ log 'present(branch("unknown"))'
2770 2770 $ log 'present(branch("re:unknown"))'
2771 2771 $ log 'user(bob)'
2772 2772 2
2773 2773
2774 2774 $ log '4::8'
2775 2775 4
2776 2776 8
2777 2777 $ log '4:8'
2778 2778 4
2779 2779 5
2780 2780 6
2781 2781 7
2782 2782 8
2783 2783
2784 2784 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
2785 2785 4
2786 2786 2
2787 2787 5
2788 2788
2789 2789 $ log 'not 0 and 0:2'
2790 2790 1
2791 2791 2
2792 2792 $ log 'not 1 and 0:2'
2793 2793 0
2794 2794 2
2795 2795 $ log 'not 2 and 0:2'
2796 2796 0
2797 2797 1
2798 2798 $ log '(1 and 2)::'
2799 2799 $ log '(1 and 2):'
2800 2800 $ log '(1 and 2):3'
2801 2801 $ log 'sort(head(), -rev)'
2802 2802 9
2803 2803 7
2804 2804 6
2805 2805 5
2806 2806 4
2807 2807 3
2808 2808 2
2809 2809 1
2810 2810 0
2811 2811 $ log '4::8 - 8'
2812 2812 4
2813 2813
2814 2814 matching() should preserve the order of the input set:
2815 2815
2816 2816 $ log '(2 or 3 or 1) and matching(1 or 2 or 3)'
2817 2817 2
2818 2818 3
2819 2819 1
2820 2820
2821 2821 $ log 'named("unknown")'
2822 2822 abort: namespace 'unknown' does not exist!
2823 2823 [255]
2824 2824 $ log 'named("re:unknown")'
2825 2825 abort: no namespace exists that match 'unknown'!
2826 2826 [255]
2827 2827 $ log 'present(named("unknown"))'
2828 2828 $ log 'present(named("re:unknown"))'
2829 2829
2830 2830 $ log 'tag()'
2831 2831 6
2832 2832 $ log 'named("tags")'
2833 2833 6
2834 2834
2835 2835 issue2437
2836 2836
2837 2837 $ log '3 and p1(5)'
2838 2838 3
2839 2839 $ log '4 and p2(6)'
2840 2840 4
2841 2841 $ log '1 and parents(:2)'
2842 2842 1
2843 2843 $ log '2 and children(1:)'
2844 2844 2
2845 2845 $ log 'roots(all()) or roots(all())'
2846 2846 0
2847 2847 $ hg debugrevspec 'roots(all()) or roots(all())'
2848 2848 0
2849 2849 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
2850 2850 9
2851 2851 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
2852 2852 4
2853 2853
2854 2854 issue2654: report a parse error if the revset was not completely parsed
2855 2855
2856 2856 $ log '1 OR 2'
2857 2857 hg: parse error at 2: invalid token
2858 2858 [255]
2859 2859
2860 2860 or operator should preserve ordering:
2861 2861 $ log 'reverse(2::4) or tip'
2862 2862 4
2863 2863 2
2864 2864 9
2865 2865
2866 2866 parentrevspec
2867 2867
2868 2868 $ log 'merge()^0'
2869 2869 6
2870 2870 $ log 'merge()^'
2871 2871 5
2872 2872 $ log 'merge()^1'
2873 2873 5
2874 2874 $ log 'merge()^2'
2875 2875 4
2876 2876 $ log '(not merge())^2'
2877 2877 $ log 'merge()^^'
2878 2878 3
2879 2879 $ log 'merge()^1^'
2880 2880 3
2881 2881 $ log 'merge()^^^'
2882 2882 1
2883 2883
2884 2884 $ log 'merge()~0'
2885 2885 6
2886 2886 $ log 'merge()~1'
2887 2887 5
2888 2888 $ log 'merge()~2'
2889 2889 3
2890 2890 $ log 'merge()~2^1'
2891 2891 1
2892 2892 $ log 'merge()~3'
2893 2893 1
2894 2894
2895 2895 $ log '(-3:tip)^'
2896 2896 4
2897 2897 6
2898 2898 8
2899 2899
2900 2900 $ log 'tip^foo'
2901 2901 hg: parse error: ^ expects a number 0, 1, or 2
2902 2902 [255]
2903 2903
2904 2904 Bogus function gets suggestions
2905 2905 $ log 'add()'
2906 2906 hg: parse error: unknown identifier: add
2907 2907 (did you mean adds?)
2908 2908 [255]
2909 2909 $ log 'added()'
2910 2910 hg: parse error: unknown identifier: added
2911 2911 (did you mean adds?)
2912 2912 [255]
2913 2913 $ log 'remo()'
2914 2914 hg: parse error: unknown identifier: remo
2915 2915 (did you mean one of remote, removes?)
2916 2916 [255]
2917 2917 $ log 'babar()'
2918 2918 hg: parse error: unknown identifier: babar
2919 2919 [255]
2920 2920
2921 2921 Bogus function with a similar internal name doesn't suggest the internal name
2922 2922 $ log 'matches()'
2923 2923 hg: parse error: unknown identifier: matches
2924 2924 (did you mean matching?)
2925 2925 [255]
2926 2926
2927 2927 Undocumented functions aren't suggested as similar either
2928 2928 $ log 'tagged2()'
2929 2929 hg: parse error: unknown identifier: tagged2
2930 2930 [255]
2931 2931
2932 2932 multiple revspecs
2933 2933
2934 2934 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
2935 2935 8
2936 2936 9
2937 2937 4
2938 2938 5
2939 2939 6
2940 2940 7
2941 2941
2942 2942 test usage in revpair (with "+")
2943 2943
2944 2944 (real pair)
2945 2945
2946 2946 $ hg diff -r 'tip^^' -r 'tip'
2947 2947 diff -r 2326846efdab -r 24286f4ae135 .hgtags
2948 2948 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2949 2949 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
2950 2950 @@ -0,0 +1,1 @@
2951 2951 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
2952 2952 $ hg diff -r 'tip^^::tip'
2953 2953 diff -r 2326846efdab -r 24286f4ae135 .hgtags
2954 2954 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2955 2955 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
2956 2956 @@ -0,0 +1,1 @@
2957 2957 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
2958 2958
2959 2959 (single rev)
2960 2960
2961 2961 $ hg diff -r 'tip^' -r 'tip^'
2962 2962 $ hg diff -r 'tip^:tip^'
2963 2963
2964 2964 (single rev that does not looks like a range)
2965 2965
2966 2966 $ hg diff -r 'tip^::tip^ or tip^'
2967 2967 diff -r d5d0dcbdc4d9 .hgtags
2968 2968 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2969 2969 +++ b/.hgtags * (glob)
2970 2970 @@ -0,0 +1,1 @@
2971 2971 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
2972 2972 $ hg diff -r 'tip^ or tip^'
2973 2973 diff -r d5d0dcbdc4d9 .hgtags
2974 2974 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2975 2975 +++ b/.hgtags * (glob)
2976 2976 @@ -0,0 +1,1 @@
2977 2977 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
2978 2978
2979 2979 (no rev)
2980 2980
2981 2981 $ hg diff -r 'author("babar") or author("celeste")'
2982 2982 abort: empty revision range
2983 2983 [255]
2984 2984
2985 2985 aliases:
2986 2986
2987 2987 $ echo '[revsetalias]' >> .hg/hgrc
2988 2988 $ echo 'm = merge()' >> .hg/hgrc
2989 2989 (revset aliases can override builtin revsets)
2990 2990 $ echo 'p2($1) = p1($1)' >> .hg/hgrc
2991 2991 $ echo 'sincem = descendants(m)' >> .hg/hgrc
2992 2992 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
2993 2993 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
2994 2994 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
2995 2995
2996 2996 $ try m
2997 2997 ('symbol', 'm')
2998 2998 * expanded:
2999 2999 (func
3000 3000 ('symbol', 'merge')
3001 3001 None)
3002 3002 * set:
3003 3003 <filteredset
3004 3004 <fullreposet+ 0:9>,
3005 3005 <merge>>
3006 3006 6
3007 3007
3008 3008 $ HGPLAIN=1
3009 3009 $ export HGPLAIN
3010 3010 $ try m
3011 3011 ('symbol', 'm')
3012 3012 abort: unknown revision 'm'!
3013 3013 [255]
3014 3014
3015 3015 $ HGPLAINEXCEPT=revsetalias
3016 3016 $ export HGPLAINEXCEPT
3017 3017 $ try m
3018 3018 ('symbol', 'm')
3019 3019 * expanded:
3020 3020 (func
3021 3021 ('symbol', 'merge')
3022 3022 None)
3023 3023 * set:
3024 3024 <filteredset
3025 3025 <fullreposet+ 0:9>,
3026 3026 <merge>>
3027 3027 6
3028 3028
3029 3029 $ unset HGPLAIN
3030 3030 $ unset HGPLAINEXCEPT
3031 3031
3032 3032 $ try 'p2(.)'
3033 3033 (func
3034 3034 ('symbol', 'p2')
3035 3035 ('symbol', '.'))
3036 3036 * expanded:
3037 3037 (func
3038 3038 ('symbol', 'p1')
3039 3039 ('symbol', '.'))
3040 3040 * set:
3041 3041 <baseset+ [8]>
3042 3042 8
3043 3043
3044 3044 $ HGPLAIN=1
3045 3045 $ export HGPLAIN
3046 3046 $ try 'p2(.)'
3047 3047 (func
3048 3048 ('symbol', 'p2')
3049 3049 ('symbol', '.'))
3050 3050 * set:
3051 3051 <baseset+ []>
3052 3052
3053 3053 $ HGPLAINEXCEPT=revsetalias
3054 3054 $ export HGPLAINEXCEPT
3055 3055 $ try 'p2(.)'
3056 3056 (func
3057 3057 ('symbol', 'p2')
3058 3058 ('symbol', '.'))
3059 3059 * expanded:
3060 3060 (func
3061 3061 ('symbol', 'p1')
3062 3062 ('symbol', '.'))
3063 3063 * set:
3064 3064 <baseset+ [8]>
3065 3065 8
3066 3066
3067 3067 $ unset HGPLAIN
3068 3068 $ unset HGPLAINEXCEPT
3069 3069
3070 3070 test alias recursion
3071 3071
3072 3072 $ try sincem
3073 3073 ('symbol', 'sincem')
3074 3074 * expanded:
3075 3075 (func
3076 3076 ('symbol', 'descendants')
3077 3077 (func
3078 3078 ('symbol', 'merge')
3079 3079 None))
3080 3080 * set:
3081 3081 <addset+
3082 3082 <filteredset
3083 3083 <fullreposet+ 0:9>,
3084 3084 <merge>>,
3085 3085 <generatorset+>>
3086 3086 6
3087 3087 7
3088 3088
3089 3089 test infinite recursion
3090 3090
3091 3091 $ echo 'recurse1 = recurse2' >> .hg/hgrc
3092 3092 $ echo 'recurse2 = recurse1' >> .hg/hgrc
3093 3093 $ try recurse1
3094 3094 ('symbol', 'recurse1')
3095 3095 hg: parse error: infinite expansion of revset alias "recurse1" detected
3096 3096 [255]
3097 3097
3098 3098 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
3099 3099 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
3100 3100 $ try "level2(level1(1, 2), 3)"
3101 3101 (func
3102 3102 ('symbol', 'level2')
3103 3103 (list
3104 3104 (func
3105 3105 ('symbol', 'level1')
3106 3106 (list
3107 3107 ('symbol', '1')
3108 3108 ('symbol', '2')))
3109 3109 ('symbol', '3')))
3110 3110 * expanded:
3111 3111 (or
3112 3112 (list
3113 3113 ('symbol', '3')
3114 3114 (or
3115 3115 (list
3116 3116 ('symbol', '1')
3117 3117 ('symbol', '2')))))
3118 3118 * set:
3119 3119 <addset
3120 3120 <baseset [3]>,
3121 3121 <baseset [1, 2]>>
3122 3122 3
3123 3123 1
3124 3124 2
3125 3125
3126 3126 test nesting and variable passing
3127 3127
3128 3128 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
3129 3129 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
3130 3130 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
3131 3131 $ try 'nested(2:5)'
3132 3132 (func
3133 3133 ('symbol', 'nested')
3134 3134 (range
3135 3135 ('symbol', '2')
3136 3136 ('symbol', '5')))
3137 3137 * expanded:
3138 3138 (func
3139 3139 ('symbol', 'max')
3140 3140 (range
3141 3141 ('symbol', '2')
3142 3142 ('symbol', '5')))
3143 3143 * set:
3144 3144 <baseset
3145 3145 <max
3146 3146 <fullreposet+ 0:9>,
3147 3147 <spanset+ 2:5>>>
3148 3148 5
3149 3149
3150 3150 test chained `or` operations are flattened at parsing phase
3151 3151
3152 3152 $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
3153 3153 $ try 'chainedorops(0:1, 1:2, 2:3)'
3154 3154 (func
3155 3155 ('symbol', 'chainedorops')
3156 3156 (list
3157 3157 (range
3158 3158 ('symbol', '0')
3159 3159 ('symbol', '1'))
3160 3160 (range
3161 3161 ('symbol', '1')
3162 3162 ('symbol', '2'))
3163 3163 (range
3164 3164 ('symbol', '2')
3165 3165 ('symbol', '3'))))
3166 3166 * expanded:
3167 3167 (or
3168 3168 (list
3169 3169 (range
3170 3170 ('symbol', '0')
3171 3171 ('symbol', '1'))
3172 3172 (range
3173 3173 ('symbol', '1')
3174 3174 ('symbol', '2'))
3175 3175 (range
3176 3176 ('symbol', '2')
3177 3177 ('symbol', '3'))))
3178 3178 * set:
3179 3179 <addset
3180 3180 <spanset+ 0:1>,
3181 3181 <addset
3182 3182 <spanset+ 1:2>,
3183 3183 <spanset+ 2:3>>>
3184 3184 0
3185 3185 1
3186 3186 2
3187 3187 3
3188 3188
3189 3189 test variable isolation, variable placeholders are rewritten as string
3190 3190 then parsed and matched again as string. Check they do not leak too
3191 3191 far away.
3192 3192
3193 3193 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
3194 3194 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
3195 3195 $ try 'callinjection(2:5)'
3196 3196 (func
3197 3197 ('symbol', 'callinjection')
3198 3198 (range
3199 3199 ('symbol', '2')
3200 3200 ('symbol', '5')))
3201 3201 * expanded:
3202 3202 (func
3203 3203 ('symbol', 'descendants')
3204 3204 (func
3205 3205 ('symbol', 'max')
3206 3206 ('string', '$1')))
3207 3207 abort: unknown revision '$1'!
3208 3208 [255]
3209 3209
3210 3210 test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)',
3211 3211 but 'all()' should never be substituted to '0()'.
3212 3212
3213 3213 $ echo 'universe = all()' >> .hg/hgrc
3214 3214 $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
3215 3215 $ try 'shadowall(0)'
3216 3216 (func
3217 3217 ('symbol', 'shadowall')
3218 3218 ('symbol', '0'))
3219 3219 * expanded:
3220 3220 (and
3221 3221 ('symbol', '0')
3222 3222 (func
3223 3223 ('symbol', 'all')
3224 3224 None))
3225 3225 * set:
3226 3226 <filteredset
3227 3227 <baseset [0]>,
3228 3228 <spanset+ 0:9>>
3229 3229 0
3230 3230
3231 3231 test unknown reference:
3232 3232
3233 3233 $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
3234 3234 (func
3235 3235 ('symbol', 'unknownref')
3236 3236 ('symbol', '0'))
3237 3237 abort: bad definition of revset alias "unknownref": invalid symbol '$2'
3238 3238 [255]
3239 3239
3240 3240 $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
3241 3241 ('symbol', 'tip')
3242 3242 warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
3243 3243 * set:
3244 3244 <baseset [9]>
3245 3245 9
3246 3246
3247 3247 $ try 'tip'
3248 3248 ('symbol', 'tip')
3249 3249 * set:
3250 3250 <baseset [9]>
3251 3251 9
3252 3252
3253 3253 $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
3254 3254 ('symbol', 'tip')
3255 3255 warning: bad declaration of revset alias "bad name": at 4: invalid token
3256 3256 * set:
3257 3257 <baseset [9]>
3258 3258 9
3259 3259 $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
3260 3260 $ try 'strictreplacing("foo", tip)'
3261 3261 (func
3262 3262 ('symbol', 'strictreplacing')
3263 3263 (list
3264 3264 ('string', 'foo')
3265 3265 ('symbol', 'tip')))
3266 3266 * expanded:
3267 3267 (or
3268 3268 (list
3269 3269 ('symbol', 'tip')
3270 3270 (func
3271 3271 ('symbol', 'desc')
3272 3272 ('string', '$1'))))
3273 3273 * set:
3274 3274 <addset
3275 3275 <baseset [9]>,
3276 3276 <filteredset
3277 3277 <fullreposet+ 0:9>,
3278 3278 <desc '$1'>>>
3279 3279 9
3280 3280
3281 3281 $ try 'd(2:5)'
3282 3282 (func
3283 3283 ('symbol', 'd')
3284 3284 (range
3285 3285 ('symbol', '2')
3286 3286 ('symbol', '5')))
3287 3287 * expanded:
3288 3288 (func
3289 3289 ('symbol', 'reverse')
3290 3290 (func
3291 3291 ('symbol', 'sort')
3292 3292 (list
3293 3293 (range
3294 3294 ('symbol', '2')
3295 3295 ('symbol', '5'))
3296 3296 ('symbol', 'date'))))
3297 3297 * set:
3298 3298 <baseset [4, 5, 3, 2]>
3299 3299 4
3300 3300 5
3301 3301 3
3302 3302 2
3303 3303 $ try 'rs(2 or 3, date)'
3304 3304 (func
3305 3305 ('symbol', 'rs')
3306 3306 (list
3307 3307 (or
3308 3308 (list
3309 3309 ('symbol', '2')
3310 3310 ('symbol', '3')))
3311 3311 ('symbol', 'date')))
3312 3312 * expanded:
3313 3313 (func
3314 3314 ('symbol', 'reverse')
3315 3315 (func
3316 3316 ('symbol', 'sort')
3317 3317 (list
3318 3318 (or
3319 3319 (list
3320 3320 ('symbol', '2')
3321 3321 ('symbol', '3')))
3322 3322 ('symbol', 'date'))))
3323 3323 * set:
3324 3324 <baseset [3, 2]>
3325 3325 3
3326 3326 2
3327 3327 $ try 'rs()'
3328 3328 (func
3329 3329 ('symbol', 'rs')
3330 3330 None)
3331 3331 hg: parse error: invalid number of arguments: 0
3332 3332 [255]
3333 3333 $ try 'rs(2)'
3334 3334 (func
3335 3335 ('symbol', 'rs')
3336 3336 ('symbol', '2'))
3337 3337 hg: parse error: invalid number of arguments: 1
3338 3338 [255]
3339 3339 $ try 'rs(2, data, 7)'
3340 3340 (func
3341 3341 ('symbol', 'rs')
3342 3342 (list
3343 3343 ('symbol', '2')
3344 3344 ('symbol', 'data')
3345 3345 ('symbol', '7')))
3346 3346 hg: parse error: invalid number of arguments: 3
3347 3347 [255]
3348 3348 $ try 'rs4(2 or 3, x, x, date)'
3349 3349 (func
3350 3350 ('symbol', 'rs4')
3351 3351 (list
3352 3352 (or
3353 3353 (list
3354 3354 ('symbol', '2')
3355 3355 ('symbol', '3')))
3356 3356 ('symbol', 'x')
3357 3357 ('symbol', 'x')
3358 3358 ('symbol', 'date')))
3359 3359 * expanded:
3360 3360 (func
3361 3361 ('symbol', 'reverse')
3362 3362 (func
3363 3363 ('symbol', 'sort')
3364 3364 (list
3365 3365 (or
3366 3366 (list
3367 3367 ('symbol', '2')
3368 3368 ('symbol', '3')))
3369 3369 ('symbol', 'date'))))
3370 3370 * set:
3371 3371 <baseset [3, 2]>
3372 3372 3
3373 3373 2
3374 3374
3375 3375 issue4553: check that revset aliases override existing hash prefix
3376 3376
3377 3377 $ hg log -qr e
3378 3378 6:e0cc66ef77e8
3379 3379
3380 3380 $ hg log -qr e --config revsetalias.e="all()"
3381 3381 0:2785f51eece5
3382 3382 1:d75937da8da0
3383 3383 2:5ed5505e9f1c
3384 3384 3:8528aa5637f2
3385 3385 4:2326846efdab
3386 3386 5:904fa392b941
3387 3387 6:e0cc66ef77e8
3388 3388 7:013af1973af4
3389 3389 8:d5d0dcbdc4d9
3390 3390 9:24286f4ae135
3391 3391
3392 3392 $ hg log -qr e: --config revsetalias.e="0"
3393 3393 0:2785f51eece5
3394 3394 1:d75937da8da0
3395 3395 2:5ed5505e9f1c
3396 3396 3:8528aa5637f2
3397 3397 4:2326846efdab
3398 3398 5:904fa392b941
3399 3399 6:e0cc66ef77e8
3400 3400 7:013af1973af4
3401 3401 8:d5d0dcbdc4d9
3402 3402 9:24286f4ae135
3403 3403
3404 3404 $ hg log -qr :e --config revsetalias.e="9"
3405 3405 0:2785f51eece5
3406 3406 1:d75937da8da0
3407 3407 2:5ed5505e9f1c
3408 3408 3:8528aa5637f2
3409 3409 4:2326846efdab
3410 3410 5:904fa392b941
3411 3411 6:e0cc66ef77e8
3412 3412 7:013af1973af4
3413 3413 8:d5d0dcbdc4d9
3414 3414 9:24286f4ae135
3415 3415
3416 3416 $ hg log -qr e:
3417 3417 6:e0cc66ef77e8
3418 3418 7:013af1973af4
3419 3419 8:d5d0dcbdc4d9
3420 3420 9:24286f4ae135
3421 3421
3422 3422 $ hg log -qr :e
3423 3423 0:2785f51eece5
3424 3424 1:d75937da8da0
3425 3425 2:5ed5505e9f1c
3426 3426 3:8528aa5637f2
3427 3427 4:2326846efdab
3428 3428 5:904fa392b941
3429 3429 6:e0cc66ef77e8
3430 3430
3431 3431 issue2549 - correct optimizations
3432 3432
3433 3433 $ try 'limit(1 or 2 or 3, 2) and not 2'
3434 3434 (and
3435 3435 (func
3436 3436 ('symbol', 'limit')
3437 3437 (list
3438 3438 (or
3439 3439 (list
3440 3440 ('symbol', '1')
3441 3441 ('symbol', '2')
3442 3442 ('symbol', '3')))
3443 3443 ('symbol', '2')))
3444 3444 (not
3445 3445 ('symbol', '2')))
3446 3446 * set:
3447 3447 <filteredset
3448 3448 <baseset
3449 3449 <limit n=2, offset=0,
3450 3450 <fullreposet+ 0:9>,
3451 3451 <baseset [1, 2, 3]>>>,
3452 3452 <not
3453 3453 <baseset [2]>>>
3454 3454 1
3455 3455 $ try 'max(1 or 2) and not 2'
3456 3456 (and
3457 3457 (func
3458 3458 ('symbol', 'max')
3459 3459 (or
3460 3460 (list
3461 3461 ('symbol', '1')
3462 3462 ('symbol', '2'))))
3463 3463 (not
3464 3464 ('symbol', '2')))
3465 3465 * set:
3466 3466 <filteredset
3467 3467 <baseset
3468 3468 <max
3469 3469 <fullreposet+ 0:9>,
3470 3470 <baseset [1, 2]>>>,
3471 3471 <not
3472 3472 <baseset [2]>>>
3473 3473 $ try 'min(1 or 2) and not 1'
3474 3474 (and
3475 3475 (func
3476 3476 ('symbol', 'min')
3477 3477 (or
3478 3478 (list
3479 3479 ('symbol', '1')
3480 3480 ('symbol', '2'))))
3481 3481 (not
3482 3482 ('symbol', '1')))
3483 3483 * set:
3484 3484 <filteredset
3485 3485 <baseset
3486 3486 <min
3487 3487 <fullreposet+ 0:9>,
3488 3488 <baseset [1, 2]>>>,
3489 3489 <not
3490 3490 <baseset [1]>>>
3491 3491 $ try 'last(1 or 2, 1) and not 2'
3492 3492 (and
3493 3493 (func
3494 3494 ('symbol', 'last')
3495 3495 (list
3496 3496 (or
3497 3497 (list
3498 3498 ('symbol', '1')
3499 3499 ('symbol', '2')))
3500 3500 ('symbol', '1')))
3501 3501 (not
3502 3502 ('symbol', '2')))
3503 3503 * set:
3504 3504 <filteredset
3505 3505 <baseset
3506 3506 <last n=1,
3507 3507 <fullreposet+ 0:9>,
3508 3508 <baseset [2, 1]>>>,
3509 3509 <not
3510 3510 <baseset [2]>>>
3511 3511
3512 3512 issue4289 - ordering of built-ins
3513 3513 $ hg log -M -q -r 3:2
3514 3514 3:8528aa5637f2
3515 3515 2:5ed5505e9f1c
3516 3516
3517 3517 test revsets started with 40-chars hash (issue3669)
3518 3518
3519 3519 $ ISSUE3669_TIP=`hg tip --template '{node}'`
3520 3520 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
3521 3521 9
3522 3522 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
3523 3523 8
3524 3524
3525 3525 test or-ed indirect predicates (issue3775)
3526 3526
3527 3527 $ log '6 or 6^1' | sort
3528 3528 5
3529 3529 6
3530 3530 $ log '6^1 or 6' | sort
3531 3531 5
3532 3532 6
3533 3533 $ log '4 or 4~1' | sort
3534 3534 2
3535 3535 4
3536 3536 $ log '4~1 or 4' | sort
3537 3537 2
3538 3538 4
3539 3539 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
3540 3540 0
3541 3541 1
3542 3542 2
3543 3543 3
3544 3544 4
3545 3545 5
3546 3546 6
3547 3547 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
3548 3548 0
3549 3549 1
3550 3550 2
3551 3551 3
3552 3552 4
3553 3553 5
3554 3554 6
3555 3555
3556 3556 tests for 'remote()' predicate:
3557 3557 #. (csets in remote) (id) (remote)
3558 3558 1. less than local current branch "default"
3559 3559 2. same with local specified "default"
3560 3560 3. more than local specified specified
3561 3561
3562 3562 $ hg clone --quiet -U . ../remote3
3563 3563 $ cd ../remote3
3564 3564 $ hg update -q 7
3565 3565 $ echo r > r
3566 3566 $ hg ci -Aqm 10
3567 3567 $ log 'remote()'
3568 3568 7
3569 3569 $ log 'remote("a-b-c-")'
3570 3570 2
3571 3571 $ cd ../repo
3572 3572 $ log 'remote(".a.b.c.", "../remote3")'
3573 3573
3574 3574 tests for concatenation of strings/symbols by "##"
3575 3575
3576 3576 $ try "278 ## '5f5' ## 1ee ## 'ce5'"
3577 3577 (_concat
3578 3578 (_concat
3579 3579 (_concat
3580 3580 ('symbol', '278')
3581 3581 ('string', '5f5'))
3582 3582 ('symbol', '1ee'))
3583 3583 ('string', 'ce5'))
3584 3584 * concatenated:
3585 3585 ('string', '2785f51eece5')
3586 3586 * set:
3587 3587 <baseset [0]>
3588 3588 0
3589 3589
3590 3590 $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
3591 3591 $ try "cat4(278, '5f5', 1ee, 'ce5')"
3592 3592 (func
3593 3593 ('symbol', 'cat4')
3594 3594 (list
3595 3595 ('symbol', '278')
3596 3596 ('string', '5f5')
3597 3597 ('symbol', '1ee')
3598 3598 ('string', 'ce5')))
3599 3599 * expanded:
3600 3600 (_concat
3601 3601 (_concat
3602 3602 (_concat
3603 3603 ('symbol', '278')
3604 3604 ('string', '5f5'))
3605 3605 ('symbol', '1ee'))
3606 3606 ('string', 'ce5'))
3607 3607 * concatenated:
3608 3608 ('string', '2785f51eece5')
3609 3609 * set:
3610 3610 <baseset [0]>
3611 3611 0
3612 3612
3613 3613 (check concatenation in alias nesting)
3614 3614
3615 3615 $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
3616 3616 $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
3617 3617 $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
3618 3618 0
3619 3619
3620 3620 (check operator priority)
3621 3621
3622 3622 $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
3623 3623 $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
3624 3624 0
3625 3625 4
3626 3626
3627 3627 $ cd ..
3628 3628
3629 3629 prepare repository that has "default" branches of multiple roots
3630 3630
3631 3631 $ hg init namedbranch
3632 3632 $ cd namedbranch
3633 3633
3634 3634 $ echo default0 >> a
3635 3635 $ hg ci -Aqm0
3636 3636 $ echo default1 >> a
3637 3637 $ hg ci -m1
3638 3638
3639 3639 $ hg branch -q stable
3640 3640 $ echo stable2 >> a
3641 3641 $ hg ci -m2
3642 3642 $ echo stable3 >> a
3643 3643 $ hg ci -m3
3644 3644
3645 3645 $ hg update -q null
3646 3646 $ echo default4 >> a
3647 3647 $ hg ci -Aqm4
3648 3648 $ echo default5 >> a
3649 3649 $ hg ci -m5
3650 3650
3651 3651 "null" revision belongs to "default" branch (issue4683)
3652 3652
3653 3653 $ log 'branch(null)'
3654 3654 0
3655 3655 1
3656 3656 4
3657 3657 5
3658 3658
3659 3659 "null" revision belongs to "default" branch, but it shouldn't appear in set
3660 3660 unless explicitly specified (issue4682)
3661 3661
3662 3662 $ log 'children(branch(default))'
3663 3663 1
3664 3664 2
3665 3665 5
3666 3666
3667 3667 $ cd ..
3668 3668
3669 3669 test author/desc/keyword in problematic encoding
3670 3670 # unicode: cp932:
3671 3671 # u30A2 0x83 0x41(= 'A')
3672 3672 # u30C2 0x83 0x61(= 'a')
3673 3673
3674 3674 $ hg init problematicencoding
3675 3675 $ cd problematicencoding
3676 3676
3677 3677 $ python > setup.sh <<EOF
3678 3678 > print u'''
3679 3679 > echo a > text
3680 3680 > hg add text
3681 3681 > hg --encoding utf-8 commit -u '\u30A2' -m none
3682 3682 > echo b > text
3683 3683 > hg --encoding utf-8 commit -u '\u30C2' -m none
3684 3684 > echo c > text
3685 3685 > hg --encoding utf-8 commit -u none -m '\u30A2'
3686 3686 > echo d > text
3687 3687 > hg --encoding utf-8 commit -u none -m '\u30C2'
3688 3688 > '''.encode('utf-8')
3689 3689 > EOF
3690 3690 $ sh < setup.sh
3691 3691
3692 3692 test in problematic encoding
3693 3693 $ python > test.sh <<EOF
3694 3694 > print u'''
3695 3695 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
3696 3696 > echo ====
3697 3697 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
3698 3698 > echo ====
3699 3699 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
3700 3700 > echo ====
3701 3701 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
3702 3702 > echo ====
3703 3703 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
3704 3704 > echo ====
3705 3705 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
3706 3706 > '''.encode('cp932')
3707 3707 > EOF
3708 3708 $ sh < test.sh
3709 3709 0
3710 3710 ====
3711 3711 1
3712 3712 ====
3713 3713 2
3714 3714 ====
3715 3715 3
3716 3716 ====
3717 3717 0
3718 3718 2
3719 3719 ====
3720 3720 1
3721 3721 3
3722 3722
3723 3723 test error message of bad revset
3724 3724 $ hg log -r 'foo\\'
3725 3725 hg: parse error at 3: syntax error in revset 'foo\\'
3726 3726 [255]
3727 3727
3728 3728 $ cd ..
3729 3729
3730 3730 Test that revset predicate of extension isn't loaded at failure of
3731 3731 loading it
3732 3732
3733 3733 $ cd repo
3734 3734
3735 3735 $ cat <<EOF > $TESTTMP/custompredicate.py
3736 3736 > from mercurial import error, registrar, revset
3737 3737 >
3738 3738 > revsetpredicate = registrar.revsetpredicate()
3739 3739 >
3740 3740 > @revsetpredicate('custom1()')
3741 3741 > def custom1(repo, subset, x):
3742 3742 > return revset.baseset([1])
3743 3743 >
3744 3744 > raise error.Abort('intentional failure of loading extension')
3745 3745 > EOF
3746 3746 $ cat <<EOF > .hg/hgrc
3747 3747 > [extensions]
3748 3748 > custompredicate = $TESTTMP/custompredicate.py
3749 3749 > EOF
3750 3750
3751 3751 $ hg debugrevspec "custom1()"
3752 3752 *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension
3753 3753 hg: parse error: unknown identifier: custom1
3754 3754 [255]
3755 3755
3756 3756 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now