mirror of
https://gerrit.googlesource.com/git-repo
synced 2024-12-21 07:16:21 +00:00
Implement islink, readlink and realpath using Win32 api
Change-Id: I18452cbb32d24db73601ad10485dbe6bb278731c
This commit is contained in:
parent
2a4be94878
commit
227ad2ef42
@ -242,3 +242,57 @@ def rename(src, dst):
|
|||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
os.rename(src, dst)
|
os.rename(src, dst)
|
||||||
|
|
||||||
|
|
||||||
|
def islink(path):
|
||||||
|
"""Test whether a path is a symbolic link.
|
||||||
|
|
||||||
|
Availability: Windows, Unix.
|
||||||
|
"""
|
||||||
|
if isWindows():
|
||||||
|
import platform_utils_win32
|
||||||
|
return platform_utils_win32.islink(path)
|
||||||
|
else:
|
||||||
|
return os.path.islink(path)
|
||||||
|
|
||||||
|
|
||||||
|
def readlink(path):
|
||||||
|
"""Return a string representing the path to which the symbolic link
|
||||||
|
points. The result may be either an absolute or relative pathname;
|
||||||
|
if it is relative, it may be converted to an absolute pathname using
|
||||||
|
os.path.join(os.path.dirname(path), result).
|
||||||
|
|
||||||
|
Availability: Windows, Unix.
|
||||||
|
"""
|
||||||
|
if isWindows():
|
||||||
|
import platform_utils_win32
|
||||||
|
return platform_utils_win32.readlink(path)
|
||||||
|
else:
|
||||||
|
return os.readlink(path)
|
||||||
|
|
||||||
|
|
||||||
|
def realpath(path):
|
||||||
|
"""Return the canonical path of the specified filename, eliminating
|
||||||
|
any symbolic links encountered in the path.
|
||||||
|
|
||||||
|
Availability: Windows, Unix.
|
||||||
|
"""
|
||||||
|
if isWindows():
|
||||||
|
current_path = os.path.abspath(path)
|
||||||
|
path_tail = []
|
||||||
|
for c in range(0, 100): # Avoid cycles
|
||||||
|
if islink(current_path):
|
||||||
|
target = readlink(current_path)
|
||||||
|
current_path = os.path.join(os.path.dirname(current_path), target)
|
||||||
|
else:
|
||||||
|
basename = os.path.basename(current_path)
|
||||||
|
if basename == '':
|
||||||
|
path_tail.append(current_path)
|
||||||
|
break
|
||||||
|
path_tail.append(basename)
|
||||||
|
current_path = os.path.dirname(current_path)
|
||||||
|
path_tail.reverse()
|
||||||
|
result = os.path.normpath(os.path.join(*path_tail))
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return os.path.realpath(path)
|
||||||
|
@ -15,13 +15,20 @@
|
|||||||
|
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
from ctypes import WinDLL, get_last_error, FormatError, WinError
|
from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
|
||||||
from ctypes.wintypes import BOOL, LPCWSTR, DWORD
|
from ctypes import c_buffer
|
||||||
|
from ctypes.wintypes import BOOL, LPCWSTR, DWORD, HANDLE, POINTER, c_ubyte
|
||||||
|
from ctypes.wintypes import WCHAR, USHORT, LPVOID, Structure, Union, ULONG
|
||||||
|
from ctypes.wintypes import byref
|
||||||
|
|
||||||
kernel32 = WinDLL('kernel32', use_last_error=True)
|
kernel32 = WinDLL('kernel32', use_last_error=True)
|
||||||
|
|
||||||
|
LPDWORD = POINTER(DWORD)
|
||||||
|
UCHAR = c_ubyte
|
||||||
|
|
||||||
# Win32 error codes
|
# Win32 error codes
|
||||||
ERROR_SUCCESS = 0
|
ERROR_SUCCESS = 0
|
||||||
|
ERROR_NOT_SUPPORTED = 50
|
||||||
ERROR_PRIVILEGE_NOT_HELD = 1314
|
ERROR_PRIVILEGE_NOT_HELD = 1314
|
||||||
|
|
||||||
# Win32 API entry points
|
# Win32 API entry points
|
||||||
@ -35,6 +42,94 @@ CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
|
|||||||
SYMBOLIC_LINK_FLAG_FILE = 0x00
|
SYMBOLIC_LINK_FLAG_FILE = 0x00
|
||||||
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
|
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
|
||||||
|
|
||||||
|
GetFileAttributesW = kernel32.GetFileAttributesW
|
||||||
|
GetFileAttributesW.restype = DWORD
|
||||||
|
GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In
|
||||||
|
|
||||||
|
INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
|
||||||
|
FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
|
||||||
|
|
||||||
|
CreateFileW = kernel32.CreateFileW
|
||||||
|
CreateFileW.restype = HANDLE
|
||||||
|
CreateFileW.argtypes = (LPCWSTR, # lpFileName In
|
||||||
|
DWORD, # dwDesiredAccess In
|
||||||
|
DWORD, # dwShareMode In
|
||||||
|
LPVOID, # lpSecurityAttributes In_opt
|
||||||
|
DWORD, # dwCreationDisposition In
|
||||||
|
DWORD, # dwFlagsAndAttributes In
|
||||||
|
HANDLE) # hTemplateFile In_opt
|
||||||
|
|
||||||
|
CloseHandle = kernel32.CloseHandle
|
||||||
|
CloseHandle.restype = BOOL
|
||||||
|
CloseHandle.argtypes = (HANDLE,) # hObject In
|
||||||
|
|
||||||
|
INVALID_HANDLE_VALUE = HANDLE(-1).value
|
||||||
|
OPEN_EXISTING = 3
|
||||||
|
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
|
||||||
|
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
|
||||||
|
|
||||||
|
DeviceIoControl = kernel32.DeviceIoControl
|
||||||
|
DeviceIoControl.restype = BOOL
|
||||||
|
DeviceIoControl.argtypes = (HANDLE, # hDevice In
|
||||||
|
DWORD, # dwIoControlCode In
|
||||||
|
LPVOID, # lpInBuffer In_opt
|
||||||
|
DWORD, # nInBufferSize In
|
||||||
|
LPVOID, # lpOutBuffer Out_opt
|
||||||
|
DWORD, # nOutBufferSize In
|
||||||
|
LPDWORD, # lpBytesReturned Out_opt
|
||||||
|
LPVOID) # lpOverlapped Inout_opt
|
||||||
|
|
||||||
|
# Device I/O control flags and options
|
||||||
|
FSCTL_GET_REPARSE_POINT = 0x000900A8
|
||||||
|
IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
|
||||||
|
IO_REPARSE_TAG_SYMLINK = 0xA000000C
|
||||||
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
|
||||||
|
|
||||||
|
|
||||||
|
class GENERIC_REPARSE_BUFFER(Structure):
|
||||||
|
_fields_ = (('DataBuffer', UCHAR * 1),)
|
||||||
|
|
||||||
|
|
||||||
|
class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
|
||||||
|
_fields_ = (('SubstituteNameOffset', USHORT),
|
||||||
|
('SubstituteNameLength', USHORT),
|
||||||
|
('PrintNameOffset', USHORT),
|
||||||
|
('PrintNameLength', USHORT),
|
||||||
|
('Flags', ULONG),
|
||||||
|
('PathBuffer', WCHAR * 1))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def PrintName(self):
|
||||||
|
arrayt = WCHAR * (self.PrintNameLength // 2)
|
||||||
|
offset = type(self).PathBuffer.offset + self.PrintNameOffset
|
||||||
|
return arrayt.from_address(addressof(self) + offset).value
|
||||||
|
|
||||||
|
|
||||||
|
class MOUNT_POINT_REPARSE_BUFFER(Structure):
|
||||||
|
_fields_ = (('SubstituteNameOffset', USHORT),
|
||||||
|
('SubstituteNameLength', USHORT),
|
||||||
|
('PrintNameOffset', USHORT),
|
||||||
|
('PrintNameLength', USHORT),
|
||||||
|
('PathBuffer', WCHAR * 1))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def PrintName(self):
|
||||||
|
arrayt = WCHAR * (self.PrintNameLength // 2)
|
||||||
|
offset = type(self).PathBuffer.offset + self.PrintNameOffset
|
||||||
|
return arrayt.from_address(addressof(self) + offset).value
|
||||||
|
|
||||||
|
|
||||||
|
class REPARSE_DATA_BUFFER(Structure):
|
||||||
|
class REPARSE_BUFFER(Union):
|
||||||
|
_fields_ = (('SymbolicLinkReparseBuffer', SYMBOLIC_LINK_REPARSE_BUFFER),
|
||||||
|
('MountPointReparseBuffer', MOUNT_POINT_REPARSE_BUFFER),
|
||||||
|
('GenericReparseBuffer', GENERIC_REPARSE_BUFFER))
|
||||||
|
_fields_ = (('ReparseTag', ULONG),
|
||||||
|
('ReparseDataLength', USHORT),
|
||||||
|
('Reserved', USHORT),
|
||||||
|
('ReparseBuffer', REPARSE_BUFFER))
|
||||||
|
_anonymous_ = ('ReparseBuffer',)
|
||||||
|
|
||||||
|
|
||||||
def create_filesymlink(source, link_name):
|
def create_filesymlink(source, link_name):
|
||||||
"""Creates a Windows file symbolic link source pointing to link_name."""
|
"""Creates a Windows file symbolic link source pointing to link_name."""
|
||||||
@ -58,6 +153,65 @@ def _create_symlink(source, link_name, dwFlags):
|
|||||||
error_desc = FormatError(code).strip()
|
error_desc = FormatError(code).strip()
|
||||||
if code == ERROR_PRIVILEGE_NOT_HELD:
|
if code == ERROR_PRIVILEGE_NOT_HELD:
|
||||||
raise OSError(errno.EPERM, error_desc, link_name)
|
raise OSError(errno.EPERM, error_desc, link_name)
|
||||||
error_desc = 'Error creating symbolic link %s: %s'.format(
|
_raise_winerror(
|
||||||
link_name, error_desc)
|
code,
|
||||||
|
'Error creating symbolic link \"%s\"'.format(link_name))
|
||||||
|
|
||||||
|
|
||||||
|
def islink(path):
|
||||||
|
result = GetFileAttributesW(path)
|
||||||
|
if result == INVALID_FILE_ATTRIBUTES:
|
||||||
|
return False
|
||||||
|
return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||||
|
|
||||||
|
|
||||||
|
def readlink(path):
|
||||||
|
reparse_point_handle = CreateFileW(path,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
OPEN_EXISTING,
|
||||||
|
FILE_FLAG_OPEN_REPARSE_POINT |
|
||||||
|
FILE_FLAG_BACKUP_SEMANTICS,
|
||||||
|
None)
|
||||||
|
if reparse_point_handle == INVALID_HANDLE_VALUE:
|
||||||
|
_raise_winerror(
|
||||||
|
get_last_error(),
|
||||||
|
'Error opening symblic link \"%s\"'.format(path))
|
||||||
|
target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
|
||||||
|
n_bytes_returned = DWORD()
|
||||||
|
io_result = DeviceIoControl(reparse_point_handle,
|
||||||
|
FSCTL_GET_REPARSE_POINT,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
target_buffer,
|
||||||
|
len(target_buffer),
|
||||||
|
byref(n_bytes_returned),
|
||||||
|
None)
|
||||||
|
CloseHandle(reparse_point_handle)
|
||||||
|
if not io_result:
|
||||||
|
_raise_winerror(
|
||||||
|
get_last_error(),
|
||||||
|
'Error reading symblic link \"%s\"'.format(path))
|
||||||
|
rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
|
||||||
|
if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
|
||||||
|
return _preserve_encoding(path, rdb.SymbolicLinkReparseBuffer.PrintName)
|
||||||
|
elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
|
||||||
|
return _preserve_encoding(path, rdb.MountPointReparseBuffer.PrintName)
|
||||||
|
# Unsupported reparse point type
|
||||||
|
_raise_winerror(
|
||||||
|
ERROR_NOT_SUPPORTED,
|
||||||
|
'Error reading symblic link \"%s\"'.format(path))
|
||||||
|
|
||||||
|
|
||||||
|
def _preserve_encoding(source, target):
|
||||||
|
"""Ensures target is the same string type (i.e. unicode or str) as source."""
|
||||||
|
if isinstance(source, unicode):
|
||||||
|
return unicode(target)
|
||||||
|
return str(target)
|
||||||
|
|
||||||
|
|
||||||
|
def _raise_winerror(code, error_desc):
|
||||||
|
win_error_desc = FormatError(code).strip()
|
||||||
|
error_desc = "%s: %s".format(error_desc, win_error_desc)
|
||||||
raise WinError(code, error_desc)
|
raise WinError(code, error_desc)
|
||||||
|
26
project.py
26
project.py
@ -103,7 +103,7 @@ def _ProjectHooks():
|
|||||||
"""
|
"""
|
||||||
global _project_hook_list
|
global _project_hook_list
|
||||||
if _project_hook_list is None:
|
if _project_hook_list is None:
|
||||||
d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
|
d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
|
||||||
d = os.path.join(d, 'hooks')
|
d = os.path.join(d, 'hooks')
|
||||||
_project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
|
_project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
|
||||||
return _project_hook_list
|
return _project_hook_list
|
||||||
@ -275,7 +275,7 @@ class _LinkFile(object):
|
|||||||
|
|
||||||
def __linkIt(self, relSrc, absDest):
|
def __linkIt(self, relSrc, absDest):
|
||||||
# link file if it does not exist or is out of date
|
# link file if it does not exist or is out of date
|
||||||
if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
|
if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
|
||||||
try:
|
try:
|
||||||
# remove existing file first, since it might be read-only
|
# remove existing file first, since it might be read-only
|
||||||
if os.path.lexists(absDest):
|
if os.path.lexists(absDest):
|
||||||
@ -2315,10 +2315,10 @@ class Project(object):
|
|||||||
print("Retrying clone after deleting %s" %
|
print("Retrying clone after deleting %s" %
|
||||||
self.gitdir, file=sys.stderr)
|
self.gitdir, file=sys.stderr)
|
||||||
try:
|
try:
|
||||||
platform_utils.rmtree(os.path.realpath(self.gitdir))
|
platform_utils.rmtree(platform_utils.realpath(self.gitdir))
|
||||||
if self.worktree and os.path.exists(os.path.realpath
|
if self.worktree and os.path.exists(platform_utils.realpath
|
||||||
(self.worktree)):
|
(self.worktree)):
|
||||||
platform_utils.rmtree(os.path.realpath(self.worktree))
|
platform_utils.rmtree(platform_utils.realpath(self.worktree))
|
||||||
return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
|
return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
|
||||||
except:
|
except:
|
||||||
raise e
|
raise e
|
||||||
@ -2370,7 +2370,7 @@ class Project(object):
|
|||||||
self._InitHooks()
|
self._InitHooks()
|
||||||
|
|
||||||
def _InitHooks(self):
|
def _InitHooks(self):
|
||||||
hooks = os.path.realpath(self._gitdir_path('hooks'))
|
hooks = platform_utils.realpath(self._gitdir_path('hooks'))
|
||||||
if not os.path.exists(hooks):
|
if not os.path.exists(hooks):
|
||||||
os.makedirs(hooks)
|
os.makedirs(hooks)
|
||||||
for stock_hook in _ProjectHooks():
|
for stock_hook in _ProjectHooks():
|
||||||
@ -2386,7 +2386,7 @@ class Project(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
dst = os.path.join(hooks, name)
|
dst = os.path.join(hooks, name)
|
||||||
if os.path.islink(dst):
|
if platform_utils.islink(dst):
|
||||||
continue
|
continue
|
||||||
if os.path.exists(dst):
|
if os.path.exists(dst):
|
||||||
if filecmp.cmp(stock_hook, dst, shallow=False):
|
if filecmp.cmp(stock_hook, dst, shallow=False):
|
||||||
@ -2448,9 +2448,9 @@ class Project(object):
|
|||||||
symlink_dirs += self.working_tree_dirs
|
symlink_dirs += self.working_tree_dirs
|
||||||
to_symlink = symlink_files + symlink_dirs
|
to_symlink = symlink_files + symlink_dirs
|
||||||
for name in set(to_symlink):
|
for name in set(to_symlink):
|
||||||
dst = os.path.realpath(os.path.join(destdir, name))
|
dst = platform_utils.realpath(os.path.join(destdir, name))
|
||||||
if os.path.lexists(dst):
|
if os.path.lexists(dst):
|
||||||
src = os.path.realpath(os.path.join(srcdir, name))
|
src = platform_utils.realpath(os.path.join(srcdir, name))
|
||||||
# Fail if the links are pointing to the wrong place
|
# Fail if the links are pointing to the wrong place
|
||||||
if src != dst:
|
if src != dst:
|
||||||
_error('%s is different in %s vs %s', name, destdir, srcdir)
|
_error('%s is different in %s vs %s', name, destdir, srcdir)
|
||||||
@ -2482,10 +2482,10 @@ class Project(object):
|
|||||||
if copy_all:
|
if copy_all:
|
||||||
to_copy = os.listdir(gitdir)
|
to_copy = os.listdir(gitdir)
|
||||||
|
|
||||||
dotgit = os.path.realpath(dotgit)
|
dotgit = platform_utils.realpath(dotgit)
|
||||||
for name in set(to_copy).union(to_symlink):
|
for name in set(to_copy).union(to_symlink):
|
||||||
try:
|
try:
|
||||||
src = os.path.realpath(os.path.join(gitdir, name))
|
src = platform_utils.realpath(os.path.join(gitdir, name))
|
||||||
dst = os.path.join(dotgit, name)
|
dst = os.path.join(dotgit, name)
|
||||||
|
|
||||||
if os.path.lexists(dst):
|
if os.path.lexists(dst):
|
||||||
@ -2498,7 +2498,7 @@ class Project(object):
|
|||||||
if name in to_symlink:
|
if name in to_symlink:
|
||||||
platform_utils.symlink(
|
platform_utils.symlink(
|
||||||
os.path.relpath(src, os.path.dirname(dst)), dst)
|
os.path.relpath(src, os.path.dirname(dst)), dst)
|
||||||
elif copy_all and not os.path.islink(dst):
|
elif copy_all and not platform_utils.islink(dst):
|
||||||
if os.path.isdir(src):
|
if os.path.isdir(src):
|
||||||
shutil.copytree(src, dst)
|
shutil.copytree(src, dst)
|
||||||
elif os.path.isfile(src):
|
elif os.path.isfile(src):
|
||||||
@ -2556,7 +2556,7 @@ class Project(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def _gitdir_path(self, path):
|
def _gitdir_path(self, path):
|
||||||
return os.path.realpath(os.path.join(self.gitdir, path))
|
return platform_utils.realpath(os.path.join(self.gitdir, path))
|
||||||
|
|
||||||
def _revlist(self, *args, **kw):
|
def _revlist(self, *args, **kw):
|
||||||
a = []
|
a = []
|
||||||
|
@ -498,7 +498,7 @@ later is required to fix a server side protocol bug.
|
|||||||
dirs_to_remove += [os.path.join(root, d) for d in dirs
|
dirs_to_remove += [os.path.join(root, d) for d in dirs
|
||||||
if os.path.join(root, d) not in dirs_to_remove]
|
if os.path.join(root, d) not in dirs_to_remove]
|
||||||
for d in reversed(dirs_to_remove):
|
for d in reversed(dirs_to_remove):
|
||||||
if os.path.islink(d):
|
if platform_utils.islink(d):
|
||||||
try:
|
try:
|
||||||
os.remove(d)
|
os.remove(d)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
Loading…
Reference in New Issue
Block a user