##// END OF EJS Templates
log: abort for -d '>' or -d '<' (issue2749)
Yun Lee -
r13867:28f557e8 default
parent child Browse files
Show More
@@ -1,1563 +1,1567 b''
1 1 # util.py - Mercurial utility functions and platform specfic 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 specfic 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 i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, stat, time, calendar, textwrap, unicodedata, signal
20 20 import imp, socket
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 if sys.version_info >= (2, 5):
32 32 from hashlib import sha1 as _sha1
33 33 else:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 39 import __builtin__
40 40
41 41 if sys.version_info[0] < 3:
42 42 def fakebuffer(sliceable, offset=0):
43 43 return sliceable[offset:]
44 44 else:
45 45 def fakebuffer(sliceable, offset=0):
46 46 return memoryview(sliceable)[offset:]
47 47 try:
48 48 buffer
49 49 except NameError:
50 50 __builtin__.buffer = fakebuffer
51 51
52 52 import subprocess
53 53 closefds = os.name == 'posix'
54 54
55 55 def popen2(cmd, env=None, newlines=False):
56 56 # Setting bufsize to -1 lets the system decide the buffer size.
57 57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 60 close_fds=closefds,
61 61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 62 universal_newlines=newlines,
63 63 env=env)
64 64 return p.stdin, p.stdout
65 65
66 66 def popen3(cmd, env=None, newlines=False):
67 67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 68 close_fds=closefds,
69 69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 70 stderr=subprocess.PIPE,
71 71 universal_newlines=newlines,
72 72 env=env)
73 73 return p.stdin, p.stdout, p.stderr
74 74
75 75 def version():
76 76 """Return version information if available."""
77 77 try:
78 78 import __version__
79 79 return __version__.version
80 80 except ImportError:
81 81 return 'unknown'
82 82
83 83 # used by parsedate
84 84 defaultdateformats = (
85 85 '%Y-%m-%d %H:%M:%S',
86 86 '%Y-%m-%d %I:%M:%S%p',
87 87 '%Y-%m-%d %H:%M',
88 88 '%Y-%m-%d %I:%M%p',
89 89 '%Y-%m-%d',
90 90 '%m-%d',
91 91 '%m/%d',
92 92 '%m/%d/%y',
93 93 '%m/%d/%Y',
94 94 '%a %b %d %H:%M:%S %Y',
95 95 '%a %b %d %I:%M:%S%p %Y',
96 96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 97 '%b %d %H:%M:%S %Y',
98 98 '%b %d %I:%M:%S%p %Y',
99 99 '%b %d %H:%M:%S',
100 100 '%b %d %I:%M:%S%p',
101 101 '%b %d %H:%M',
102 102 '%b %d %I:%M%p',
103 103 '%b %d %Y',
104 104 '%b %d',
105 105 '%H:%M:%S',
106 106 '%I:%M:%S%p',
107 107 '%H:%M',
108 108 '%I:%M%p',
109 109 )
110 110
111 111 extendeddateformats = defaultdateformats + (
112 112 "%Y",
113 113 "%Y-%m",
114 114 "%b",
115 115 "%b %Y",
116 116 )
117 117
118 118 def cachefunc(func):
119 119 '''cache the result of function calls'''
120 120 # XXX doesn't handle keywords args
121 121 cache = {}
122 122 if func.func_code.co_argcount == 1:
123 123 # we gain a small amount of time because
124 124 # we don't need to pack/unpack the list
125 125 def f(arg):
126 126 if arg not in cache:
127 127 cache[arg] = func(arg)
128 128 return cache[arg]
129 129 else:
130 130 def f(*args):
131 131 if args not in cache:
132 132 cache[args] = func(*args)
133 133 return cache[args]
134 134
135 135 return f
136 136
137 137 def lrucachefunc(func):
138 138 '''cache most recent results of function calls'''
139 139 cache = {}
140 140 order = []
141 141 if func.func_code.co_argcount == 1:
142 142 def f(arg):
143 143 if arg not in cache:
144 144 if len(cache) > 20:
145 145 del cache[order.pop(0)]
146 146 cache[arg] = func(arg)
147 147 else:
148 148 order.remove(arg)
149 149 order.append(arg)
150 150 return cache[arg]
151 151 else:
152 152 def f(*args):
153 153 if args not in cache:
154 154 if len(cache) > 20:
155 155 del cache[order.pop(0)]
156 156 cache[args] = func(*args)
157 157 else:
158 158 order.remove(args)
159 159 order.append(args)
160 160 return cache[args]
161 161
162 162 return f
163 163
164 164 class propertycache(object):
165 165 def __init__(self, func):
166 166 self.func = func
167 167 self.name = func.__name__
168 168 def __get__(self, obj, type=None):
169 169 result = self.func(obj)
170 170 setattr(obj, self.name, result)
171 171 return result
172 172
173 173 def pipefilter(s, cmd):
174 174 '''filter string S through command CMD, returning its output'''
175 175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 177 pout, perr = p.communicate(s)
178 178 return pout
179 179
180 180 def tempfilter(s, cmd):
181 181 '''filter string S through a pair of temporary files with CMD.
182 182 CMD is used as a template to create the real command to be run,
183 183 with the strings INFILE and OUTFILE replaced by the real names of
184 184 the temporary files generated.'''
185 185 inname, outname = None, None
186 186 try:
187 187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 188 fp = os.fdopen(infd, 'wb')
189 189 fp.write(s)
190 190 fp.close()
191 191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 192 os.close(outfd)
193 193 cmd = cmd.replace('INFILE', inname)
194 194 cmd = cmd.replace('OUTFILE', outname)
195 195 code = os.system(cmd)
196 196 if sys.platform == 'OpenVMS' and code & 1:
197 197 code = 0
198 198 if code:
199 199 raise Abort(_("command '%s' failed: %s") %
200 200 (cmd, explain_exit(code)))
201 201 fp = open(outname, 'rb')
202 202 r = fp.read()
203 203 fp.close()
204 204 return r
205 205 finally:
206 206 try:
207 207 if inname:
208 208 os.unlink(inname)
209 209 except:
210 210 pass
211 211 try:
212 212 if outname:
213 213 os.unlink(outname)
214 214 except:
215 215 pass
216 216
217 217 filtertable = {
218 218 'tempfile:': tempfilter,
219 219 'pipe:': pipefilter,
220 220 }
221 221
222 222 def filter(s, cmd):
223 223 "filter a string through a command that transforms its input to its output"
224 224 for name, fn in filtertable.iteritems():
225 225 if cmd.startswith(name):
226 226 return fn(s, cmd[len(name):].lstrip())
227 227 return pipefilter(s, cmd)
228 228
229 229 def binary(s):
230 230 """return true if a string is binary data"""
231 231 return bool(s and '\0' in s)
232 232
233 233 def increasingchunks(source, min=1024, max=65536):
234 234 '''return no less than min bytes per chunk while data remains,
235 235 doubling min after each chunk until it reaches max'''
236 236 def log2(x):
237 237 if not x:
238 238 return 0
239 239 i = 0
240 240 while x:
241 241 x >>= 1
242 242 i += 1
243 243 return i - 1
244 244
245 245 buf = []
246 246 blen = 0
247 247 for chunk in source:
248 248 buf.append(chunk)
249 249 blen += len(chunk)
250 250 if blen >= min:
251 251 if min < max:
252 252 min = min << 1
253 253 nmin = 1 << log2(blen)
254 254 if nmin > min:
255 255 min = nmin
256 256 if min > max:
257 257 min = max
258 258 yield ''.join(buf)
259 259 blen = 0
260 260 buf = []
261 261 if buf:
262 262 yield ''.join(buf)
263 263
264 264 Abort = error.Abort
265 265
266 266 def always(fn):
267 267 return True
268 268
269 269 def never(fn):
270 270 return False
271 271
272 272 def pathto(root, n1, n2):
273 273 '''return the relative path from one place to another.
274 274 root should use os.sep to separate directories
275 275 n1 should use os.sep to separate directories
276 276 n2 should use "/" to separate directories
277 277 returns an os.sep-separated path.
278 278
279 279 If n1 is a relative path, it's assumed it's
280 280 relative to root.
281 281 n2 should always be relative to root.
282 282 '''
283 283 if not n1:
284 284 return localpath(n2)
285 285 if os.path.isabs(n1):
286 286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 287 return os.path.join(root, localpath(n2))
288 288 n2 = '/'.join((pconvert(root), n2))
289 289 a, b = splitpath(n1), n2.split('/')
290 290 a.reverse()
291 291 b.reverse()
292 292 while a and b and a[-1] == b[-1]:
293 293 a.pop()
294 294 b.pop()
295 295 b.reverse()
296 296 return os.sep.join((['..'] * len(a)) + b) or '.'
297 297
298 298 def canonpath(root, cwd, myname, auditor=None):
299 299 """return the canonical path of myname, given cwd and root"""
300 300 if endswithsep(root):
301 301 rootsep = root
302 302 else:
303 303 rootsep = root + os.sep
304 304 name = myname
305 305 if not os.path.isabs(name):
306 306 name = os.path.join(root, cwd, name)
307 307 name = os.path.normpath(name)
308 308 if auditor is None:
309 309 auditor = path_auditor(root)
310 310 if name != rootsep and name.startswith(rootsep):
311 311 name = name[len(rootsep):]
312 312 auditor(name)
313 313 return pconvert(name)
314 314 elif name == root:
315 315 return ''
316 316 else:
317 317 # Determine whether `name' is in the hierarchy at or beneath `root',
318 318 # by iterating name=dirname(name) until that causes no change (can't
319 319 # check name == '/', because that doesn't work on windows). For each
320 320 # `name', compare dev/inode numbers. If they match, the list `rel'
321 321 # holds the reversed list of components making up the relative file
322 322 # name we want.
323 323 root_st = os.stat(root)
324 324 rel = []
325 325 while True:
326 326 try:
327 327 name_st = os.stat(name)
328 328 except OSError:
329 329 break
330 330 if samestat(name_st, root_st):
331 331 if not rel:
332 332 # name was actually the same as root (maybe a symlink)
333 333 return ''
334 334 rel.reverse()
335 335 name = os.path.join(*rel)
336 336 auditor(name)
337 337 return pconvert(name)
338 338 dirname, basename = os.path.split(name)
339 339 rel.append(basename)
340 340 if dirname == name:
341 341 break
342 342 name = dirname
343 343
344 344 raise Abort('%s not under root' % myname)
345 345
346 346 _hgexecutable = None
347 347
348 348 def main_is_frozen():
349 349 """return True if we are a frozen executable.
350 350
351 351 The code supports py2exe (most common, Windows only) and tools/freeze
352 352 (portable, not much used).
353 353 """
354 354 return (hasattr(sys, "frozen") or # new py2exe
355 355 hasattr(sys, "importers") or # old py2exe
356 356 imp.is_frozen("__main__")) # tools/freeze
357 357
358 358 def hgexecutable():
359 359 """return location of the 'hg' executable.
360 360
361 361 Defaults to $HG or 'hg' in the search path.
362 362 """
363 363 if _hgexecutable is None:
364 364 hg = os.environ.get('HG')
365 365 if hg:
366 366 set_hgexecutable(hg)
367 367 elif main_is_frozen():
368 368 set_hgexecutable(sys.executable)
369 369 else:
370 370 exe = find_exe('hg') or os.path.basename(sys.argv[0])
371 371 set_hgexecutable(exe)
372 372 return _hgexecutable
373 373
374 374 def set_hgexecutable(path):
375 375 """set location of the 'hg' executable"""
376 376 global _hgexecutable
377 377 _hgexecutable = path
378 378
379 379 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
380 380 '''enhanced shell command execution.
381 381 run with environment maybe modified, maybe in different dir.
382 382
383 383 if command fails and onerr is None, return status. if ui object,
384 384 print error message and return status, else raise onerr object as
385 385 exception.
386 386
387 387 if out is specified, it is assumed to be a file-like object that has a
388 388 write() method. stdout and stderr will be redirected to out.'''
389 389 try:
390 390 sys.stdout.flush()
391 391 except Exception:
392 392 pass
393 393 def py2shell(val):
394 394 'convert python object into string that is useful to shell'
395 395 if val is None or val is False:
396 396 return '0'
397 397 if val is True:
398 398 return '1'
399 399 return str(val)
400 400 origcmd = cmd
401 401 cmd = quotecommand(cmd)
402 402 env = dict(os.environ)
403 403 env.update((k, py2shell(v)) for k, v in environ.iteritems())
404 404 env['HG'] = hgexecutable()
405 405 if out is None:
406 406 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
407 407 env=env, cwd=cwd)
408 408 else:
409 409 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
410 410 env=env, cwd=cwd, stdout=subprocess.PIPE,
411 411 stderr=subprocess.STDOUT)
412 412 for line in proc.stdout:
413 413 out.write(line)
414 414 proc.wait()
415 415 rc = proc.returncode
416 416 if sys.platform == 'OpenVMS' and rc & 1:
417 417 rc = 0
418 418 if rc and onerr:
419 419 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
420 420 explain_exit(rc)[0])
421 421 if errprefix:
422 422 errmsg = '%s: %s' % (errprefix, errmsg)
423 423 try:
424 424 onerr.warn(errmsg + '\n')
425 425 except AttributeError:
426 426 raise onerr(errmsg)
427 427 return rc
428 428
429 429 def checksignature(func):
430 430 '''wrap a function with code to check for calling errors'''
431 431 def check(*args, **kwargs):
432 432 try:
433 433 return func(*args, **kwargs)
434 434 except TypeError:
435 435 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
436 436 raise error.SignatureError
437 437 raise
438 438
439 439 return check
440 440
441 441 def makedir(path, notindexed):
442 442 os.mkdir(path)
443 443
444 444 def unlinkpath(f):
445 445 """unlink and remove the directory if it is empty"""
446 446 os.unlink(f)
447 447 # try removing directories that might now be empty
448 448 try:
449 449 os.removedirs(os.path.dirname(f))
450 450 except OSError:
451 451 pass
452 452
453 453 def copyfile(src, dest):
454 454 "copy a file, preserving mode and atime/mtime"
455 455 if os.path.islink(src):
456 456 try:
457 457 os.unlink(dest)
458 458 except:
459 459 pass
460 460 os.symlink(os.readlink(src), dest)
461 461 else:
462 462 try:
463 463 shutil.copyfile(src, dest)
464 464 shutil.copymode(src, dest)
465 465 except shutil.Error, inst:
466 466 raise Abort(str(inst))
467 467
468 468 def copyfiles(src, dst, hardlink=None):
469 469 """Copy a directory tree using hardlinks if possible"""
470 470
471 471 if hardlink is None:
472 472 hardlink = (os.stat(src).st_dev ==
473 473 os.stat(os.path.dirname(dst)).st_dev)
474 474
475 475 num = 0
476 476 if os.path.isdir(src):
477 477 os.mkdir(dst)
478 478 for name, kind in osutil.listdir(src):
479 479 srcname = os.path.join(src, name)
480 480 dstname = os.path.join(dst, name)
481 481 hardlink, n = copyfiles(srcname, dstname, hardlink)
482 482 num += n
483 483 else:
484 484 if hardlink:
485 485 try:
486 486 os_link(src, dst)
487 487 except (IOError, OSError):
488 488 hardlink = False
489 489 shutil.copy(src, dst)
490 490 else:
491 491 shutil.copy(src, dst)
492 492 num += 1
493 493
494 494 return hardlink, num
495 495
496 496 class path_auditor(object):
497 497 '''ensure that a filesystem path contains no banned components.
498 498 the following properties of a path are checked:
499 499
500 500 - ends with a directory separator
501 501 - under top-level .hg
502 502 - starts at the root of a windows drive
503 503 - contains ".."
504 504 - traverses a symlink (e.g. a/symlink_here/b)
505 505 - inside a nested repository (a callback can be used to approve
506 506 some nested repositories, e.g., subrepositories)
507 507 '''
508 508
509 509 def __init__(self, root, callback=None):
510 510 self.audited = set()
511 511 self.auditeddir = set()
512 512 self.root = root
513 513 self.callback = callback
514 514
515 515 def __call__(self, path):
516 516 if path in self.audited:
517 517 return
518 518 # AIX ignores "/" at end of path, others raise EISDIR.
519 519 if endswithsep(path):
520 520 raise Abort(_("path ends in directory separator: %s") % path)
521 521 normpath = os.path.normcase(path)
522 522 parts = splitpath(normpath)
523 523 if (os.path.splitdrive(path)[0]
524 524 or parts[0].lower() in ('.hg', '.hg.', '')
525 525 or os.pardir in parts):
526 526 raise Abort(_("path contains illegal component: %s") % path)
527 527 if '.hg' in path.lower():
528 528 lparts = [p.lower() for p in parts]
529 529 for p in '.hg', '.hg.':
530 530 if p in lparts[1:]:
531 531 pos = lparts.index(p)
532 532 base = os.path.join(*parts[:pos])
533 533 raise Abort(_('path %r is inside repo %r') % (path, base))
534 534 def check(prefix):
535 535 curpath = os.path.join(self.root, prefix)
536 536 try:
537 537 st = os.lstat(curpath)
538 538 except OSError, err:
539 539 # EINVAL can be raised as invalid path syntax under win32.
540 540 # They must be ignored for patterns can be checked too.
541 541 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
542 542 raise
543 543 else:
544 544 if stat.S_ISLNK(st.st_mode):
545 545 raise Abort(_('path %r traverses symbolic link %r') %
546 546 (path, prefix))
547 547 elif (stat.S_ISDIR(st.st_mode) and
548 548 os.path.isdir(os.path.join(curpath, '.hg'))):
549 549 if not self.callback or not self.callback(curpath):
550 550 raise Abort(_('path %r is inside repo %r') %
551 551 (path, prefix))
552 552 parts.pop()
553 553 prefixes = []
554 554 while parts:
555 555 prefix = os.sep.join(parts)
556 556 if prefix in self.auditeddir:
557 557 break
558 558 check(prefix)
559 559 prefixes.append(prefix)
560 560 parts.pop()
561 561
562 562 self.audited.add(path)
563 563 # only add prefixes to the cache after checking everything: we don't
564 564 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
565 565 self.auditeddir.update(prefixes)
566 566
567 567 def lookup_reg(key, name=None, scope=None):
568 568 return None
569 569
570 570 def hidewindow():
571 571 """Hide current shell window.
572 572
573 573 Used to hide the window opened when starting asynchronous
574 574 child process under Windows, unneeded on other systems.
575 575 """
576 576 pass
577 577
578 578 if os.name == 'nt':
579 579 from windows import *
580 580 else:
581 581 from posix import *
582 582
583 583 def makelock(info, pathname):
584 584 try:
585 585 return os.symlink(info, pathname)
586 586 except OSError, why:
587 587 if why.errno == errno.EEXIST:
588 588 raise
589 589 except AttributeError: # no symlink in os
590 590 pass
591 591
592 592 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
593 593 os.write(ld, info)
594 594 os.close(ld)
595 595
596 596 def readlock(pathname):
597 597 try:
598 598 return os.readlink(pathname)
599 599 except OSError, why:
600 600 if why.errno not in (errno.EINVAL, errno.ENOSYS):
601 601 raise
602 602 except AttributeError: # no symlink in os
603 603 pass
604 604 fp = posixfile(pathname)
605 605 r = fp.read()
606 606 fp.close()
607 607 return r
608 608
609 609 def fstat(fp):
610 610 '''stat file object that may not have fileno method.'''
611 611 try:
612 612 return os.fstat(fp.fileno())
613 613 except AttributeError:
614 614 return os.stat(fp.name)
615 615
616 616 # File system features
617 617
618 618 def checkcase(path):
619 619 """
620 620 Check whether the given path is on a case-sensitive filesystem
621 621
622 622 Requires a path (like /foo/.hg) ending with a foldable final
623 623 directory component.
624 624 """
625 625 s1 = os.stat(path)
626 626 d, b = os.path.split(path)
627 627 p2 = os.path.join(d, b.upper())
628 628 if path == p2:
629 629 p2 = os.path.join(d, b.lower())
630 630 try:
631 631 s2 = os.stat(p2)
632 632 if s2 == s1:
633 633 return False
634 634 return True
635 635 except:
636 636 return True
637 637
638 638 _fspathcache = {}
639 639 def fspath(name, root):
640 640 '''Get name in the case stored in the filesystem
641 641
642 642 The name is either relative to root, or it is an absolute path starting
643 643 with root. Note that this function is unnecessary, and should not be
644 644 called, for case-sensitive filesystems (simply because it's expensive).
645 645 '''
646 646 # If name is absolute, make it relative
647 647 if name.lower().startswith(root.lower()):
648 648 l = len(root)
649 649 if name[l] == os.sep or name[l] == os.altsep:
650 650 l = l + 1
651 651 name = name[l:]
652 652
653 653 if not os.path.lexists(os.path.join(root, name)):
654 654 return None
655 655
656 656 seps = os.sep
657 657 if os.altsep:
658 658 seps = seps + os.altsep
659 659 # Protect backslashes. This gets silly very quickly.
660 660 seps.replace('\\','\\\\')
661 661 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
662 662 dir = os.path.normcase(os.path.normpath(root))
663 663 result = []
664 664 for part, sep in pattern.findall(name):
665 665 if sep:
666 666 result.append(sep)
667 667 continue
668 668
669 669 if dir not in _fspathcache:
670 670 _fspathcache[dir] = os.listdir(dir)
671 671 contents = _fspathcache[dir]
672 672
673 673 lpart = part.lower()
674 674 lenp = len(part)
675 675 for n in contents:
676 676 if lenp == len(n) and n.lower() == lpart:
677 677 result.append(n)
678 678 break
679 679 else:
680 680 # Cannot happen, as the file exists!
681 681 result.append(part)
682 682 dir = os.path.join(dir, lpart)
683 683
684 684 return ''.join(result)
685 685
686 686 def checkexec(path):
687 687 """
688 688 Check whether the given path is on a filesystem with UNIX-like exec flags
689 689
690 690 Requires a directory (like /foo/.hg)
691 691 """
692 692
693 693 # VFAT on some Linux versions can flip mode but it doesn't persist
694 694 # a FS remount. Frequently we can detect it if files are created
695 695 # with exec bit on.
696 696
697 697 try:
698 698 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
699 699 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
700 700 try:
701 701 os.close(fh)
702 702 m = os.stat(fn).st_mode & 0777
703 703 new_file_has_exec = m & EXECFLAGS
704 704 os.chmod(fn, m ^ EXECFLAGS)
705 705 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
706 706 finally:
707 707 os.unlink(fn)
708 708 except (IOError, OSError):
709 709 # we don't care, the user probably won't be able to commit anyway
710 710 return False
711 711 return not (new_file_has_exec or exec_flags_cannot_flip)
712 712
713 713 def checklink(path):
714 714 """check whether the given path is on a symlink-capable filesystem"""
715 715 # mktemp is not racy because symlink creation will fail if the
716 716 # file already exists
717 717 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
718 718 try:
719 719 os.symlink(".", name)
720 720 os.unlink(name)
721 721 return True
722 722 except (OSError, AttributeError):
723 723 return False
724 724
725 725 def checknlink(testfile):
726 726 '''check whether hardlink count reporting works properly'''
727 727
728 728 # testfile may be open, so we need a separate file for checking to
729 729 # work around issue2543 (or testfile may get lost on Samba shares)
730 730 f1 = testfile + ".hgtmp1"
731 731 if os.path.lexists(f1):
732 732 return False
733 733 try:
734 734 posixfile(f1, 'w').close()
735 735 except IOError:
736 736 return False
737 737
738 738 f2 = testfile + ".hgtmp2"
739 739 fd = None
740 740 try:
741 741 try:
742 742 os_link(f1, f2)
743 743 except OSError:
744 744 return False
745 745
746 746 # nlinks() may behave differently for files on Windows shares if
747 747 # the file is open.
748 748 fd = posixfile(f2)
749 749 return nlinks(f2) > 1
750 750 finally:
751 751 if fd is not None:
752 752 fd.close()
753 753 for f in (f1, f2):
754 754 try:
755 755 os.unlink(f)
756 756 except OSError:
757 757 pass
758 758
759 759 return False
760 760
761 761 def endswithsep(path):
762 762 '''Check path ends with os.sep or os.altsep.'''
763 763 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
764 764
765 765 def splitpath(path):
766 766 '''Split path by os.sep.
767 767 Note that this function does not use os.altsep because this is
768 768 an alternative of simple "xxx.split(os.sep)".
769 769 It is recommended to use os.path.normpath() before using this
770 770 function if need.'''
771 771 return path.split(os.sep)
772 772
773 773 def gui():
774 774 '''Are we running in a GUI?'''
775 775 if sys.platform == 'darwin':
776 776 if 'SSH_CONNECTION' in os.environ:
777 777 # handle SSH access to a box where the user is logged in
778 778 return False
779 779 elif getattr(osutil, 'isgui', None):
780 780 # check if a CoreGraphics session is available
781 781 return osutil.isgui()
782 782 else:
783 783 # pure build; use a safe default
784 784 return True
785 785 else:
786 786 return os.name == "nt" or os.environ.get("DISPLAY")
787 787
788 788 def mktempcopy(name, emptyok=False, createmode=None):
789 789 """Create a temporary file with the same contents from name
790 790
791 791 The permission bits are copied from the original file.
792 792
793 793 If the temporary file is going to be truncated immediately, you
794 794 can use emptyok=True as an optimization.
795 795
796 796 Returns the name of the temporary file.
797 797 """
798 798 d, fn = os.path.split(name)
799 799 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
800 800 os.close(fd)
801 801 # Temporary files are created with mode 0600, which is usually not
802 802 # what we want. If the original file already exists, just copy
803 803 # its mode. Otherwise, manually obey umask.
804 804 try:
805 805 st_mode = os.lstat(name).st_mode & 0777
806 806 except OSError, inst:
807 807 if inst.errno != errno.ENOENT:
808 808 raise
809 809 st_mode = createmode
810 810 if st_mode is None:
811 811 st_mode = ~umask
812 812 st_mode &= 0666
813 813 os.chmod(temp, st_mode)
814 814 if emptyok:
815 815 return temp
816 816 try:
817 817 try:
818 818 ifp = posixfile(name, "rb")
819 819 except IOError, inst:
820 820 if inst.errno == errno.ENOENT:
821 821 return temp
822 822 if not getattr(inst, 'filename', None):
823 823 inst.filename = name
824 824 raise
825 825 ofp = posixfile(temp, "wb")
826 826 for chunk in filechunkiter(ifp):
827 827 ofp.write(chunk)
828 828 ifp.close()
829 829 ofp.close()
830 830 except:
831 831 try: os.unlink(temp)
832 832 except: pass
833 833 raise
834 834 return temp
835 835
836 836 class atomictempfile(object):
837 837 """file-like object that atomically updates a file
838 838
839 839 All writes will be redirected to a temporary copy of the original
840 840 file. When rename is called, the copy is renamed to the original
841 841 name, making the changes visible.
842 842 """
843 843 def __init__(self, name, mode='w+b', createmode=None):
844 844 self.__name = name
845 845 self._fp = None
846 846 self.temp = mktempcopy(name, emptyok=('w' in mode),
847 847 createmode=createmode)
848 848 self._fp = posixfile(self.temp, mode)
849 849
850 850 def __getattr__(self, name):
851 851 return getattr(self._fp, name)
852 852
853 853 def rename(self):
854 854 if not self._fp.closed:
855 855 self._fp.close()
856 856 rename(self.temp, localpath(self.__name))
857 857
858 858 def close(self):
859 859 if not self._fp:
860 860 return
861 861 if not self._fp.closed:
862 862 try:
863 863 os.unlink(self.temp)
864 864 except: pass
865 865 self._fp.close()
866 866
867 867 def __del__(self):
868 868 self.close()
869 869
870 870 def makedirs(name, mode=None):
871 871 """recursive directory creation with parent mode inheritance"""
872 872 parent = os.path.abspath(os.path.dirname(name))
873 873 try:
874 874 os.mkdir(name)
875 875 if mode is not None:
876 876 os.chmod(name, mode)
877 877 return
878 878 except OSError, err:
879 879 if err.errno == errno.EEXIST:
880 880 return
881 881 if not name or parent == name or err.errno != errno.ENOENT:
882 882 raise
883 883 makedirs(parent, mode)
884 884 makedirs(name, mode)
885 885
886 886 class opener(object):
887 887 """Open files relative to a base directory
888 888
889 889 This class is used to hide the details of COW semantics and
890 890 remote file access from higher level code.
891 891 """
892 892 def __init__(self, base, audit=True):
893 893 self.base = base
894 894 if audit:
895 895 self.auditor = path_auditor(base)
896 896 else:
897 897 self.auditor = always
898 898 self.createmode = None
899 899 self._trustnlink = None
900 900
901 901 @propertycache
902 902 def _can_symlink(self):
903 903 return checklink(self.base)
904 904
905 905 def _fixfilemode(self, name):
906 906 if self.createmode is None:
907 907 return
908 908 os.chmod(name, self.createmode & 0666)
909 909
910 910 def __call__(self, path, mode="r", text=False, atomictemp=False):
911 911 self.auditor(path)
912 912 f = os.path.join(self.base, path)
913 913
914 914 if not text and "b" not in mode:
915 915 mode += "b" # for that other OS
916 916
917 917 nlink = -1
918 918 dirname, basename = os.path.split(f)
919 919 # If basename is empty, then the path is malformed because it points
920 920 # to a directory. Let the posixfile() call below raise IOError.
921 921 if basename and mode not in ('r', 'rb'):
922 922 if atomictemp:
923 923 if not os.path.isdir(dirname):
924 924 makedirs(dirname, self.createmode)
925 925 return atomictempfile(f, mode, self.createmode)
926 926 try:
927 927 if 'w' in mode:
928 928 unlink(f)
929 929 nlink = 0
930 930 else:
931 931 # nlinks() may behave differently for files on Windows
932 932 # shares if the file is open.
933 933 fd = posixfile(f)
934 934 nlink = nlinks(f)
935 935 if nlink < 1:
936 936 nlink = 2 # force mktempcopy (issue1922)
937 937 fd.close()
938 938 except (OSError, IOError), e:
939 939 if e.errno != errno.ENOENT:
940 940 raise
941 941 nlink = 0
942 942 if not os.path.isdir(dirname):
943 943 makedirs(dirname, self.createmode)
944 944 if nlink > 0:
945 945 if self._trustnlink is None:
946 946 self._trustnlink = nlink > 1 or checknlink(f)
947 947 if nlink > 1 or not self._trustnlink:
948 948 rename(mktempcopy(f), f)
949 949 fp = posixfile(f, mode)
950 950 if nlink == 0:
951 951 self._fixfilemode(f)
952 952 return fp
953 953
954 954 def symlink(self, src, dst):
955 955 self.auditor(dst)
956 956 linkname = os.path.join(self.base, dst)
957 957 try:
958 958 os.unlink(linkname)
959 959 except OSError:
960 960 pass
961 961
962 962 dirname = os.path.dirname(linkname)
963 963 if not os.path.exists(dirname):
964 964 makedirs(dirname, self.createmode)
965 965
966 966 if self._can_symlink:
967 967 try:
968 968 os.symlink(src, linkname)
969 969 except OSError, err:
970 970 raise OSError(err.errno, _('could not symlink to %r: %s') %
971 971 (src, err.strerror), linkname)
972 972 else:
973 973 f = self(dst, "w")
974 974 f.write(src)
975 975 f.close()
976 976 self._fixfilemode(dst)
977 977
978 978 class chunkbuffer(object):
979 979 """Allow arbitrary sized chunks of data to be efficiently read from an
980 980 iterator over chunks of arbitrary size."""
981 981
982 982 def __init__(self, in_iter):
983 983 """in_iter is the iterator that's iterating over the input chunks.
984 984 targetsize is how big a buffer to try to maintain."""
985 985 def splitbig(chunks):
986 986 for chunk in chunks:
987 987 if len(chunk) > 2**20:
988 988 pos = 0
989 989 while pos < len(chunk):
990 990 end = pos + 2 ** 18
991 991 yield chunk[pos:end]
992 992 pos = end
993 993 else:
994 994 yield chunk
995 995 self.iter = splitbig(in_iter)
996 996 self._queue = []
997 997
998 998 def read(self, l):
999 999 """Read L bytes of data from the iterator of chunks of data.
1000 1000 Returns less than L bytes if the iterator runs dry."""
1001 1001 left = l
1002 1002 buf = ''
1003 1003 queue = self._queue
1004 1004 while left > 0:
1005 1005 # refill the queue
1006 1006 if not queue:
1007 1007 target = 2**18
1008 1008 for chunk in self.iter:
1009 1009 queue.append(chunk)
1010 1010 target -= len(chunk)
1011 1011 if target <= 0:
1012 1012 break
1013 1013 if not queue:
1014 1014 break
1015 1015
1016 1016 chunk = queue.pop(0)
1017 1017 left -= len(chunk)
1018 1018 if left < 0:
1019 1019 queue.insert(0, chunk[left:])
1020 1020 buf += chunk[:left]
1021 1021 else:
1022 1022 buf += chunk
1023 1023
1024 1024 return buf
1025 1025
1026 1026 def filechunkiter(f, size=65536, limit=None):
1027 1027 """Create a generator that produces the data in the file size
1028 1028 (default 65536) bytes at a time, up to optional limit (default is
1029 1029 to read all data). Chunks may be less than size bytes if the
1030 1030 chunk is the last chunk in the file, or the file is a socket or
1031 1031 some other type of file that sometimes reads less data than is
1032 1032 requested."""
1033 1033 assert size >= 0
1034 1034 assert limit is None or limit >= 0
1035 1035 while True:
1036 1036 if limit is None:
1037 1037 nbytes = size
1038 1038 else:
1039 1039 nbytes = min(limit, size)
1040 1040 s = nbytes and f.read(nbytes)
1041 1041 if not s:
1042 1042 break
1043 1043 if limit:
1044 1044 limit -= len(s)
1045 1045 yield s
1046 1046
1047 1047 def makedate():
1048 1048 lt = time.localtime()
1049 1049 if lt[8] == 1 and time.daylight:
1050 1050 tz = time.altzone
1051 1051 else:
1052 1052 tz = time.timezone
1053 1053 t = time.mktime(lt)
1054 1054 if t < 0:
1055 1055 hint = _("check your clock")
1056 1056 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1057 1057 return t, tz
1058 1058
1059 1059 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1060 1060 """represent a (unixtime, offset) tuple as a localized time.
1061 1061 unixtime is seconds since the epoch, and offset is the time zone's
1062 1062 number of seconds away from UTC. if timezone is false, do not
1063 1063 append time zone to string."""
1064 1064 t, tz = date or makedate()
1065 1065 if t < 0:
1066 1066 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1067 1067 tz = 0
1068 1068 if "%1" in format or "%2" in format:
1069 1069 sign = (tz > 0) and "-" or "+"
1070 1070 minutes = abs(tz) // 60
1071 1071 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1072 1072 format = format.replace("%2", "%02d" % (minutes % 60))
1073 1073 s = time.strftime(format, time.gmtime(float(t) - tz))
1074 1074 return s
1075 1075
1076 1076 def shortdate(date=None):
1077 1077 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1078 1078 return datestr(date, format='%Y-%m-%d')
1079 1079
1080 1080 def strdate(string, format, defaults=[]):
1081 1081 """parse a localized time string and return a (unixtime, offset) tuple.
1082 1082 if the string cannot be parsed, ValueError is raised."""
1083 1083 def timezone(string):
1084 1084 tz = string.split()[-1]
1085 1085 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1086 1086 sign = (tz[0] == "+") and 1 or -1
1087 1087 hours = int(tz[1:3])
1088 1088 minutes = int(tz[3:5])
1089 1089 return -sign * (hours * 60 + minutes) * 60
1090 1090 if tz == "GMT" or tz == "UTC":
1091 1091 return 0
1092 1092 return None
1093 1093
1094 1094 # NOTE: unixtime = localunixtime + offset
1095 1095 offset, date = timezone(string), string
1096 1096 if offset is not None:
1097 1097 date = " ".join(string.split()[:-1])
1098 1098
1099 1099 # add missing elements from defaults
1100 1100 usenow = False # default to using biased defaults
1101 1101 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1102 1102 found = [True for p in part if ("%"+p) in format]
1103 1103 if not found:
1104 1104 date += "@" + defaults[part][usenow]
1105 1105 format += "@%" + part[0]
1106 1106 else:
1107 1107 # We've found a specific time element, less specific time
1108 1108 # elements are relative to today
1109 1109 usenow = True
1110 1110
1111 1111 timetuple = time.strptime(date, format)
1112 1112 localunixtime = int(calendar.timegm(timetuple))
1113 1113 if offset is None:
1114 1114 # local timezone
1115 1115 unixtime = int(time.mktime(timetuple))
1116 1116 offset = unixtime - localunixtime
1117 1117 else:
1118 1118 unixtime = localunixtime + offset
1119 1119 return unixtime, offset
1120 1120
1121 1121 def parsedate(date, formats=None, bias={}):
1122 1122 """parse a localized date/time and return a (unixtime, offset) tuple.
1123 1123
1124 1124 The date may be a "unixtime offset" string or in one of the specified
1125 1125 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1126 1126 """
1127 1127 if not date:
1128 1128 return 0, 0
1129 1129 if isinstance(date, tuple) and len(date) == 2:
1130 1130 return date
1131 1131 if not formats:
1132 1132 formats = defaultdateformats
1133 1133 date = date.strip()
1134 1134 try:
1135 1135 when, offset = map(int, date.split(' '))
1136 1136 except ValueError:
1137 1137 # fill out defaults
1138 1138 now = makedate()
1139 1139 defaults = {}
1140 1140 nowmap = {}
1141 1141 for part in ("d", "mb", "yY", "HI", "M", "S"):
1142 1142 # this piece is for rounding the specific end of unknowns
1143 1143 b = bias.get(part)
1144 1144 if b is None:
1145 1145 if part[0] in "HMS":
1146 1146 b = "00"
1147 1147 else:
1148 1148 b = "0"
1149 1149
1150 1150 # this piece is for matching the generic end to today's date
1151 1151 n = datestr(now, "%" + part[0])
1152 1152
1153 1153 defaults[part] = (b, n)
1154 1154
1155 1155 for format in formats:
1156 1156 try:
1157 1157 when, offset = strdate(date, format, defaults)
1158 1158 except (ValueError, OverflowError):
1159 1159 pass
1160 1160 else:
1161 1161 break
1162 1162 else:
1163 1163 raise Abort(_('invalid date: %r') % date)
1164 1164 # validate explicit (probably user-specified) date and
1165 1165 # time zone offset. values must fit in signed 32 bits for
1166 1166 # current 32-bit linux runtimes. timezones go from UTC-12
1167 1167 # to UTC+14
1168 1168 if abs(when) > 0x7fffffff:
1169 1169 raise Abort(_('date exceeds 32 bits: %d') % when)
1170 1170 if when < 0:
1171 1171 raise Abort(_('negative date value: %d') % when)
1172 1172 if offset < -50400 or offset > 43200:
1173 1173 raise Abort(_('impossible time zone offset: %d') % offset)
1174 1174 return when, offset
1175 1175
1176 1176 def matchdate(date):
1177 1177 """Return a function that matches a given date match specifier
1178 1178
1179 1179 Formats include:
1180 1180
1181 1181 '{date}' match a given date to the accuracy provided
1182 1182
1183 1183 '<{date}' on or before a given date
1184 1184
1185 1185 '>{date}' on or after a given date
1186 1186
1187 1187 >>> p1 = parsedate("10:29:59")
1188 1188 >>> p2 = parsedate("10:30:00")
1189 1189 >>> p3 = parsedate("10:30:59")
1190 1190 >>> p4 = parsedate("10:31:00")
1191 1191 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1192 1192 >>> f = matchdate("10:30")
1193 1193 >>> f(p1[0])
1194 1194 False
1195 1195 >>> f(p2[0])
1196 1196 True
1197 1197 >>> f(p3[0])
1198 1198 True
1199 1199 >>> f(p4[0])
1200 1200 False
1201 1201 >>> f(p5[0])
1202 1202 False
1203 1203 """
1204 1204
1205 1205 def lower(date):
1206 1206 d = dict(mb="1", d="1")
1207 1207 return parsedate(date, extendeddateformats, d)[0]
1208 1208
1209 1209 def upper(date):
1210 1210 d = dict(mb="12", HI="23", M="59", S="59")
1211 1211 for days in ("31", "30", "29"):
1212 1212 try:
1213 1213 d["d"] = days
1214 1214 return parsedate(date, extendeddateformats, d)[0]
1215 1215 except:
1216 1216 pass
1217 1217 d["d"] = "28"
1218 1218 return parsedate(date, extendeddateformats, d)[0]
1219 1219
1220 1220 date = date.strip()
1221 1221
1222 1222 if not date:
1223 1223 raise Abort(_("dates cannot consist entirely of whitespace"))
1224 1224 elif date[0] == "<":
1225 if not date[1:]:
1226 raise Abort(_("invalid day spec. use '<{datetime}' "))
1225 1227 when = upper(date[1:])
1226 1228 return lambda x: x <= when
1227 1229 elif date[0] == ">":
1230 if not date[1:]:
1231 raise Abort(_("invalid day spec. use '>{datetime}' "))
1228 1232 when = lower(date[1:])
1229 1233 return lambda x: x >= when
1230 1234 elif date[0] == "-":
1231 1235 try:
1232 1236 days = int(date[1:])
1233 1237 except ValueError:
1234 1238 raise Abort(_("invalid day spec: %s") % date[1:])
1235 1239 when = makedate()[0] - days * 3600 * 24
1236 1240 return lambda x: x >= when
1237 1241 elif " to " in date:
1238 1242 a, b = date.split(" to ")
1239 1243 start, stop = lower(a), upper(b)
1240 1244 return lambda x: x >= start and x <= stop
1241 1245 else:
1242 1246 start, stop = lower(date), upper(date)
1243 1247 return lambda x: x >= start and x <= stop
1244 1248
1245 1249 def shortuser(user):
1246 1250 """Return a short representation of a user name or email address."""
1247 1251 f = user.find('@')
1248 1252 if f >= 0:
1249 1253 user = user[:f]
1250 1254 f = user.find('<')
1251 1255 if f >= 0:
1252 1256 user = user[f + 1:]
1253 1257 f = user.find(' ')
1254 1258 if f >= 0:
1255 1259 user = user[:f]
1256 1260 f = user.find('.')
1257 1261 if f >= 0:
1258 1262 user = user[:f]
1259 1263 return user
1260 1264
1261 1265 def email(author):
1262 1266 '''get email of author.'''
1263 1267 r = author.find('>')
1264 1268 if r == -1:
1265 1269 r = None
1266 1270 return author[author.find('<') + 1:r]
1267 1271
1268 1272 def _ellipsis(text, maxlength):
1269 1273 if len(text) <= maxlength:
1270 1274 return text, False
1271 1275 else:
1272 1276 return "%s..." % (text[:maxlength - 3]), True
1273 1277
1274 1278 def ellipsis(text, maxlength=400):
1275 1279 """Trim string to at most maxlength (default: 400) characters."""
1276 1280 try:
1277 1281 # use unicode not to split at intermediate multi-byte sequence
1278 1282 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1279 1283 maxlength)
1280 1284 if not truncated:
1281 1285 return text
1282 1286 return utext.encode(encoding.encoding)
1283 1287 except (UnicodeDecodeError, UnicodeEncodeError):
1284 1288 return _ellipsis(text, maxlength)[0]
1285 1289
1286 1290 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1287 1291 '''yield every hg repository under path, recursively.'''
1288 1292 def errhandler(err):
1289 1293 if err.filename == path:
1290 1294 raise err
1291 1295 if followsym and hasattr(os.path, 'samestat'):
1292 1296 def _add_dir_if_not_there(dirlst, dirname):
1293 1297 match = False
1294 1298 samestat = os.path.samestat
1295 1299 dirstat = os.stat(dirname)
1296 1300 for lstdirstat in dirlst:
1297 1301 if samestat(dirstat, lstdirstat):
1298 1302 match = True
1299 1303 break
1300 1304 if not match:
1301 1305 dirlst.append(dirstat)
1302 1306 return not match
1303 1307 else:
1304 1308 followsym = False
1305 1309
1306 1310 if (seen_dirs is None) and followsym:
1307 1311 seen_dirs = []
1308 1312 _add_dir_if_not_there(seen_dirs, path)
1309 1313 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1310 1314 dirs.sort()
1311 1315 if '.hg' in dirs:
1312 1316 yield root # found a repository
1313 1317 qroot = os.path.join(root, '.hg', 'patches')
1314 1318 if os.path.isdir(os.path.join(qroot, '.hg')):
1315 1319 yield qroot # we have a patch queue repo here
1316 1320 if recurse:
1317 1321 # avoid recursing inside the .hg directory
1318 1322 dirs.remove('.hg')
1319 1323 else:
1320 1324 dirs[:] = [] # don't descend further
1321 1325 elif followsym:
1322 1326 newdirs = []
1323 1327 for d in dirs:
1324 1328 fname = os.path.join(root, d)
1325 1329 if _add_dir_if_not_there(seen_dirs, fname):
1326 1330 if os.path.islink(fname):
1327 1331 for hgname in walkrepos(fname, True, seen_dirs):
1328 1332 yield hgname
1329 1333 else:
1330 1334 newdirs.append(d)
1331 1335 dirs[:] = newdirs
1332 1336
1333 1337 _rcpath = None
1334 1338
1335 1339 def os_rcpath():
1336 1340 '''return default os-specific hgrc search path'''
1337 1341 path = system_rcpath()
1338 1342 path.extend(user_rcpath())
1339 1343 path = [os.path.normpath(f) for f in path]
1340 1344 return path
1341 1345
1342 1346 def rcpath():
1343 1347 '''return hgrc search path. if env var HGRCPATH is set, use it.
1344 1348 for each item in path, if directory, use files ending in .rc,
1345 1349 else use item.
1346 1350 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1347 1351 if no HGRCPATH, use default os-specific path.'''
1348 1352 global _rcpath
1349 1353 if _rcpath is None:
1350 1354 if 'HGRCPATH' in os.environ:
1351 1355 _rcpath = []
1352 1356 for p in os.environ['HGRCPATH'].split(os.pathsep):
1353 1357 if not p:
1354 1358 continue
1355 1359 p = expandpath(p)
1356 1360 if os.path.isdir(p):
1357 1361 for f, kind in osutil.listdir(p):
1358 1362 if f.endswith('.rc'):
1359 1363 _rcpath.append(os.path.join(p, f))
1360 1364 else:
1361 1365 _rcpath.append(p)
1362 1366 else:
1363 1367 _rcpath = os_rcpath()
1364 1368 return _rcpath
1365 1369
1366 1370 def bytecount(nbytes):
1367 1371 '''return byte count formatted as readable string, with units'''
1368 1372
1369 1373 units = (
1370 1374 (100, 1 << 30, _('%.0f GB')),
1371 1375 (10, 1 << 30, _('%.1f GB')),
1372 1376 (1, 1 << 30, _('%.2f GB')),
1373 1377 (100, 1 << 20, _('%.0f MB')),
1374 1378 (10, 1 << 20, _('%.1f MB')),
1375 1379 (1, 1 << 20, _('%.2f MB')),
1376 1380 (100, 1 << 10, _('%.0f KB')),
1377 1381 (10, 1 << 10, _('%.1f KB')),
1378 1382 (1, 1 << 10, _('%.2f KB')),
1379 1383 (1, 1, _('%.0f bytes')),
1380 1384 )
1381 1385
1382 1386 for multiplier, divisor, format in units:
1383 1387 if nbytes >= divisor * multiplier:
1384 1388 return format % (nbytes / float(divisor))
1385 1389 return units[-1][2] % nbytes
1386 1390
1387 1391 def uirepr(s):
1388 1392 # Avoid double backslash in Windows path repr()
1389 1393 return repr(s).replace('\\\\', '\\')
1390 1394
1391 1395 # delay import of textwrap
1392 1396 def MBTextWrapper(**kwargs):
1393 1397 class tw(textwrap.TextWrapper):
1394 1398 """
1395 1399 Extend TextWrapper for double-width characters.
1396 1400
1397 1401 Some Asian characters use two terminal columns instead of one.
1398 1402 A good example of this behavior can be seen with u'\u65e5\u672c',
1399 1403 the two Japanese characters for "Japan":
1400 1404 len() returns 2, but when printed to a terminal, they eat 4 columns.
1401 1405
1402 1406 (Note that this has nothing to do whatsoever with unicode
1403 1407 representation, or encoding of the underlying string)
1404 1408 """
1405 1409 def __init__(self, **kwargs):
1406 1410 textwrap.TextWrapper.__init__(self, **kwargs)
1407 1411
1408 1412 def _cutdown(self, str, space_left):
1409 1413 l = 0
1410 1414 ucstr = unicode(str, encoding.encoding)
1411 1415 colwidth = unicodedata.east_asian_width
1412 1416 for i in xrange(len(ucstr)):
1413 1417 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1414 1418 if space_left < l:
1415 1419 return (ucstr[:i].encode(encoding.encoding),
1416 1420 ucstr[i:].encode(encoding.encoding))
1417 1421 return str, ''
1418 1422
1419 1423 # overriding of base class
1420 1424 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1421 1425 space_left = max(width - cur_len, 1)
1422 1426
1423 1427 if self.break_long_words:
1424 1428 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1425 1429 cur_line.append(cut)
1426 1430 reversed_chunks[-1] = res
1427 1431 elif not cur_line:
1428 1432 cur_line.append(reversed_chunks.pop())
1429 1433
1430 1434 global MBTextWrapper
1431 1435 MBTextWrapper = tw
1432 1436 return tw(**kwargs)
1433 1437
1434 1438 def wrap(line, width, initindent='', hangindent=''):
1435 1439 maxindent = max(len(hangindent), len(initindent))
1436 1440 if width <= maxindent:
1437 1441 # adjust for weird terminal size
1438 1442 width = max(78, maxindent + 1)
1439 1443 wrapper = MBTextWrapper(width=width,
1440 1444 initial_indent=initindent,
1441 1445 subsequent_indent=hangindent)
1442 1446 return wrapper.fill(line)
1443 1447
1444 1448 def iterlines(iterator):
1445 1449 for chunk in iterator:
1446 1450 for line in chunk.splitlines():
1447 1451 yield line
1448 1452
1449 1453 def expandpath(path):
1450 1454 return os.path.expanduser(os.path.expandvars(path))
1451 1455
1452 1456 def hgcmd():
1453 1457 """Return the command used to execute current hg
1454 1458
1455 1459 This is different from hgexecutable() because on Windows we want
1456 1460 to avoid things opening new shell windows like batch files, so we
1457 1461 get either the python call or current executable.
1458 1462 """
1459 1463 if main_is_frozen():
1460 1464 return [sys.executable]
1461 1465 return gethgcmd()
1462 1466
1463 1467 def rundetached(args, condfn):
1464 1468 """Execute the argument list in a detached process.
1465 1469
1466 1470 condfn is a callable which is called repeatedly and should return
1467 1471 True once the child process is known to have started successfully.
1468 1472 At this point, the child process PID is returned. If the child
1469 1473 process fails to start or finishes before condfn() evaluates to
1470 1474 True, return -1.
1471 1475 """
1472 1476 # Windows case is easier because the child process is either
1473 1477 # successfully starting and validating the condition or exiting
1474 1478 # on failure. We just poll on its PID. On Unix, if the child
1475 1479 # process fails to start, it will be left in a zombie state until
1476 1480 # the parent wait on it, which we cannot do since we expect a long
1477 1481 # running process on success. Instead we listen for SIGCHLD telling
1478 1482 # us our child process terminated.
1479 1483 terminated = set()
1480 1484 def handler(signum, frame):
1481 1485 terminated.add(os.wait())
1482 1486 prevhandler = None
1483 1487 if hasattr(signal, 'SIGCHLD'):
1484 1488 prevhandler = signal.signal(signal.SIGCHLD, handler)
1485 1489 try:
1486 1490 pid = spawndetached(args)
1487 1491 while not condfn():
1488 1492 if ((pid in terminated or not testpid(pid))
1489 1493 and not condfn()):
1490 1494 return -1
1491 1495 time.sleep(0.1)
1492 1496 return pid
1493 1497 finally:
1494 1498 if prevhandler is not None:
1495 1499 signal.signal(signal.SIGCHLD, prevhandler)
1496 1500
1497 1501 try:
1498 1502 any, all = any, all
1499 1503 except NameError:
1500 1504 def any(iterable):
1501 1505 for i in iterable:
1502 1506 if i:
1503 1507 return True
1504 1508 return False
1505 1509
1506 1510 def all(iterable):
1507 1511 for i in iterable:
1508 1512 if not i:
1509 1513 return False
1510 1514 return True
1511 1515
1512 1516 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1513 1517 """Return the result of interpolating items in the mapping into string s.
1514 1518
1515 1519 prefix is a single character string, or a two character string with
1516 1520 a backslash as the first character if the prefix needs to be escaped in
1517 1521 a regular expression.
1518 1522
1519 1523 fn is an optional function that will be applied to the replacement text
1520 1524 just before replacement.
1521 1525
1522 1526 escape_prefix is an optional flag that allows using doubled prefix for
1523 1527 its escaping.
1524 1528 """
1525 1529 fn = fn or (lambda s: s)
1526 1530 patterns = '|'.join(mapping.keys())
1527 1531 if escape_prefix:
1528 1532 patterns += '|' + prefix
1529 1533 if len(prefix) > 1:
1530 1534 prefix_char = prefix[1:]
1531 1535 else:
1532 1536 prefix_char = prefix
1533 1537 mapping[prefix_char] = prefix_char
1534 1538 r = re.compile(r'%s(%s)' % (prefix, patterns))
1535 1539 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1536 1540
1537 1541 def getport(port):
1538 1542 """Return the port for a given network service.
1539 1543
1540 1544 If port is an integer, it's returned as is. If it's a string, it's
1541 1545 looked up using socket.getservbyname(). If there's no matching
1542 1546 service, util.Abort is raised.
1543 1547 """
1544 1548 try:
1545 1549 return int(port)
1546 1550 except ValueError:
1547 1551 pass
1548 1552
1549 1553 try:
1550 1554 return socket.getservbyname(port)
1551 1555 except socket.error:
1552 1556 raise Abort(_("no port number associated with service '%s'") % port)
1553 1557
1554 1558 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1555 1559 '0': False, 'no': False, 'false': False, 'off': False,
1556 1560 'never': False}
1557 1561
1558 1562 def parsebool(s):
1559 1563 """Parse s into a boolean.
1560 1564
1561 1565 If s is not a valid boolean, returns None.
1562 1566 """
1563 1567 return _booleans.get(s.lower(), None)
@@ -1,1123 +1,1135 b''
1 1 $ hg init a
2 2
3 3 $ cd a
4 4 $ echo a > a
5 5 $ hg ci -Ama -d '1 0'
6 6 adding a
7 7
8 8 $ hg cp a b
9 9 $ hg ci -mb -d '2 0'
10 10
11 11 $ mkdir dir
12 12 $ hg mv b dir
13 13 $ hg ci -mc -d '3 0'
14 14
15 15 $ hg mv a b
16 16 $ echo a > d
17 17 $ hg add d
18 18 $ hg ci -md -d '4 0'
19 19
20 20 $ hg mv dir/b e
21 21 $ hg ci -me -d '5 0'
22 22
23 23 $ hg log a
24 24 changeset: 0:8580ff50825a
25 25 user: test
26 26 date: Thu Jan 01 00:00:01 1970 +0000
27 27 summary: a
28 28
29 29
30 30 -f, directory
31 31
32 32 $ hg log -f dir
33 33 abort: cannot follow nonexistent file: "dir"
34 34 [255]
35 35
36 36 -f, but no args
37 37
38 38 $ hg log -f
39 39 changeset: 4:66c1345dc4f9
40 40 tag: tip
41 41 user: test
42 42 date: Thu Jan 01 00:00:05 1970 +0000
43 43 summary: e
44 44
45 45 changeset: 3:7c6c671bb7cc
46 46 user: test
47 47 date: Thu Jan 01 00:00:04 1970 +0000
48 48 summary: d
49 49
50 50 changeset: 2:41dd4284081e
51 51 user: test
52 52 date: Thu Jan 01 00:00:03 1970 +0000
53 53 summary: c
54 54
55 55 changeset: 1:784de7cef101
56 56 user: test
57 57 date: Thu Jan 01 00:00:02 1970 +0000
58 58 summary: b
59 59
60 60 changeset: 0:8580ff50825a
61 61 user: test
62 62 date: Thu Jan 01 00:00:01 1970 +0000
63 63 summary: a
64 64
65 65
66 66 one rename
67 67
68 68 $ hg log -vf a
69 69 changeset: 0:8580ff50825a
70 70 user: test
71 71 date: Thu Jan 01 00:00:01 1970 +0000
72 72 files: a
73 73 description:
74 74 a
75 75
76 76
77 77
78 78 many renames
79 79
80 80 $ hg log -vf e
81 81 changeset: 4:66c1345dc4f9
82 82 tag: tip
83 83 user: test
84 84 date: Thu Jan 01 00:00:05 1970 +0000
85 85 files: dir/b e
86 86 description:
87 87 e
88 88
89 89
90 90 changeset: 2:41dd4284081e
91 91 user: test
92 92 date: Thu Jan 01 00:00:03 1970 +0000
93 93 files: b dir/b
94 94 description:
95 95 c
96 96
97 97
98 98 changeset: 1:784de7cef101
99 99 user: test
100 100 date: Thu Jan 01 00:00:02 1970 +0000
101 101 files: b
102 102 description:
103 103 b
104 104
105 105
106 106 changeset: 0:8580ff50825a
107 107 user: test
108 108 date: Thu Jan 01 00:00:01 1970 +0000
109 109 files: a
110 110 description:
111 111 a
112 112
113 113
114 114
115 115
116 116 log -pf dir/b
117 117
118 118 $ hg log -pf dir/b
119 119 changeset: 2:41dd4284081e
120 120 user: test
121 121 date: Thu Jan 01 00:00:03 1970 +0000
122 122 summary: c
123 123
124 124 diff -r 784de7cef101 -r 41dd4284081e dir/b
125 125 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
126 126 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
127 127 @@ -0,0 +1,1 @@
128 128 +a
129 129
130 130 changeset: 1:784de7cef101
131 131 user: test
132 132 date: Thu Jan 01 00:00:02 1970 +0000
133 133 summary: b
134 134
135 135 diff -r 8580ff50825a -r 784de7cef101 b
136 136 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
137 137 +++ b/b Thu Jan 01 00:00:02 1970 +0000
138 138 @@ -0,0 +1,1 @@
139 139 +a
140 140
141 141 changeset: 0:8580ff50825a
142 142 user: test
143 143 date: Thu Jan 01 00:00:01 1970 +0000
144 144 summary: a
145 145
146 146 diff -r 000000000000 -r 8580ff50825a a
147 147 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
148 148 +++ b/a Thu Jan 01 00:00:01 1970 +0000
149 149 @@ -0,0 +1,1 @@
150 150 +a
151 151
152 152
153 153 log -vf dir/b
154 154
155 155 $ hg log -vf dir/b
156 156 changeset: 2:41dd4284081e
157 157 user: test
158 158 date: Thu Jan 01 00:00:03 1970 +0000
159 159 files: b dir/b
160 160 description:
161 161 c
162 162
163 163
164 164 changeset: 1:784de7cef101
165 165 user: test
166 166 date: Thu Jan 01 00:00:02 1970 +0000
167 167 files: b
168 168 description:
169 169 b
170 170
171 171
172 172 changeset: 0:8580ff50825a
173 173 user: test
174 174 date: Thu Jan 01 00:00:01 1970 +0000
175 175 files: a
176 176 description:
177 177 a
178 178
179 179
180 180
181 181
182 182 log copies with --copies
183 183
184 184 $ hg log -vC --template '{rev} {file_copies}\n'
185 185 4 e (dir/b)
186 186 3 b (a)
187 187 2 dir/b (b)
188 188 1 b (a)
189 189 0
190 190
191 191 log copies switch without --copies, with old filecopy template
192 192
193 193 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
194 194 4
195 195 3
196 196 2
197 197 1
198 198 0
199 199
200 200 log copies switch with --copies
201 201
202 202 $ hg log -vC --template '{rev} {file_copies_switch}\n'
203 203 4 e (dir/b)
204 204 3 b (a)
205 205 2 dir/b (b)
206 206 1 b (a)
207 207 0
208 208
209 209
210 210 log copies with hardcoded style and with --style=default
211 211
212 212 $ hg log -vC -r4
213 213 changeset: 4:66c1345dc4f9
214 214 tag: tip
215 215 user: test
216 216 date: Thu Jan 01 00:00:05 1970 +0000
217 217 files: dir/b e
218 218 copies: e (dir/b)
219 219 description:
220 220 e
221 221
222 222
223 223 $ hg log -vC -r4 --style=default
224 224 changeset: 4:66c1345dc4f9
225 225 tag: tip
226 226 user: test
227 227 date: Thu Jan 01 00:00:05 1970 +0000
228 228 files: dir/b e
229 229 copies: e (dir/b)
230 230 description:
231 231 e
232 232
233 233
234 234
235 235
236 236 log copies, non-linear manifest
237 237
238 238 $ hg up -C 3
239 239 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
240 240 $ hg mv dir/b e
241 241 $ echo foo > foo
242 242 $ hg ci -Ame2 -d '6 0'
243 243 adding foo
244 244 created new head
245 245 $ hg log -v --template '{rev} {file_copies}\n' -r 5
246 246 5 e (dir/b)
247 247
248 248
249 249 log copies, execute bit set
250 250
251 251 $ chmod +x e
252 252 $ hg ci -me3 -d '7 0'
253 253 $ hg log -v --template '{rev} {file_copies}\n' -r 6
254 254 6
255 255
256 256
257 257 log -p d
258 258
259 259 $ hg log -pv d
260 260 changeset: 3:7c6c671bb7cc
261 261 user: test
262 262 date: Thu Jan 01 00:00:04 1970 +0000
263 263 files: a b d
264 264 description:
265 265 d
266 266
267 267
268 268 diff -r 41dd4284081e -r 7c6c671bb7cc d
269 269 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
270 270 +++ b/d Thu Jan 01 00:00:04 1970 +0000
271 271 @@ -0,0 +1,1 @@
272 272 +a
273 273
274 274
275 275
276 276 log --removed file
277 277
278 278 $ hg log --removed -v a
279 279 changeset: 3:7c6c671bb7cc
280 280 user: test
281 281 date: Thu Jan 01 00:00:04 1970 +0000
282 282 files: a b d
283 283 description:
284 284 d
285 285
286 286
287 287 changeset: 0:8580ff50825a
288 288 user: test
289 289 date: Thu Jan 01 00:00:01 1970 +0000
290 290 files: a
291 291 description:
292 292 a
293 293
294 294
295 295
296 296 log --removed revrange file
297 297
298 298 $ hg log --removed -v -r0:2 a
299 299 changeset: 0:8580ff50825a
300 300 user: test
301 301 date: Thu Jan 01 00:00:01 1970 +0000
302 302 files: a
303 303 description:
304 304 a
305 305
306 306
307 307
308 308
309 309 log --follow tests
310 310
311 311 $ hg init ../follow
312 312 $ cd ../follow
313 313
314 314 $ echo base > base
315 315 $ hg ci -Ambase -d '1 0'
316 316 adding base
317 317
318 318 $ echo r1 >> base
319 319 $ hg ci -Amr1 -d '1 0'
320 320 $ echo r2 >> base
321 321 $ hg ci -Amr2 -d '1 0'
322 322
323 323 $ hg up -C 1
324 324 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
325 325 $ echo b1 > b1
326 326 $ hg ci -Amb1 -d '1 0'
327 327 adding b1
328 328 created new head
329 329
330 330
331 331 log -f
332 332
333 333 $ hg log -f
334 334 changeset: 3:e62f78d544b4
335 335 tag: tip
336 336 parent: 1:3d5bf5654eda
337 337 user: test
338 338 date: Thu Jan 01 00:00:01 1970 +0000
339 339 summary: b1
340 340
341 341 changeset: 1:3d5bf5654eda
342 342 user: test
343 343 date: Thu Jan 01 00:00:01 1970 +0000
344 344 summary: r1
345 345
346 346 changeset: 0:67e992f2c4f3
347 347 user: test
348 348 date: Thu Jan 01 00:00:01 1970 +0000
349 349 summary: base
350 350
351 351
352 352
353 353 log -f -r 1:tip
354 354
355 355 $ hg up -C 0
356 356 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
357 357 $ echo b2 > b2
358 358 $ hg ci -Amb2 -d '1 0'
359 359 adding b2
360 360 created new head
361 361 $ hg log -f -r 1:tip
362 362 changeset: 1:3d5bf5654eda
363 363 user: test
364 364 date: Thu Jan 01 00:00:01 1970 +0000
365 365 summary: r1
366 366
367 367 changeset: 2:60c670bf5b30
368 368 user: test
369 369 date: Thu Jan 01 00:00:01 1970 +0000
370 370 summary: r2
371 371
372 372 changeset: 3:e62f78d544b4
373 373 parent: 1:3d5bf5654eda
374 374 user: test
375 375 date: Thu Jan 01 00:00:01 1970 +0000
376 376 summary: b1
377 377
378 378
379 379
380 380 log -r . with two parents
381 381
382 382 $ hg up -C 3
383 383 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
384 384 $ hg merge tip
385 385 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
386 386 (branch merge, don't forget to commit)
387 387 $ hg log -r .
388 388 changeset: 3:e62f78d544b4
389 389 parent: 1:3d5bf5654eda
390 390 user: test
391 391 date: Thu Jan 01 00:00:01 1970 +0000
392 392 summary: b1
393 393
394 394
395 395
396 396 log -r . with one parent
397 397
398 398 $ hg ci -mm12 -d '1 0'
399 399 $ hg log -r .
400 400 changeset: 5:302e9dd6890d
401 401 tag: tip
402 402 parent: 3:e62f78d544b4
403 403 parent: 4:ddb82e70d1a1
404 404 user: test
405 405 date: Thu Jan 01 00:00:01 1970 +0000
406 406 summary: m12
407 407
408 408
409 409 $ echo postm >> b1
410 410 $ hg ci -Amb1.1 -d'1 0'
411 411
412 412
413 413 log --follow-first
414 414
415 415 $ hg log --follow-first
416 416 changeset: 6:2404bbcab562
417 417 tag: tip
418 418 user: test
419 419 date: Thu Jan 01 00:00:01 1970 +0000
420 420 summary: b1.1
421 421
422 422 changeset: 5:302e9dd6890d
423 423 parent: 3:e62f78d544b4
424 424 parent: 4:ddb82e70d1a1
425 425 user: test
426 426 date: Thu Jan 01 00:00:01 1970 +0000
427 427 summary: m12
428 428
429 429 changeset: 3:e62f78d544b4
430 430 parent: 1:3d5bf5654eda
431 431 user: test
432 432 date: Thu Jan 01 00:00:01 1970 +0000
433 433 summary: b1
434 434
435 435 changeset: 1:3d5bf5654eda
436 436 user: test
437 437 date: Thu Jan 01 00:00:01 1970 +0000
438 438 summary: r1
439 439
440 440 changeset: 0:67e992f2c4f3
441 441 user: test
442 442 date: Thu Jan 01 00:00:01 1970 +0000
443 443 summary: base
444 444
445 445
446 446
447 447 log -P 2
448 448
449 449 $ hg log -P 2
450 450 changeset: 6:2404bbcab562
451 451 tag: tip
452 452 user: test
453 453 date: Thu Jan 01 00:00:01 1970 +0000
454 454 summary: b1.1
455 455
456 456 changeset: 5:302e9dd6890d
457 457 parent: 3:e62f78d544b4
458 458 parent: 4:ddb82e70d1a1
459 459 user: test
460 460 date: Thu Jan 01 00:00:01 1970 +0000
461 461 summary: m12
462 462
463 463 changeset: 4:ddb82e70d1a1
464 464 parent: 0:67e992f2c4f3
465 465 user: test
466 466 date: Thu Jan 01 00:00:01 1970 +0000
467 467 summary: b2
468 468
469 469 changeset: 3:e62f78d544b4
470 470 parent: 1:3d5bf5654eda
471 471 user: test
472 472 date: Thu Jan 01 00:00:01 1970 +0000
473 473 summary: b1
474 474
475 475
476 476
477 477 log -r tip -p --git
478 478
479 479 $ hg log -r tip -p --git
480 480 changeset: 6:2404bbcab562
481 481 tag: tip
482 482 user: test
483 483 date: Thu Jan 01 00:00:01 1970 +0000
484 484 summary: b1.1
485 485
486 486 diff --git a/b1 b/b1
487 487 --- a/b1
488 488 +++ b/b1
489 489 @@ -1,1 +1,2 @@
490 490 b1
491 491 +postm
492 492
493 493
494 494
495 495 log -r ""
496 496
497 497 $ hg log -r ''
498 498 hg: parse error: empty query
499 499 [255]
500 500
501 501 log -r <some unknown node id>
502 502
503 503 $ hg log -r 1000000000000000000000000000000000000000
504 504 abort: unknown revision '1000000000000000000000000000000000000000'!
505 505 [255]
506 506
507 507 log -k r1
508 508
509 509 $ hg log -k r1
510 510 changeset: 1:3d5bf5654eda
511 511 user: test
512 512 date: Thu Jan 01 00:00:01 1970 +0000
513 513 summary: r1
514 514
515 515 log -d " " (whitespaces only)
516 516
517 517 $ hg log -d " "
518 518 abort: dates cannot consist entirely of whitespace
519 519 [255]
520 520
521 521 log -d -1
522 522
523 523 $ hg log -d -1
524 524
525 log -d ">"
526
527 $ hg log -d ">"
528 abort: invalid day spec. use '>{datetime}'
529 [255]
530
531 log -d "<"
532
533 $ hg log -d "<"
534 abort: invalid day spec. use '<{datetime}'
535 [255]
536
525 537
526 538 log -p -l2 --color=always
527 539
528 540 $ hg --config extensions.color= --config color.mode=ansi \
529 541 > log -p -l2 --color=always
530 542 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
531 543 tag: tip
532 544 user: test
533 545 date: Thu Jan 01 00:00:01 1970 +0000
534 546 summary: b1.1
535 547
536 548 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
537 549 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
538 550 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
539 551 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
540 552 b1
541 553 \x1b[0;32m+postm\x1b[0m (esc)
542 554
543 555 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
544 556 parent: 3:e62f78d544b4
545 557 parent: 4:ddb82e70d1a1
546 558 user: test
547 559 date: Thu Jan 01 00:00:01 1970 +0000
548 560 summary: m12
549 561
550 562 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
551 563 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
552 564 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
553 565 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
554 566 \x1b[0;32m+b2\x1b[0m (esc)
555 567
556 568
557 569
558 570 log -r tip --stat
559 571
560 572 $ hg log -r tip --stat
561 573 changeset: 6:2404bbcab562
562 574 tag: tip
563 575 user: test
564 576 date: Thu Jan 01 00:00:01 1970 +0000
565 577 summary: b1.1
566 578
567 579 b1 | 1 +
568 580 1 files changed, 1 insertions(+), 0 deletions(-)
569 581
570 582
571 583 $ cd ..
572 584
573 585 $ hg init usertest
574 586 $ cd usertest
575 587
576 588 $ echo a > a
577 589 $ hg ci -A -m "a" -u "User One <user1@example.org>"
578 590 adding a
579 591 $ echo b > b
580 592 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
581 593 adding b
582 594
583 595 $ hg log -u "User One <user1@example.org>"
584 596 changeset: 0:29a4c94f1924
585 597 user: User One <user1@example.org>
586 598 date: Thu Jan 01 00:00:00 1970 +0000
587 599 summary: a
588 600
589 601 $ hg log -u "user1" -u "user2"
590 602 changeset: 1:e834b5e69c0e
591 603 tag: tip
592 604 user: User Two <user2@example.org>
593 605 date: Thu Jan 01 00:00:00 1970 +0000
594 606 summary: b
595 607
596 608 changeset: 0:29a4c94f1924
597 609 user: User One <user1@example.org>
598 610 date: Thu Jan 01 00:00:00 1970 +0000
599 611 summary: a
600 612
601 613 $ hg log -u "user3"
602 614
603 615 $ cd ..
604 616
605 617 $ hg init branches
606 618 $ cd branches
607 619
608 620 $ echo a > a
609 621 $ hg ci -A -m "commit on default"
610 622 adding a
611 623 $ hg branch test
612 624 marked working directory as branch test
613 625 $ echo b > b
614 626 $ hg ci -A -m "commit on test"
615 627 adding b
616 628
617 629 $ hg up default
618 630 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
619 631 $ echo c > c
620 632 $ hg ci -A -m "commit on default"
621 633 adding c
622 634 $ hg up test
623 635 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
624 636 $ echo c > c
625 637 $ hg ci -A -m "commit on test"
626 638 adding c
627 639
628 640
629 641 log -b default
630 642
631 643 $ hg log -b default
632 644 changeset: 2:c3a4f03cc9a7
633 645 parent: 0:24427303d56f
634 646 user: test
635 647 date: Thu Jan 01 00:00:00 1970 +0000
636 648 summary: commit on default
637 649
638 650 changeset: 0:24427303d56f
639 651 user: test
640 652 date: Thu Jan 01 00:00:00 1970 +0000
641 653 summary: commit on default
642 654
643 655
644 656
645 657 log -b test
646 658
647 659 $ hg log -b test
648 660 changeset: 3:f5d8de11c2e2
649 661 branch: test
650 662 tag: tip
651 663 parent: 1:d32277701ccb
652 664 user: test
653 665 date: Thu Jan 01 00:00:00 1970 +0000
654 666 summary: commit on test
655 667
656 668 changeset: 1:d32277701ccb
657 669 branch: test
658 670 user: test
659 671 date: Thu Jan 01 00:00:00 1970 +0000
660 672 summary: commit on test
661 673
662 674
663 675
664 676 log -b dummy
665 677
666 678 $ hg log -b dummy
667 679 abort: unknown revision 'dummy'!
668 680 [255]
669 681
670 682
671 683 log -b .
672 684
673 685 $ hg log -b .
674 686 changeset: 3:f5d8de11c2e2
675 687 branch: test
676 688 tag: tip
677 689 parent: 1:d32277701ccb
678 690 user: test
679 691 date: Thu Jan 01 00:00:00 1970 +0000
680 692 summary: commit on test
681 693
682 694 changeset: 1:d32277701ccb
683 695 branch: test
684 696 user: test
685 697 date: Thu Jan 01 00:00:00 1970 +0000
686 698 summary: commit on test
687 699
688 700
689 701
690 702 log -b default -b test
691 703
692 704 $ hg log -b default -b test
693 705 changeset: 3:f5d8de11c2e2
694 706 branch: test
695 707 tag: tip
696 708 parent: 1:d32277701ccb
697 709 user: test
698 710 date: Thu Jan 01 00:00:00 1970 +0000
699 711 summary: commit on test
700 712
701 713 changeset: 2:c3a4f03cc9a7
702 714 parent: 0:24427303d56f
703 715 user: test
704 716 date: Thu Jan 01 00:00:00 1970 +0000
705 717 summary: commit on default
706 718
707 719 changeset: 1:d32277701ccb
708 720 branch: test
709 721 user: test
710 722 date: Thu Jan 01 00:00:00 1970 +0000
711 723 summary: commit on test
712 724
713 725 changeset: 0:24427303d56f
714 726 user: test
715 727 date: Thu Jan 01 00:00:00 1970 +0000
716 728 summary: commit on default
717 729
718 730
719 731
720 732 log -b default -b .
721 733
722 734 $ hg log -b default -b .
723 735 changeset: 3:f5d8de11c2e2
724 736 branch: test
725 737 tag: tip
726 738 parent: 1:d32277701ccb
727 739 user: test
728 740 date: Thu Jan 01 00:00:00 1970 +0000
729 741 summary: commit on test
730 742
731 743 changeset: 2:c3a4f03cc9a7
732 744 parent: 0:24427303d56f
733 745 user: test
734 746 date: Thu Jan 01 00:00:00 1970 +0000
735 747 summary: commit on default
736 748
737 749 changeset: 1:d32277701ccb
738 750 branch: test
739 751 user: test
740 752 date: Thu Jan 01 00:00:00 1970 +0000
741 753 summary: commit on test
742 754
743 755 changeset: 0:24427303d56f
744 756 user: test
745 757 date: Thu Jan 01 00:00:00 1970 +0000
746 758 summary: commit on default
747 759
748 760
749 761
750 762 log -b . -b test
751 763
752 764 $ hg log -b . -b test
753 765 changeset: 3:f5d8de11c2e2
754 766 branch: test
755 767 tag: tip
756 768 parent: 1:d32277701ccb
757 769 user: test
758 770 date: Thu Jan 01 00:00:00 1970 +0000
759 771 summary: commit on test
760 772
761 773 changeset: 1:d32277701ccb
762 774 branch: test
763 775 user: test
764 776 date: Thu Jan 01 00:00:00 1970 +0000
765 777 summary: commit on test
766 778
767 779
768 780
769 781 log -b 2
770 782
771 783 $ hg log -b 2
772 784 changeset: 2:c3a4f03cc9a7
773 785 parent: 0:24427303d56f
774 786 user: test
775 787 date: Thu Jan 01 00:00:00 1970 +0000
776 788 summary: commit on default
777 789
778 790 changeset: 0:24427303d56f
779 791 user: test
780 792 date: Thu Jan 01 00:00:00 1970 +0000
781 793 summary: commit on default
782 794
783 795
784 796
785 797 log -p --cwd dir (in subdir)
786 798
787 799 $ mkdir dir
788 800 $ hg log -p --cwd dir
789 801 changeset: 3:f5d8de11c2e2
790 802 branch: test
791 803 tag: tip
792 804 parent: 1:d32277701ccb
793 805 user: test
794 806 date: Thu Jan 01 00:00:00 1970 +0000
795 807 summary: commit on test
796 808
797 809 diff -r d32277701ccb -r f5d8de11c2e2 c
798 810 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
799 811 +++ b/c Thu Jan 01 00:00:00 1970 +0000
800 812 @@ -0,0 +1,1 @@
801 813 +c
802 814
803 815 changeset: 2:c3a4f03cc9a7
804 816 parent: 0:24427303d56f
805 817 user: test
806 818 date: Thu Jan 01 00:00:00 1970 +0000
807 819 summary: commit on default
808 820
809 821 diff -r 24427303d56f -r c3a4f03cc9a7 c
810 822 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
811 823 +++ b/c Thu Jan 01 00:00:00 1970 +0000
812 824 @@ -0,0 +1,1 @@
813 825 +c
814 826
815 827 changeset: 1:d32277701ccb
816 828 branch: test
817 829 user: test
818 830 date: Thu Jan 01 00:00:00 1970 +0000
819 831 summary: commit on test
820 832
821 833 diff -r 24427303d56f -r d32277701ccb b
822 834 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
823 835 +++ b/b Thu Jan 01 00:00:00 1970 +0000
824 836 @@ -0,0 +1,1 @@
825 837 +b
826 838
827 839 changeset: 0:24427303d56f
828 840 user: test
829 841 date: Thu Jan 01 00:00:00 1970 +0000
830 842 summary: commit on default
831 843
832 844 diff -r 000000000000 -r 24427303d56f a
833 845 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
834 846 +++ b/a Thu Jan 01 00:00:00 1970 +0000
835 847 @@ -0,0 +1,1 @@
836 848 +a
837 849
838 850
839 851
840 852 log -p -R repo
841 853
842 854 $ cd dir
843 855 $ hg log -p -R .. ../a
844 856 changeset: 0:24427303d56f
845 857 user: test
846 858 date: Thu Jan 01 00:00:00 1970 +0000
847 859 summary: commit on default
848 860
849 861 diff -r 000000000000 -r 24427303d56f a
850 862 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
851 863 +++ b/a Thu Jan 01 00:00:00 1970 +0000
852 864 @@ -0,0 +1,1 @@
853 865 +a
854 866
855 867
856 868
857 869 $ cd ..
858 870 $ hg init follow2
859 871 $ cd follow2
860 872
861 873
862 874 # Build the following history:
863 875 # tip - o - x - o - x - x
864 876 # \ /
865 877 # o - o - o - x
866 878 # \ /
867 879 # o
868 880 #
869 881 # Where "o" is a revision containing "foo" and
870 882 # "x" is a revision without "foo"
871 883
872 884 $ touch init
873 885 $ hg ci -A -m "init, unrelated"
874 886 adding init
875 887 $ echo 'foo' > init
876 888 $ hg ci -m "change, unrelated"
877 889 $ echo 'foo' > foo
878 890 $ hg ci -A -m "add unrelated old foo"
879 891 adding foo
880 892 $ hg rm foo
881 893 $ hg ci -m "delete foo, unrelated"
882 894 $ echo 'related' > foo
883 895 $ hg ci -A -m "add foo, related"
884 896 adding foo
885 897
886 898 $ hg up 0
887 899 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
888 900 $ touch branch
889 901 $ hg ci -A -m "first branch, unrelated"
890 902 adding branch
891 903 created new head
892 904 $ touch foo
893 905 $ hg ci -A -m "create foo, related"
894 906 adding foo
895 907 $ echo 'change' > foo
896 908 $ hg ci -m "change foo, related"
897 909
898 910 $ hg up 6
899 911 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
900 912 $ echo 'change foo in branch' > foo
901 913 $ hg ci -m "change foo in branch, related"
902 914 created new head
903 915 $ hg merge 7
904 916 merging foo
905 917 warning: conflicts during merge.
906 918 merging foo failed!
907 919 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
908 920 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
909 921 [1]
910 922 $ echo 'merge 1' > foo
911 923 $ hg resolve -m foo
912 924 $ hg ci -m "First merge, related"
913 925
914 926 $ hg merge 4
915 927 merging foo
916 928 warning: conflicts during merge.
917 929 merging foo failed!
918 930 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
919 931 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
920 932 [1]
921 933 $ echo 'merge 2' > foo
922 934 $ hg resolve -m foo
923 935 $ hg ci -m "Last merge, related"
924 936
925 937 $ hg --config "extensions.graphlog=" glog
926 938 @ changeset: 10:4dae8563d2c5
927 939 |\ tag: tip
928 940 | | parent: 9:7b35701b003e
929 941 | | parent: 4:88176d361b69
930 942 | | user: test
931 943 | | date: Thu Jan 01 00:00:00 1970 +0000
932 944 | | summary: Last merge, related
933 945 | |
934 946 | o changeset: 9:7b35701b003e
935 947 | |\ parent: 8:e5416ad8a855
936 948 | | | parent: 7:87fe3144dcfa
937 949 | | | user: test
938 950 | | | date: Thu Jan 01 00:00:00 1970 +0000
939 951 | | | summary: First merge, related
940 952 | | |
941 953 | | o changeset: 8:e5416ad8a855
942 954 | | | parent: 6:dc6c325fe5ee
943 955 | | | user: test
944 956 | | | date: Thu Jan 01 00:00:00 1970 +0000
945 957 | | | summary: change foo in branch, related
946 958 | | |
947 959 | o | changeset: 7:87fe3144dcfa
948 960 | |/ user: test
949 961 | | date: Thu Jan 01 00:00:00 1970 +0000
950 962 | | summary: change foo, related
951 963 | |
952 964 | o changeset: 6:dc6c325fe5ee
953 965 | | user: test
954 966 | | date: Thu Jan 01 00:00:00 1970 +0000
955 967 | | summary: create foo, related
956 968 | |
957 969 | o changeset: 5:73db34516eb9
958 970 | | parent: 0:e87515fd044a
959 971 | | user: test
960 972 | | date: Thu Jan 01 00:00:00 1970 +0000
961 973 | | summary: first branch, unrelated
962 974 | |
963 975 o | changeset: 4:88176d361b69
964 976 | | user: test
965 977 | | date: Thu Jan 01 00:00:00 1970 +0000
966 978 | | summary: add foo, related
967 979 | |
968 980 o | changeset: 3:dd78ae4afb56
969 981 | | user: test
970 982 | | date: Thu Jan 01 00:00:00 1970 +0000
971 983 | | summary: delete foo, unrelated
972 984 | |
973 985 o | changeset: 2:c4c64aedf0f7
974 986 | | user: test
975 987 | | date: Thu Jan 01 00:00:00 1970 +0000
976 988 | | summary: add unrelated old foo
977 989 | |
978 990 o | changeset: 1:e5faa7440653
979 991 |/ user: test
980 992 | date: Thu Jan 01 00:00:00 1970 +0000
981 993 | summary: change, unrelated
982 994 |
983 995 o changeset: 0:e87515fd044a
984 996 user: test
985 997 date: Thu Jan 01 00:00:00 1970 +0000
986 998 summary: init, unrelated
987 999
988 1000
989 1001 $ hg --traceback log -f foo
990 1002 changeset: 10:4dae8563d2c5
991 1003 tag: tip
992 1004 parent: 9:7b35701b003e
993 1005 parent: 4:88176d361b69
994 1006 user: test
995 1007 date: Thu Jan 01 00:00:00 1970 +0000
996 1008 summary: Last merge, related
997 1009
998 1010 changeset: 9:7b35701b003e
999 1011 parent: 8:e5416ad8a855
1000 1012 parent: 7:87fe3144dcfa
1001 1013 user: test
1002 1014 date: Thu Jan 01 00:00:00 1970 +0000
1003 1015 summary: First merge, related
1004 1016
1005 1017 changeset: 8:e5416ad8a855
1006 1018 parent: 6:dc6c325fe5ee
1007 1019 user: test
1008 1020 date: Thu Jan 01 00:00:00 1970 +0000
1009 1021 summary: change foo in branch, related
1010 1022
1011 1023 changeset: 7:87fe3144dcfa
1012 1024 user: test
1013 1025 date: Thu Jan 01 00:00:00 1970 +0000
1014 1026 summary: change foo, related
1015 1027
1016 1028 changeset: 6:dc6c325fe5ee
1017 1029 user: test
1018 1030 date: Thu Jan 01 00:00:00 1970 +0000
1019 1031 summary: create foo, related
1020 1032
1021 1033 changeset: 4:88176d361b69
1022 1034 user: test
1023 1035 date: Thu Jan 01 00:00:00 1970 +0000
1024 1036 summary: add foo, related
1025 1037
1026 1038
1027 1039 Also check when maxrev < lastrevfilelog
1028 1040
1029 1041 $ hg --traceback log -f -r4 foo
1030 1042 changeset: 4:88176d361b69
1031 1043 user: test
1032 1044 date: Thu Jan 01 00:00:00 1970 +0000
1033 1045 summary: add foo, related
1034 1046
1035 1047
1036 1048 Issue2383: hg log showing _less_ differences than hg diff
1037 1049
1038 1050 $ hg init issue2383
1039 1051 $ cd issue2383
1040 1052
1041 1053 Create a test repo:
1042 1054
1043 1055 $ echo a > a
1044 1056 $ hg ci -Am0
1045 1057 adding a
1046 1058 $ echo b > b
1047 1059 $ hg ci -Am1
1048 1060 adding b
1049 1061 $ hg co 0
1050 1062 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1051 1063 $ echo b > a
1052 1064 $ hg ci -m2
1053 1065 created new head
1054 1066
1055 1067 Merge:
1056 1068
1057 1069 $ hg merge
1058 1070 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1059 1071 (branch merge, don't forget to commit)
1060 1072
1061 1073 Make sure there's a file listed in the merge to trigger the bug:
1062 1074
1063 1075 $ echo c > a
1064 1076 $ hg ci -m3
1065 1077
1066 1078 Two files shown here in diff:
1067 1079
1068 1080 $ hg diff --rev 2:3
1069 1081 diff -r b09be438c43a -r 8e07aafe1edc a
1070 1082 --- a/a Thu Jan 01 00:00:00 1970 +0000
1071 1083 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1072 1084 @@ -1,1 +1,1 @@
1073 1085 -b
1074 1086 +c
1075 1087 diff -r b09be438c43a -r 8e07aafe1edc b
1076 1088 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1077 1089 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1078 1090 @@ -0,0 +1,1 @@
1079 1091 +b
1080 1092
1081 1093 Diff here should be the same:
1082 1094
1083 1095 $ hg log -vpr 3
1084 1096 changeset: 3:8e07aafe1edc
1085 1097 tag: tip
1086 1098 parent: 2:b09be438c43a
1087 1099 parent: 1:925d80f479bb
1088 1100 user: test
1089 1101 date: Thu Jan 01 00:00:00 1970 +0000
1090 1102 files: a
1091 1103 description:
1092 1104 3
1093 1105
1094 1106
1095 1107 diff -r b09be438c43a -r 8e07aafe1edc a
1096 1108 --- a/a Thu Jan 01 00:00:00 1970 +0000
1097 1109 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1098 1110 @@ -1,1 +1,1 @@
1099 1111 -b
1100 1112 +c
1101 1113 diff -r b09be438c43a -r 8e07aafe1edc b
1102 1114 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1103 1115 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1104 1116 @@ -0,0 +1,1 @@
1105 1117 +b
1106 1118
1107 1119 $ cd ..
1108 1120
1109 1121 'hg log -r rev fn' when last(filelog(fn)) != rev
1110 1122
1111 1123 $ hg init simplelog; cd simplelog
1112 1124 $ echo f > a
1113 1125 $ hg ci -Am'a' -d '0 0'
1114 1126 adding a
1115 1127 $ echo f >> a
1116 1128 $ hg ci -Am'a bis' -d '1 0'
1117 1129
1118 1130 $ hg log -r0 a
1119 1131 changeset: 0:9f758d63dcde
1120 1132 user: test
1121 1133 date: Thu Jan 01 00:00:00 1970 +0000
1122 1134 summary: a
1123 1135
General Comments 0
You need to be logged in to leave comments. Login now