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