diff --git a/contrib/wix/dist.wxs b/contrib/wix/dist.wxs --- a/contrib/wix/dist.wxs +++ b/contrib/wix/dist.wxs @@ -16,23 +16,14 @@ - - - - - - - - - + - diff --git a/contrib/wix/guids.wxi b/contrib/wix/guids.wxi --- a/contrib/wix/guids.wxi +++ b/contrib/wix/guids.wxi @@ -9,7 +9,7 @@ - + diff --git a/mercurial/posix.py b/mercurial/posix.py --- a/mercurial/posix.py +++ b/mercurial/posix.py @@ -13,6 +13,7 @@ posixfile = open nulldev = '/dev/null' normpath = os.path.normpath samestat = os.path.samestat +os_link = os.link unlink = os.unlink rename = os.rename expandglobs = False @@ -24,6 +25,10 @@ def openhardlinks(): '''return true if it is safe to hold open file handles to hardlinks''' return True +def nlinks(name): + '''return number of hardlinks for the given file''' + return os.lstat(name).st_nlink + def rcfiles(path): rcs = [os.path.join(path, 'hgrc')] rcdir = os.path.join(path, 'hgrc.d') diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -554,16 +554,6 @@ class path_auditor(object): # want to add "foo/bar/baz" before checking if there's a "foo/.hg" self.auditeddir.update(prefixes) -def nlinks(pathname): - """Return number of hardlinks for the given file.""" - return os.lstat(pathname).st_nlink - -if hasattr(os, 'link'): - os_link = os.link -else: - def os_link(src, dst): - raise OSError(0, _("Hardlinks not supported")) - def lookup_reg(key, name=None, scope=None): return None diff --git a/mercurial/win32.py b/mercurial/win32.py --- a/mercurial/win32.py +++ b/mercurial/win32.py @@ -5,74 +5,173 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -"""Utility functions that use win32 API. +import osutil, encoding +import ctypes, errno, os, struct, subprocess + +_kernel32 = ctypes.windll.kernel32 + +_BOOL = ctypes.c_long +_WORD = ctypes.c_ushort +_DWORD = ctypes.c_ulong +_LPCSTR = _LPSTR = ctypes.c_char_p +_HANDLE = ctypes.c_void_p +_HWND = _HANDLE + +_INVALID_HANDLE_VALUE = -1 + +# GetLastError +_ERROR_SUCCESS = 0 +_ERROR_INVALID_PARAMETER = 87 +_ERROR_INSUFFICIENT_BUFFER = 122 + +# WPARAM is defined as UINT_PTR (unsigned type) +# LPARAM is defined as LONG_PTR (signed type) +if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p): + _WPARAM = ctypes.c_ulong + _LPARAM = ctypes.c_long +elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p): + _WPARAM = ctypes.c_ulonglong + _LPARAM = ctypes.c_longlong + +class _FILETIME(ctypes.Structure): + _fields_ = [('dwLowDateTime', _DWORD), + ('dwHighDateTime', _DWORD)] + +class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure): + _fields_ = [('dwFileAttributes', _DWORD), + ('ftCreationTime', _FILETIME), + ('ftLastAccessTime', _FILETIME), + ('ftLastWriteTime', _FILETIME), + ('dwVolumeSerialNumber', _DWORD), + ('nFileSizeHigh', _DWORD), + ('nFileSizeLow', _DWORD), + ('nNumberOfLinks', _DWORD), + ('nFileIndexHigh', _DWORD), + ('nFileIndexLow', _DWORD)] + +# CreateFile +_FILE_SHARE_READ = 0x00000001 +_FILE_SHARE_WRITE = 0x00000002 +_FILE_SHARE_DELETE = 0x00000004 + +_OPEN_EXISTING = 3 + +# Process Security and Access Rights +_PROCESS_QUERY_INFORMATION = 0x0400 + +# GetExitCodeProcess +_STILL_ACTIVE = 259 + +# registry +_HKEY_CURRENT_USER = 0x80000001L +_HKEY_LOCAL_MACHINE = 0x80000002L +_KEY_READ = 0x20019 +_REG_SZ = 1 +_REG_DWORD = 4 -Mark Hammond's win32all package allows better functionality on -Windows. This module overrides definitions in util.py. If not -available, import of this module will fail, and generic code will be -used. -""" +class _STARTUPINFO(ctypes.Structure): + _fields_ = [('cb', _DWORD), + ('lpReserved', _LPSTR), + ('lpDesktop', _LPSTR), + ('lpTitle', _LPSTR), + ('dwX', _DWORD), + ('dwY', _DWORD), + ('dwXSize', _DWORD), + ('dwYSize', _DWORD), + ('dwXCountChars', _DWORD), + ('dwYCountChars', _DWORD), + ('dwFillAttribute', _DWORD), + ('dwFlags', _DWORD), + ('wShowWindow', _WORD), + ('cbReserved2', _WORD), + ('lpReserved2', ctypes.c_char_p), + ('hStdInput', _HANDLE), + ('hStdOutput', _HANDLE), + ('hStdError', _HANDLE)] + +class _PROCESS_INFORMATION(ctypes.Structure): + _fields_ = [('hProcess', _HANDLE), + ('hThread', _HANDLE), + ('dwProcessId', _DWORD), + ('dwThreadId', _DWORD)] + +_DETACHED_PROCESS = 0x00000008 +_STARTF_USESHOWWINDOW = 0x00000001 +_SW_HIDE = 0 -import win32api +class _COORD(ctypes.Structure): + _fields_ = [('X', ctypes.c_short), + ('Y', ctypes.c_short)] + +class _SMALL_RECT(ctypes.Structure): + _fields_ = [('Left', ctypes.c_short), + ('Top', ctypes.c_short), + ('Right', ctypes.c_short), + ('Bottom', ctypes.c_short)] + +class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): + _fields_ = [('dwSize', _COORD), + ('dwCursorPosition', _COORD), + ('wAttributes', _WORD), + ('srWindow', _SMALL_RECT), + ('dwMaximumWindowSize', _COORD)] -import errno, os, sys, pywintypes, win32con, win32file, win32process -import winerror, win32gui, win32console -import osutil, encoding -from win32com.shell import shell, shellcon +_STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12 + +def _raiseoserror(name): + err = ctypes.WinError() + raise OSError(err.errno, '%s: %s' % (name, err.strerror)) + +def _getfileinfo(name): + fh = _kernel32.CreateFileA(name, 0, + _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE, + None, _OPEN_EXISTING, 0, None) + if fh == _INVALID_HANDLE_VALUE: + _raiseoserror(name) + try: + fi = _BY_HANDLE_FILE_INFORMATION() + if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)): + _raiseoserror(name) + return fi + finally: + _kernel32.CloseHandle(fh) def os_link(src, dst): - try: - win32file.CreateHardLink(dst, src) - except pywintypes.error: - raise OSError(errno.EINVAL, 'target implements hardlinks improperly') - except NotImplementedError: # Another fake error win Win98 - raise OSError(errno.EINVAL, 'Hardlinking not supported') + if not _kernel32.CreateHardLinkA(dst, src, None): + _raiseoserror(src) -def _getfileinfo(pathname): - """Return number of hardlinks for the given file.""" - try: - fh = win32file.CreateFile(pathname, 0, - win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE | - win32file.FILE_SHARE_DELETE, - None, win32file.OPEN_EXISTING, 0, None) - except pywintypes.error: - raise OSError(errno.ENOENT, 'The system cannot find the file specified') - try: - return win32file.GetFileInformationByHandle(fh) - finally: - fh.Close() - -def nlinks(pathname): - """Return number of hardlinks for the given file.""" - return _getfileinfo(pathname)[7] +def nlinks(name): + '''return number of hardlinks for the given file''' + return _getfileinfo(name).nNumberOfLinks def samefile(fpath1, fpath2): - """Returns whether fpath1 and fpath2 refer to the same file. This is only - guaranteed to work for files, not directories.""" + '''Returns whether fpath1 and fpath2 refer to the same file. This is only + guaranteed to work for files, not directories.''' res1 = _getfileinfo(fpath1) res2 = _getfileinfo(fpath2) - # Index 4 is the volume serial number, and 8 and 9 contain the file ID - return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9] + return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber + and res1.nFileIndexHigh == res2.nFileIndexHigh + and res1.nFileIndexLow == res2.nFileIndexLow) def samedevice(fpath1, fpath2): - """Returns whether fpath1 and fpath2 are on the same device. This is only - guaranteed to work for files, not directories.""" + '''Returns whether fpath1 and fpath2 are on the same device. This is only + guaranteed to work for files, not directories.''' res1 = _getfileinfo(fpath1) res2 = _getfileinfo(fpath2) - return res1[4] == res2[4] + return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber def testpid(pid): '''return True if pid is still running or unable to determine, False otherwise''' - try: - handle = win32api.OpenProcess( - win32con.PROCESS_QUERY_INFORMATION, False, pid) - if handle: - status = win32process.GetExitCodeProcess(handle) - return status == win32con.STILL_ACTIVE - except pywintypes.error, details: - return details[0] != winerror.ERROR_INVALID_PARAMETER - return True + h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid) + if h: + try: + status = _DWORD() + if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)): + return status.value == _STILL_ACTIVE + finally: + _kernel32.CloseHandle(h) + return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER def lookup_reg(key, valname=None, scope=None): ''' Look up a key/value name in the Windows registry. @@ -83,101 +182,169 @@ def lookup_reg(key, valname=None, scope= a sequence of scopes to look up in order. Default (CURRENT_USER, LOCAL_MACHINE). ''' - try: - from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \ - QueryValueEx, OpenKey - except ImportError: - return None - + adv = ctypes.windll.advapi32 + byref = ctypes.byref if scope is None: - scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE) + scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE) elif not isinstance(scope, (list, tuple)): scope = (scope,) for s in scope: + kh = _HANDLE() + res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh)) + if res != _ERROR_SUCCESS: + continue try: - val = QueryValueEx(OpenKey(s, key), valname)[0] - # never let a Unicode string escape into the wild - return encoding.tolocal(val.encode('UTF-8')) - except EnvironmentError: - pass + size = _DWORD(600) + type = _DWORD() + buf = ctypes.create_string_buffer(size.value + 1) + res = adv.RegQueryValueExA(kh.value, valname, None, + byref(type), buf, byref(size)) + if res != _ERROR_SUCCESS: + continue + if type.value == _REG_SZ: + # never let a Unicode string escape into the wild + return encoding.tolocal(buf.value.encode('UTF-8')) + elif type.value == _REG_DWORD: + fmt = '