490 else: |
490 else: |
491 shutil.copy(src, dst) |
491 shutil.copy(src, dst) |
492 num += 1 |
492 num += 1 |
493 |
493 |
494 return hardlink, num |
494 return hardlink, num |
|
495 |
|
496 _windows_reserved_filenames = '''con prn aux nul |
|
497 com1 com2 com3 com4 com5 com6 com7 com8 com9 |
|
498 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split() |
|
499 _windows_reserved_chars = ':*?"<>|' |
|
500 def checkwinfilename(path): |
|
501 '''Check that the base-relative path is a valid filename on Windows. |
|
502 Returns None if the path is ok, or a UI string describing the problem. |
|
503 |
|
504 >>> checkwinfilename("just/a/normal/path") |
|
505 >>> checkwinfilename("foo/bar/con.xml") |
|
506 "filename contains 'con', which is reserved on Windows" |
|
507 >>> checkwinfilename("foo/con.xml/bar") |
|
508 "filename contains 'con', which is reserved on Windows" |
|
509 >>> checkwinfilename("foo/bar/xml.con") |
|
510 >>> checkwinfilename("foo/bar/AUX/bla.txt") |
|
511 "filename contains 'AUX', which is reserved on Windows" |
|
512 >>> checkwinfilename("foo/bar/bla:.txt") |
|
513 "filename contains ':', which is reserved on Windows" |
|
514 >>> checkwinfilename("foo/bar/b\07la.txt") |
|
515 "filename contains '\\x07', which is invalid on Windows" |
|
516 >>> checkwinfilename("foo/bar/bla ") |
|
517 "filename ends with ' ', which is not allowed on Windows" |
|
518 ''' |
|
519 for n in path.replace('\\', '/').split('/'): |
|
520 if not n: |
|
521 continue |
|
522 for c in n: |
|
523 if c in _windows_reserved_chars: |
|
524 return _("filename contains '%s', which is reserved " |
|
525 "on Windows") % c |
|
526 if ord(c) <= 31: |
|
527 return _("filename contains '%s', which is invalid " |
|
528 "on Windows") % c |
|
529 base = n.split('.')[0] |
|
530 if base and base.lower() in _windows_reserved_filenames: |
|
531 return _("filename contains '%s', which is reserved " |
|
532 "on Windows") % base |
|
533 t = n[-1] |
|
534 if t in '. ': |
|
535 return _("filename ends with '%s', which is not allowed " |
|
536 "on Windows") % t |
495 |
537 |
496 class path_auditor(object): |
538 class path_auditor(object): |
497 '''ensure that a filesystem path contains no banned components. |
539 '''ensure that a filesystem path contains no banned components. |
498 the following properties of a path are checked: |
540 the following properties of a path are checked: |
499 |
541 |
558 break |
600 break |
559 check(prefix) |
601 check(prefix) |
560 prefixes.append(prefix) |
602 prefixes.append(prefix) |
561 parts.pop() |
603 parts.pop() |
562 |
604 |
|
605 r = checkosfilename(path) |
|
606 if r: |
|
607 raise Abort("%s: %s" % (r, path)) |
563 self.audited.add(path) |
608 self.audited.add(path) |
564 # only add prefixes to the cache after checking everything: we don't |
609 # only add prefixes to the cache after checking everything: we don't |
565 # want to add "foo/bar/baz" before checking if there's a "foo/.hg" |
610 # want to add "foo/bar/baz" before checking if there's a "foo/.hg" |
566 self.auditeddir.update(prefixes) |
611 self.auditeddir.update(prefixes) |
567 |
612 |