summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2018-01-18 20:54:26 -0500
committerGitHub <noreply@github.com>2018-01-18 20:54:26 -0500
commit1942ed24c1d33eff80b638f0aea0d2fef537be02 (patch)
tree766120d4ebd342ef941c7177a0196ce9d4092141
parent80f93ce19f2f0cf37505692458a7576aade7b75f (diff)
parentf3e52e81404d23d14f557cffcd0fe0a25b9c3cfc (diff)
downloadsetuptools-scm-1942ed24c1d33eff80b638f0aea0d2fef537be02.tar.gz
Merge pull request #207 from pypa/simple-samefile
Use samefile to compare if two paths refer to the same path.
-rw-r--r--CHANGELOG.rst6
-rw-r--r--setuptools_scm/git.py10
-rw-r--r--setuptools_scm/utils.py57
-rw-r--r--setuptools_scm/win_py31_compat.py214
-rw-r--r--testing/test_functions.py58
5 files changed, 228 insertions, 117 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c574737..7908953 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,9 @@
+v1.15.8
+======
+
+* #207: Re-use samefile backport as developed in jaraco.windows,
+ and only use the backport where samefile is not available.
+
v1.15.7
======
diff --git a/setuptools_scm/git.py b/setuptools_scm/git.py
index 2d5f7fa..7fa1b32 100644
--- a/setuptools_scm/git.py
+++ b/setuptools_scm/git.py
@@ -1,8 +1,14 @@
-from .utils import do_ex, trace, has_command, _normalized
+from .utils import do_ex, trace, has_command
from .version import meta
from os.path import isfile, join
import warnings
+try:
+ from os.path import samefile
+except ImportError:
+ from .win_py31_compat import samefile
+
+
FILES_COMMAND = 'git ls-files'
DEFAULT_DESCRIBE = 'git describe --dirty --tags --long --match *.*'
@@ -21,7 +27,7 @@ class GitWorkdir(object):
if ret:
return
trace('real root', real_wd)
- if _normalized(real_wd) != _normalized(wd):
+ if not samefile(real_wd, wd):
return
return cls(real_wd)
diff --git a/setuptools_scm/utils.py b/setuptools_scm/utils.py
index e338de9..c58ec49 100644
--- a/setuptools_scm/utils.py
+++ b/setuptools_scm/utils.py
@@ -7,7 +7,6 @@ import sys
import shlex
import subprocess
import os
-from os.path import abspath, normcase, realpath
import io
import platform
@@ -108,59 +107,3 @@ def has_command(name):
if not res:
warnings.warn("%r was not found" % name)
return res
-
-
-def _normalized(path):
- if IS_WINDOWS:
- path = get_windows_long_path_name(path)
- return normcase(abspath(realpath(path)))
-
-
-if IS_WINDOWS:
- from ctypes import create_unicode_buffer, windll, WinError
- from ctypes.wintypes import MAX_PATH, LPCWSTR, LPWSTR, DWORD
-
- GetLongPathNameW = windll.kernel32.GetLongPathNameW
- GetLongPathNameW.argtypes = [LPCWSTR, LPWSTR, DWORD]
- GetLongPathNameW.restype = DWORD
-
- def get_windows_long_path_name(path):
- """
- Converts the specified path from short (MS-DOS style) to long form
- using the 'GetLongPathNameW' function from Windows API.
-
- https://msdn.microsoft.com/en-us/library/windows/desktop/aa364980(v=vs.85).aspx
- """
- if PY2:
- # decode path using filesystem encoding on python2; on python3
- # it is already a unicode string
- path = unicode(path, sys.getfilesystemencoding()) # noqa
-
- pathlen = MAX_PATH + 1
- if DEBUG:
- # test reallocation logic
- pathlen = 1
-
- for _ in range(2):
- buf = create_unicode_buffer(pathlen)
- retval = GetLongPathNameW(path, buf, pathlen)
-
- if retval == 0:
- # if the function fails for any reason (e.g. file does not
- # exist), the return value is zero
- raise WinError()
-
- if retval <= pathlen:
- # the function succeeded: the return value is the length of
- # the string copied to the buffer
- if PY2:
- # re-encode to native 'str' type (i.e. bytes) on python2
- return buf.value.encode(sys.getfilesystemencoding())
- return buf.value
-
- # if the buffer is too small to contain the result, the return
- # value is the size of the buffer required to hold the path and
- # the terminating NULL char; we retry using a large enough buffer
- pathlen = retval
-
- raise RuntimeError("Failed to get long path name: {!r}".format(path))
diff --git a/setuptools_scm/win_py31_compat.py b/setuptools_scm/win_py31_compat.py
new file mode 100644
index 0000000..82a11eb
--- /dev/null
+++ b/setuptools_scm/win_py31_compat.py
@@ -0,0 +1,214 @@
+"""
+Backport of os.path.samefile for Python prior to 3.2
+on Windows from jaraco.windows 3.8.
+
+DON'T EDIT THIS FILE!
+
+Instead, file tickets and PR's with `jaraco.windows
+<https://github.com/jaraco/jaraco.windows>`_ and request
+a port to setuptools_scm.
+"""
+
+import os
+import nt
+import posixpath
+import ctypes.wintypes
+import sys
+import __builtin__ as builtins
+
+
+##
+# From jaraco.windows.error
+
+def format_system_message(errno):
+ """
+ Call FormatMessage with a system error number to retrieve
+ the descriptive error message.
+ """
+ # first some flags used by FormatMessageW
+ ALLOCATE_BUFFER = 0x100
+ FROM_SYSTEM = 0x1000
+
+ # Let FormatMessageW allocate the buffer (we'll free it below)
+ # Also, let it know we want a system error message.
+ flags = ALLOCATE_BUFFER | FROM_SYSTEM
+ source = None
+ message_id = errno
+ language_id = 0
+ result_buffer = ctypes.wintypes.LPWSTR()
+ buffer_size = 0
+ arguments = None
+ bytes = ctypes.windll.kernel32.FormatMessageW(
+ flags,
+ source,
+ message_id,
+ language_id,
+ ctypes.byref(result_buffer),
+ buffer_size,
+ arguments,
+ )
+ # note the following will cause an infinite loop if GetLastError
+ # repeatedly returns an error that cannot be formatted, although
+ # this should not happen.
+ handle_nonzero_success(bytes)
+ message = result_buffer.value
+ ctypes.windll.kernel32.LocalFree(result_buffer)
+ return message
+
+
+class WindowsError(builtins.WindowsError):
+ """
+ More info about errors at
+ http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
+ """
+
+ def __init__(self, value=None):
+ if value is None:
+ value = ctypes.windll.kernel32.GetLastError()
+ strerror = format_system_message(value)
+ if sys.version_info > (3, 3):
+ args = 0, strerror, None, value
+ else:
+ args = value, strerror
+ super(WindowsError, self).__init__(*args)
+
+ @property
+ def message(self):
+ return self.strerror
+
+ @property
+ def code(self):
+ return self.winerror
+
+ def __str__(self):
+ return self.message
+
+ def __repr__(self):
+ return '{self.__class__.__name__}({self.winerror})'.format(**vars())
+
+
+def handle_nonzero_success(result):
+ if result == 0:
+ raise WindowsError()
+
+
+##
+# From jaraco.windows.api.filesystem
+
+FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
+FILE_FLAG_BACKUP_SEMANTICS = 0x2000000
+OPEN_EXISTING = 3
+FILE_ATTRIBUTE_NORMAL = 0x80
+FILE_READ_ATTRIBUTES = 0x80
+INVALID_HANDLE_VALUE = ctypes.wintypes.HANDLE(-1).value
+
+
+class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
+ _fields_ = [
+ ('file_attributes', ctypes.wintypes.DWORD),
+ ('creation_time', ctypes.wintypes.FILETIME),
+ ('last_access_time', ctypes.wintypes.FILETIME),
+ ('last_write_time', ctypes.wintypes.FILETIME),
+ ('volume_serial_number', ctypes.wintypes.DWORD),
+ ('file_size_high', ctypes.wintypes.DWORD),
+ ('file_size_low', ctypes.wintypes.DWORD),
+ ('number_of_links', ctypes.wintypes.DWORD),
+ ('file_index_high', ctypes.wintypes.DWORD),
+ ('file_index_low', ctypes.wintypes.DWORD),
+ ]
+
+ @property
+ def file_size(self):
+ return (self.file_size_high << 32) + self.file_size_low
+
+ @property
+ def file_index(self):
+ return (self.file_index_high << 32) + self.file_index_low
+
+
+class SECURITY_ATTRIBUTES(ctypes.Structure):
+ _fields_ = (
+ ('length', ctypes.wintypes.DWORD),
+ ('p_security_descriptor', ctypes.wintypes.LPVOID),
+ ('inherit_handle', ctypes.wintypes.BOOLEAN),
+ )
+
+
+LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)
+
+
+CreateFile = ctypes.windll.kernel32.CreateFileW
+CreateFile.argtypes = (
+ ctypes.wintypes.LPWSTR,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.DWORD,
+ LPSECURITY_ATTRIBUTES,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.HANDLE,
+)
+CreateFile.restype = ctypes.wintypes.HANDLE
+
+GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle
+GetFileInformationByHandle.restype = ctypes.wintypes.BOOL
+GetFileInformationByHandle.argtypes = (
+ ctypes.wintypes.HANDLE,
+ ctypes.POINTER(BY_HANDLE_FILE_INFORMATION),
+)
+
+
+##
+# From jaraco.windows.filesystem
+
+def compat_stat(path):
+ """
+ Generate stat as found on Python 3.2 and later.
+ """
+ stat = os.stat(path)
+ info = get_file_info(path)
+ # rewrite st_ino, st_dev, and st_nlink based on file info
+ return nt.stat_result(
+ (stat.st_mode,) +
+ (info.file_index, info.volume_serial_number, info.number_of_links) +
+ stat[4:]
+ )
+
+
+def samefile(f1, f2):
+ """
+ Backport of samefile from Python 3.2 with support for Windows.
+ """
+ return posixpath.samestat(compat_stat(f1), compat_stat(f2))
+
+
+def get_file_info(path):
+ # open the file the same way CPython does in posixmodule.c
+ desired_access = FILE_READ_ATTRIBUTES
+ share_mode = 0
+ security_attributes = None
+ creation_disposition = OPEN_EXISTING
+ flags_and_attributes = (
+ FILE_ATTRIBUTE_NORMAL |
+ FILE_FLAG_BACKUP_SEMANTICS |
+ FILE_FLAG_OPEN_REPARSE_POINT
+ )
+ template_file = None
+
+ handle = CreateFile(
+ path,
+ desired_access,
+ share_mode,
+ security_attributes,
+ creation_disposition,
+ flags_and_attributes,
+ template_file,
+ )
+
+ if handle == INVALID_HANDLE_VALUE:
+ raise WindowsError()
+
+ info = BY_HANDLE_FILE_INFORMATION()
+ res = GetFileInformationByHandle(handle, info)
+ handle_nonzero_success(res)
+
+ return info
diff --git a/testing/test_functions.py b/testing/test_functions.py
index 8fbf750..c3cc86e 100644
--- a/testing/test_functions.py
+++ b/testing/test_functions.py
@@ -1,11 +1,9 @@
import pytest
-import py
import sys
import pkg_resources
from setuptools_scm import dump_version, get_version, PRETEND_KEY
from setuptools_scm.version import guess_next_version, meta, format_version
from setuptools_scm.utils import has_command
-import subprocess
PY3 = sys.version_info > (2,)
@@ -75,59 +73,3 @@ def test_has_command(recwarn):
assert not has_command('yadayada_setuptools_aint_ne')
msg = recwarn.pop()
assert 'yadayada' in str(msg.message)
-
-
-def _get_windows_short_path(path):
- """ Call a temporary batch file that expands the first argument so that
- it contains short names only.
- Return a py._path.local.LocalPath instance.
-
- For info on Windows batch parameters:
- https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/percent.mspx?mfr=true
- """
- tmpdir = py.path.local.mkdtemp()
- try:
- batch_file = tmpdir.join("shortpathname.bat")
- batch_file.write("@echo %~s1")
- out = subprocess.check_output([str(batch_file), str(path)])
- if PY3:
- out = out.decode(sys.getfilesystemencoding())
- finally:
- tmpdir.remove()
- return py.path.local(out.strip())
-
-
-@pytest.mark.skipif(sys.platform != 'win32',
- reason="this test is only valid on windows")
-def test_get_windows_long_path_name(tmpdir):
- from setuptools_scm.utils import get_windows_long_path_name
-
- # 8.3 names are limited to max 8 characters, plus optionally a period
- # and three further characters; so here we use longer names
- file_a = tmpdir.ensure("long_name_a.txt")
- file_b = tmpdir.ensure("long_name_b.txt")
- dir_c = tmpdir.ensure("long_name_c", dir=True)
- short_file_a = _get_windows_short_path(file_a)
- short_file_b = _get_windows_short_path(file_b)
- short_dir_c = _get_windows_short_path(dir_c)
-
- # shortened names contain the first six characters (case insensitive),
- # followed by a tilde character and an incremental number that
- # distinguishes files with the same first six letters and extension
- assert short_file_a.basename == "LONG_N~1.TXT"
- assert short_file_b.basename == "LONG_N~2.TXT"
- assert short_dir_c.basename == "LONG_N~1"
-
- long_name_a = get_windows_long_path_name(str(short_file_a))
- long_name_b = get_windows_long_path_name(str(short_file_b))
- long_name_c = get_windows_long_path_name(str(short_dir_c))
- assert long_name_a.endswith("long_name_a.txt")
- assert long_name_b.endswith("long_name_b.txt")
- assert long_name_c.endswith("long_name_c")
-
- # check ctypes.WinError() with no arg shows the last error message, e.g.
- # when input path doesn't exist. Note, WinError is not itself a subclass
- # of BaseException; it's a function returning an instance of OSError
- with pytest.raises(OSError) as excinfo:
- get_windows_long_path_name("unexistent_file_name")
- assert 'The system cannot find the file specified' in str(excinfo)