1# Module 'ntpath' -- common operations on WinNT/Win95 pathnames 2"""Common pathname manipulations, WindowsNT/95 version. 3 4Instead of importing this module directly, import os and refer to this 5module as os.path. 6""" 7 8# strings representing various path-related bits and pieces 9# These are primarily for export; internally, they are hardcoded. 10# Should be set before imports for resolving cyclic dependency. 11curdir = '.' 12pardir = '..' 13extsep = '.' 14sep = '\\' 15pathsep = ';' 16altsep = '/' 17defpath = '.;C:\\bin' 18devnull = 'nul' 19 20import os 21import sys 22import stat 23import genericpath 24from genericpath import * 25 26__all__ = ["normcase","isabs","join","splitdrive","split","splitext", 27 "basename","dirname","commonprefix","getsize","getmtime", 28 "getatime","getctime", "islink","exists","lexists","isdir","isfile", 29 "ismount", "expanduser","expandvars","normpath","abspath", 30 "curdir","pardir","sep","pathsep","defpath","altsep", 31 "extsep","devnull","realpath","supports_unicode_filenames","relpath", 32 "samefile", "sameopenfile", "samestat", "commonpath"] 33 34def _get_bothseps(path): 35 if isinstance(path, bytes): 36 return b'\\/' 37 else: 38 return '\\/' 39 40# Normalize the case of a pathname and map slashes to backslashes. 41# Other normalizations (such as optimizing '../' away) are not done 42# (this is done by normpath). 43 44def normcase(s): 45 """Normalize case of pathname. 46 47 Makes all characters lowercase and all slashes into backslashes.""" 48 s = os.fspath(s) 49 if isinstance(s, bytes): 50 return s.replace(b'/', b'\\').lower() 51 else: 52 return s.replace('/', '\\').lower() 53 54 55# Return whether a path is absolute. 56# Trivial in Posix, harder on Windows. 57# For Windows it is absolute if it starts with a slash or backslash (current 58# volume), or if a pathname after the volume-letter-and-colon or UNC-resource 59# starts with a slash or backslash. 60 61def isabs(s): 62 """Test whether a path is absolute""" 63 s = os.fspath(s) 64 # Paths beginning with \\?\ are always absolute, but do not 65 # necessarily contain a drive. 66 if isinstance(s, bytes): 67 if s.replace(b'/', b'\\').startswith(b'\\\\?\\'): 68 return True 69 else: 70 if s.replace('/', '\\').startswith('\\\\?\\'): 71 return True 72 s = splitdrive(s)[1] 73 return len(s) > 0 and s[0] in _get_bothseps(s) 74 75 76# Join two (or more) paths. 77def join(path, *paths): 78 path = os.fspath(path) 79 if isinstance(path, bytes): 80 sep = b'\\' 81 seps = b'\\/' 82 colon = b':' 83 else: 84 sep = '\\' 85 seps = '\\/' 86 colon = ':' 87 try: 88 if not paths: 89 path[:0] + sep #23780: Ensure compatible data type even if p is null. 90 result_drive, result_path = splitdrive(path) 91 for p in map(os.fspath, paths): 92 p_drive, p_path = splitdrive(p) 93 if p_path and p_path[0] in seps: 94 # Second path is absolute 95 if p_drive or not result_drive: 96 result_drive = p_drive 97 result_path = p_path 98 continue 99 elif p_drive and p_drive != result_drive: 100 if p_drive.lower() != result_drive.lower(): 101 # Different drives => ignore the first path entirely 102 result_drive = p_drive 103 result_path = p_path 104 continue 105 # Same drive in different case 106 result_drive = p_drive 107 # Second path is relative to the first 108 if result_path and result_path[-1] not in seps: 109 result_path = result_path + sep 110 result_path = result_path + p_path 111 ## add separator between UNC and non-absolute path 112 if (result_path and result_path[0] not in seps and 113 result_drive and result_drive[-1:] != colon): 114 return result_drive + sep + result_path 115 return result_drive + result_path 116 except (TypeError, AttributeError, BytesWarning): 117 genericpath._check_arg_types('join', path, *paths) 118 raise 119 120 121# Split a path in a drive specification (a drive letter followed by a 122# colon) and the path specification. 123# It is always true that drivespec + pathspec == p 124def splitdrive(p): 125 """Split a pathname into drive/UNC sharepoint and relative path specifiers. 126 Returns a 2-tuple (drive_or_unc, path); either part may be empty. 127 128 If you assign 129 result = splitdrive(p) 130 It is always true that: 131 result[0] + result[1] == p 132 133 If the path contained a drive letter, drive_or_unc will contain everything 134 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir") 135 136 If the path contained a UNC path, the drive_or_unc will contain the host name 137 and share up to but not including the fourth directory separator character. 138 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir") 139 140 Paths cannot contain both a drive letter and a UNC path. 141 142 """ 143 p = os.fspath(p) 144 if len(p) >= 2: 145 if isinstance(p, bytes): 146 sep = b'\\' 147 altsep = b'/' 148 colon = b':' 149 else: 150 sep = '\\' 151 altsep = '/' 152 colon = ':' 153 normp = p.replace(altsep, sep) 154 if (normp[0:2] == sep*2) and (normp[2:3] != sep): 155 # is a UNC path: 156 # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path 157 # \\machine\mountpoint\directory\etc\... 158 # directory ^^^^^^^^^^^^^^^ 159 index = normp.find(sep, 2) 160 if index == -1: 161 return p[:0], p 162 index2 = normp.find(sep, index + 1) 163 # a UNC path can't have two slashes in a row 164 # (after the initial two) 165 if index2 == index + 1: 166 return p[:0], p 167 if index2 == -1: 168 index2 = len(p) 169 return p[:index2], p[index2:] 170 if normp[1:2] == colon: 171 return p[:2], p[2:] 172 return p[:0], p 173 174 175# Split a path in head (everything up to the last '/') and tail (the 176# rest). After the trailing '/' is stripped, the invariant 177# join(head, tail) == p holds. 178# The resulting head won't end in '/' unless it is the root. 179 180def split(p): 181 """Split a pathname. 182 183 Return tuple (head, tail) where tail is everything after the final slash. 184 Either part may be empty.""" 185 p = os.fspath(p) 186 seps = _get_bothseps(p) 187 d, p = splitdrive(p) 188 # set i to index beyond p's last slash 189 i = len(p) 190 while i and p[i-1] not in seps: 191 i -= 1 192 head, tail = p[:i], p[i:] # now tail has no slashes 193 # remove trailing slashes from head, unless it's all slashes 194 head = head.rstrip(seps) or head 195 return d + head, tail 196 197 198# Split a path in root and extension. 199# The extension is everything starting at the last dot in the last 200# pathname component; the root is everything before that. 201# It is always true that root + ext == p. 202 203def splitext(p): 204 p = os.fspath(p) 205 if isinstance(p, bytes): 206 return genericpath._splitext(p, b'\\', b'/', b'.') 207 else: 208 return genericpath._splitext(p, '\\', '/', '.') 209splitext.__doc__ = genericpath._splitext.__doc__ 210 211 212# Return the tail (basename) part of a path. 213 214def basename(p): 215 """Returns the final component of a pathname""" 216 return split(p)[1] 217 218 219# Return the head (dirname) part of a path. 220 221def dirname(p): 222 """Returns the directory component of a pathname""" 223 return split(p)[0] 224 225# Is a path a symbolic link? 226# This will always return false on systems where os.lstat doesn't exist. 227 228def islink(path): 229 """Test whether a path is a symbolic link. 230 This will always return false for Windows prior to 6.0. 231 """ 232 try: 233 st = os.lstat(path) 234 except (OSError, ValueError, AttributeError): 235 return False 236 return stat.S_ISLNK(st.st_mode) 237 238# Being true for dangling symbolic links is also useful. 239 240def lexists(path): 241 """Test whether a path exists. Returns True for broken symbolic links""" 242 try: 243 st = os.lstat(path) 244 except (OSError, ValueError): 245 return False 246 return True 247 248# Is a path a mount point? 249# Any drive letter root (eg c:\) 250# Any share UNC (eg \\server\share) 251# Any volume mounted on a filesystem folder 252# 253# No one method detects all three situations. Historically we've lexically 254# detected drive letter roots and share UNCs. The canonical approach to 255# detecting mounted volumes (querying the reparse tag) fails for the most 256# common case: drive letter roots. The alternative which uses GetVolumePathName 257# fails if the drive letter is the result of a SUBST. 258try: 259 from nt import _getvolumepathname 260except ImportError: 261 _getvolumepathname = None 262def ismount(path): 263 """Test whether a path is a mount point (a drive root, the root of a 264 share, or a mounted volume)""" 265 path = os.fspath(path) 266 seps = _get_bothseps(path) 267 path = abspath(path) 268 root, rest = splitdrive(path) 269 if root and root[0] in seps: 270 return (not rest) or (rest in seps) 271 if rest in seps: 272 return True 273 274 if _getvolumepathname: 275 return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps) 276 else: 277 return False 278 279 280# Expand paths beginning with '~' or '~user'. 281# '~' means $HOME; '~user' means that user's home directory. 282# If the path doesn't begin with '~', or if the user or $HOME is unknown, 283# the path is returned unchanged (leaving error reporting to whatever 284# function is called with the expanded path as argument). 285# See also module 'glob' for expansion of *, ? and [...] in pathnames. 286# (A function should also be defined to do full *sh-style environment 287# variable expansion.) 288 289def expanduser(path): 290 """Expand ~ and ~user constructs. 291 292 If user or $HOME is unknown, do nothing.""" 293 path = os.fspath(path) 294 if isinstance(path, bytes): 295 tilde = b'~' 296 else: 297 tilde = '~' 298 if not path.startswith(tilde): 299 return path 300 i, n = 1, len(path) 301 while i < n and path[i] not in _get_bothseps(path): 302 i += 1 303 304 if 'USERPROFILE' in os.environ: 305 userhome = os.environ['USERPROFILE'] 306 elif not 'HOMEPATH' in os.environ: 307 return path 308 else: 309 try: 310 drive = os.environ['HOMEDRIVE'] 311 except KeyError: 312 drive = '' 313 userhome = join(drive, os.environ['HOMEPATH']) 314 315 if isinstance(path, bytes): 316 userhome = os.fsencode(userhome) 317 318 if i != 1: #~user 319 userhome = join(dirname(userhome), path[1:i]) 320 321 return userhome + path[i:] 322 323 324# Expand paths containing shell variable substitutions. 325# The following rules apply: 326# - no expansion within single quotes 327# - '$$' is translated into '$' 328# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2% 329# - ${varname} is accepted. 330# - $varname is accepted. 331# - %varname% is accepted. 332# - varnames can be made out of letters, digits and the characters '_-' 333# (though is not verified in the ${varname} and %varname% cases) 334# XXX With COMMAND.COM you can use any characters in a variable name, 335# XXX except '^|<>='. 336 337def expandvars(path): 338 """Expand shell variables of the forms $var, ${var} and %var%. 339 340 Unknown variables are left unchanged.""" 341 path = os.fspath(path) 342 if isinstance(path, bytes): 343 if b'$' not in path and b'%' not in path: 344 return path 345 import string 346 varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii') 347 quote = b'\'' 348 percent = b'%' 349 brace = b'{' 350 rbrace = b'}' 351 dollar = b'$' 352 environ = getattr(os, 'environb', None) 353 else: 354 if '$' not in path and '%' not in path: 355 return path 356 import string 357 varchars = string.ascii_letters + string.digits + '_-' 358 quote = '\'' 359 percent = '%' 360 brace = '{' 361 rbrace = '}' 362 dollar = '$' 363 environ = os.environ 364 res = path[:0] 365 index = 0 366 pathlen = len(path) 367 while index < pathlen: 368 c = path[index:index+1] 369 if c == quote: # no expansion within single quotes 370 path = path[index + 1:] 371 pathlen = len(path) 372 try: 373 index = path.index(c) 374 res += c + path[:index + 1] 375 except ValueError: 376 res += c + path 377 index = pathlen - 1 378 elif c == percent: # variable or '%' 379 if path[index + 1:index + 2] == percent: 380 res += c 381 index += 1 382 else: 383 path = path[index+1:] 384 pathlen = len(path) 385 try: 386 index = path.index(percent) 387 except ValueError: 388 res += percent + path 389 index = pathlen - 1 390 else: 391 var = path[:index] 392 try: 393 if environ is None: 394 value = os.fsencode(os.environ[os.fsdecode(var)]) 395 else: 396 value = environ[var] 397 except KeyError: 398 value = percent + var + percent 399 res += value 400 elif c == dollar: # variable or '$$' 401 if path[index + 1:index + 2] == dollar: 402 res += c 403 index += 1 404 elif path[index + 1:index + 2] == brace: 405 path = path[index+2:] 406 pathlen = len(path) 407 try: 408 index = path.index(rbrace) 409 except ValueError: 410 res += dollar + brace + path 411 index = pathlen - 1 412 else: 413 var = path[:index] 414 try: 415 if environ is None: 416 value = os.fsencode(os.environ[os.fsdecode(var)]) 417 else: 418 value = environ[var] 419 except KeyError: 420 value = dollar + brace + var + rbrace 421 res += value 422 else: 423 var = path[:0] 424 index += 1 425 c = path[index:index + 1] 426 while c and c in varchars: 427 var += c 428 index += 1 429 c = path[index:index + 1] 430 try: 431 if environ is None: 432 value = os.fsencode(os.environ[os.fsdecode(var)]) 433 else: 434 value = environ[var] 435 except KeyError: 436 value = dollar + var 437 res += value 438 if c: 439 index -= 1 440 else: 441 res += c 442 index += 1 443 return res 444 445 446# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. 447# Previously, this function also truncated pathnames to 8+3 format, 448# but as this module is called "ntpath", that's obviously wrong! 449 450def normpath(path): 451 """Normalize path, eliminating double slashes, etc.""" 452 path = os.fspath(path) 453 if isinstance(path, bytes): 454 sep = b'\\' 455 altsep = b'/' 456 curdir = b'.' 457 pardir = b'..' 458 special_prefixes = (b'\\\\.\\', b'\\\\?\\') 459 else: 460 sep = '\\' 461 altsep = '/' 462 curdir = '.' 463 pardir = '..' 464 special_prefixes = ('\\\\.\\', '\\\\?\\') 465 if path.startswith(special_prefixes): 466 # in the case of paths with these prefixes: 467 # \\.\ -> device names 468 # \\?\ -> literal paths 469 # do not do any normalization, but return the path 470 # unchanged apart from the call to os.fspath() 471 return path 472 path = path.replace(altsep, sep) 473 prefix, path = splitdrive(path) 474 475 # collapse initial backslashes 476 if path.startswith(sep): 477 prefix += sep 478 path = path.lstrip(sep) 479 480 comps = path.split(sep) 481 i = 0 482 while i < len(comps): 483 if not comps[i] or comps[i] == curdir: 484 del comps[i] 485 elif comps[i] == pardir: 486 if i > 0 and comps[i-1] != pardir: 487 del comps[i-1:i+1] 488 i -= 1 489 elif i == 0 and prefix.endswith(sep): 490 del comps[i] 491 else: 492 i += 1 493 else: 494 i += 1 495 # If the path is now empty, substitute '.' 496 if not prefix and not comps: 497 comps.append(curdir) 498 return prefix + sep.join(comps) 499 500def _abspath_fallback(path): 501 """Return the absolute version of a path as a fallback function in case 502 `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for 503 more. 504 505 """ 506 507 path = os.fspath(path) 508 if not isabs(path): 509 if isinstance(path, bytes): 510 cwd = os.getcwdb() 511 else: 512 cwd = os.getcwd() 513 path = join(cwd, path) 514 return normpath(path) 515 516# Return an absolute path. 517try: 518 from nt import _getfullpathname 519 520except ImportError: # not running on Windows - mock up something sensible 521 abspath = _abspath_fallback 522 523else: # use native Windows method on Windows 524 def abspath(path): 525 """Return the absolute version of a path.""" 526 try: 527 return normpath(_getfullpathname(path)) 528 except (OSError, ValueError): 529 return _abspath_fallback(path) 530 531try: 532 from nt import _getfinalpathname, readlink as _nt_readlink 533except ImportError: 534 # realpath is a no-op on systems without _getfinalpathname support. 535 realpath = abspath 536else: 537 def _readlink_deep(path): 538 # These error codes indicate that we should stop reading links and 539 # return the path we currently have. 540 # 1: ERROR_INVALID_FUNCTION 541 # 2: ERROR_FILE_NOT_FOUND 542 # 3: ERROR_DIRECTORY_NOT_FOUND 543 # 5: ERROR_ACCESS_DENIED 544 # 21: ERROR_NOT_READY (implies drive with no media) 545 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file) 546 # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points) 547 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable) 548 # 87: ERROR_INVALID_PARAMETER 549 # 4390: ERROR_NOT_A_REPARSE_POINT 550 # 4392: ERROR_INVALID_REPARSE_DATA 551 # 4393: ERROR_REPARSE_TAG_INVALID 552 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393 553 554 seen = set() 555 while normcase(path) not in seen: 556 seen.add(normcase(path)) 557 try: 558 old_path = path 559 path = _nt_readlink(path) 560 # Links may be relative, so resolve them against their 561 # own location 562 if not isabs(path): 563 # If it's something other than a symlink, we don't know 564 # what it's actually going to be resolved against, so 565 # just return the old path. 566 if not islink(old_path): 567 path = old_path 568 break 569 path = normpath(join(dirname(old_path), path)) 570 except OSError as ex: 571 if ex.winerror in allowed_winerror: 572 break 573 raise 574 except ValueError: 575 # Stop on reparse points that are not symlinks 576 break 577 return path 578 579 def _getfinalpathname_nonstrict(path): 580 # These error codes indicate that we should stop resolving the path 581 # and return the value we currently have. 582 # 1: ERROR_INVALID_FUNCTION 583 # 2: ERROR_FILE_NOT_FOUND 584 # 3: ERROR_DIRECTORY_NOT_FOUND 585 # 5: ERROR_ACCESS_DENIED 586 # 21: ERROR_NOT_READY (implies drive with no media) 587 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file) 588 # 50: ERROR_NOT_SUPPORTED 589 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable) 590 # 87: ERROR_INVALID_PARAMETER 591 # 123: ERROR_INVALID_NAME 592 # 1920: ERROR_CANT_ACCESS_FILE 593 # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink) 594 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 123, 1920, 1921 595 596 # Non-strict algorithm is to find as much of the target directory 597 # as we can and join the rest. 598 tail = '' 599 while path: 600 try: 601 path = _getfinalpathname(path) 602 return join(path, tail) if tail else path 603 except OSError as ex: 604 if ex.winerror not in allowed_winerror: 605 raise 606 try: 607 # The OS could not resolve this path fully, so we attempt 608 # to follow the link ourselves. If we succeed, join the tail 609 # and return. 610 new_path = _readlink_deep(path) 611 if new_path != path: 612 return join(new_path, tail) if tail else new_path 613 except OSError: 614 # If we fail to readlink(), let's keep traversing 615 pass 616 path, name = split(path) 617 # TODO (bpo-38186): Request the real file name from the directory 618 # entry using FindFirstFileW. For now, we will return the path 619 # as best we have it 620 if path and not name: 621 return path + tail 622 tail = join(name, tail) if tail else name 623 return tail 624 625 def realpath(path): 626 path = normpath(path) 627 if isinstance(path, bytes): 628 prefix = b'\\\\?\\' 629 unc_prefix = b'\\\\?\\UNC\\' 630 new_unc_prefix = b'\\\\' 631 cwd = os.getcwdb() 632 # bpo-38081: Special case for realpath(b'nul') 633 if normcase(path) == normcase(os.fsencode(devnull)): 634 return b'\\\\.\\NUL' 635 else: 636 prefix = '\\\\?\\' 637 unc_prefix = '\\\\?\\UNC\\' 638 new_unc_prefix = '\\\\' 639 cwd = os.getcwd() 640 # bpo-38081: Special case for realpath('nul') 641 if normcase(path) == normcase(devnull): 642 return '\\\\.\\NUL' 643 had_prefix = path.startswith(prefix) 644 if not had_prefix and not isabs(path): 645 path = join(cwd, path) 646 try: 647 path = _getfinalpathname(path) 648 initial_winerror = 0 649 except OSError as ex: 650 initial_winerror = ex.winerror 651 path = _getfinalpathname_nonstrict(path) 652 # The path returned by _getfinalpathname will always start with \\?\ - 653 # strip off that prefix unless it was already provided on the original 654 # path. 655 if not had_prefix and path.startswith(prefix): 656 # For UNC paths, the prefix will actually be \\?\UNC\ 657 # Handle that case as well. 658 if path.startswith(unc_prefix): 659 spath = new_unc_prefix + path[len(unc_prefix):] 660 else: 661 spath = path[len(prefix):] 662 # Ensure that the non-prefixed path resolves to the same path 663 try: 664 if _getfinalpathname(spath) == path: 665 path = spath 666 except OSError as ex: 667 # If the path does not exist and originally did not exist, then 668 # strip the prefix anyway. 669 if ex.winerror == initial_winerror: 670 path = spath 671 return path 672 673 674# Win9x family and earlier have no Unicode filename support. 675supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and 676 sys.getwindowsversion()[3] >= 2) 677 678def relpath(path, start=None): 679 """Return a relative version of a path""" 680 path = os.fspath(path) 681 if isinstance(path, bytes): 682 sep = b'\\' 683 curdir = b'.' 684 pardir = b'..' 685 else: 686 sep = '\\' 687 curdir = '.' 688 pardir = '..' 689 690 if start is None: 691 start = curdir 692 693 if not path: 694 raise ValueError("no path specified") 695 696 start = os.fspath(start) 697 try: 698 start_abs = abspath(normpath(start)) 699 path_abs = abspath(normpath(path)) 700 start_drive, start_rest = splitdrive(start_abs) 701 path_drive, path_rest = splitdrive(path_abs) 702 if normcase(start_drive) != normcase(path_drive): 703 raise ValueError("path is on mount %r, start on mount %r" % ( 704 path_drive, start_drive)) 705 706 start_list = [x for x in start_rest.split(sep) if x] 707 path_list = [x for x in path_rest.split(sep) if x] 708 # Work out how much of the filepath is shared by start and path. 709 i = 0 710 for e1, e2 in zip(start_list, path_list): 711 if normcase(e1) != normcase(e2): 712 break 713 i += 1 714 715 rel_list = [pardir] * (len(start_list)-i) + path_list[i:] 716 if not rel_list: 717 return curdir 718 return join(*rel_list) 719 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning): 720 genericpath._check_arg_types('relpath', path, start) 721 raise 722 723 724# Return the longest common sub-path of the sequence of paths given as input. 725# The function is case-insensitive and 'separator-insensitive', i.e. if the 726# only difference between two paths is the use of '\' versus '/' as separator, 727# they are deemed to be equal. 728# 729# However, the returned path will have the standard '\' separator (even if the 730# given paths had the alternative '/' separator) and will have the case of the 731# first path given in the sequence. Additionally, any trailing separator is 732# stripped from the returned path. 733 734def commonpath(paths): 735 """Given a sequence of path names, returns the longest common sub-path.""" 736 737 if not paths: 738 raise ValueError('commonpath() arg is an empty sequence') 739 740 paths = tuple(map(os.fspath, paths)) 741 if isinstance(paths[0], bytes): 742 sep = b'\\' 743 altsep = b'/' 744 curdir = b'.' 745 else: 746 sep = '\\' 747 altsep = '/' 748 curdir = '.' 749 750 try: 751 drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths] 752 split_paths = [p.split(sep) for d, p in drivesplits] 753 754 try: 755 isabs, = set(p[:1] == sep for d, p in drivesplits) 756 except ValueError: 757 raise ValueError("Can't mix absolute and relative paths") from None 758 759 # Check that all drive letters or UNC paths match. The check is made only 760 # now otherwise type errors for mixing strings and bytes would not be 761 # caught. 762 if len(set(d for d, p in drivesplits)) != 1: 763 raise ValueError("Paths don't have the same drive") 764 765 drive, path = splitdrive(paths[0].replace(altsep, sep)) 766 common = path.split(sep) 767 common = [c for c in common if c and c != curdir] 768 769 split_paths = [[c for c in s if c and c != curdir] for s in split_paths] 770 s1 = min(split_paths) 771 s2 = max(split_paths) 772 for i, c in enumerate(s1): 773 if c != s2[i]: 774 common = common[:i] 775 break 776 else: 777 common = common[:len(s1)] 778 779 prefix = drive + sep if isabs else drive 780 return prefix + sep.join(common) 781 except (TypeError, AttributeError): 782 genericpath._check_arg_types('commonpath', *paths) 783 raise 784 785 786try: 787 # The genericpath.isdir implementation uses os.stat and checks the mode 788 # attribute to tell whether or not the path is a directory. 789 # This is overkill on Windows - just pass the path to GetFileAttributes 790 # and check the attribute from there. 791 from nt import _isdir as isdir 792except ImportError: 793 # Use genericpath.isdir as imported above. 794 pass 795