summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2016-10-09 12:04:07 +0200
committerGitHub <noreply@github.com>2016-10-09 12:04:07 +0200
commitac9ac551983cc086d9d631f8648d7d591daa2b41 (patch)
tree2c43def3eb8304e13e6cbb4959c9776863851003
parent4e5ef73d3d6d9b973a756fddd329cfa2a24884e2 (diff)
parent20b4f49b4e71c90581c68ea7f4cf56160a6bdc79 (diff)
downloadgitpython-ac9ac551983cc086d9d631f8648d7d591daa2b41.tar.gz
Merge pull request #513 from expobrain/master
Use flock() for file locking
-rw-r--r--.gitignore1
-rw-r--r--AUTHORS1
-rw-r--r--git/test/test_util.py2
-rw-r--r--git/util.py77
-rwxr-xr-xsetup.py7
-rw-r--r--win32-requirements.txt2
6 files changed, 81 insertions, 9 deletions
diff --git a/.gitignore b/.gitignore
index d35cddeb..a672c3f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ nbproject
.DS_Store
/*egg-info
/.tox
+.cache
diff --git a/AUTHORS b/AUTHORS
index e2c3293c..b06c5b46 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -15,5 +15,6 @@ Contributors are:
-Jonathan Chu <jonathan.chu _at_ me.com>
-Vincent Driessen <me _at_ nvie.com>
-Phil Elson <pelson _dot_ pub _at_ gmail.com>
+-Daniele Esposti <daniele.esposti _at_ gmail.com>
Portions derived from other open source works and are clearly marked.
diff --git a/git/test/test_util.py b/git/test/test_util.py
index e07417b4..6ba3d0d4 100644
--- a/git/test/test_util.py
+++ b/git/test/test_util.py
@@ -5,6 +5,7 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import tempfile
+import gc
from git.test.lib import (
TestBase,
@@ -80,6 +81,7 @@ class TestUtils(TestBase):
# auto-release on destruction
del(other_lock_file)
+ gc.collect()
lock_file._obtain_lock_or_raise()
lock_file._release_lock()
diff --git a/git/util.py b/git/util.py
index c96a6b08..037fd91b 100644
--- a/git/util.py
+++ b/git/util.py
@@ -48,6 +48,56 @@ __all__ = ("stream_copy", "join_path", "to_native_path_windows", "to_native_path
#{ Utility Methods
+if platform.system() == 'Windows':
+ # This code is a derivative work of Portalocker http://code.activestate.com/recipes/65203/
+ import win32con
+ import win32file
+ import pywintypes
+
+ LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
+ LOCK_SH = 0 # the default
+ LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
+ LOCK_UN = 1 << 2
+
+ __overlapped = pywintypes.OVERLAPPED()
+
+ def flock(fd, flags=0):
+ hfile = win32file._get_osfhandle(fd)
+
+ if flags & LOCK_UN != 0:
+ # Unlock file descriptor
+ try:
+ win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
+ except pywintypes.error as exc_value:
+ # error: (158, 'UnlockFileEx', 'The segment is already unlocked.')
+ # To match the 'posix' implementation, silently ignore this error
+ if exc_value[0] == 158:
+ pass
+ else:
+ # Q: Are there exceptions/codes we should be dealing with here?
+ raise
+
+ elif flags & LOCK_EX != 0:
+ # Lock file
+ try:
+ win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
+ except pywintypes.error as exc_value:
+ if exc_value[0] == 33:
+ # error: (33, 'LockFileEx',
+ # 'The process cannot access the file because another process has locked
+ # a portion of the file.')
+ raise IOError(33, exc_value[2])
+ else:
+ # Q: Are there exceptions/codes we should be dealing with here?
+ raise
+
+ else:
+ raise NotImplementedError("Unsupported set of bitflags {}".format(bin(flags)))
+
+
+else:
+ from fcntl import flock, LOCK_UN, LOCK_EX, LOCK_NB
+
def unbare_repo(func):
"""Methods with this decorator raise InvalidGitRepositoryError if they
@@ -555,9 +605,10 @@ class LockFile(object):
As we are a utility class to be derived from, we only use protected methods.
Locks will automatically be released on destruction"""
- __slots__ = ("_file_path", "_owns_lock")
+ __slots__ = ("_file_path", "_owns_lock", "_file_descriptor")
def __init__(self, file_path):
+ self._file_descriptor = None
self._file_path = file_path
self._owns_lock = False
@@ -579,20 +630,21 @@ class LockFile(object):
:raise IOError: if a lock was already present or a lock file could not be written"""
if self._has_lock():
return
+
lock_file = self._lock_file_path()
- if os.path.isfile(lock_file):
- raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" %
- (self._file_path, lock_file))
+ # Create file and lock
try:
- flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
+ flags = os.O_CREAT
if is_win:
flags |= os.O_SHORT_LIVED
fd = os.open(lock_file, flags, 0)
- os.close(fd)
except OSError as e:
raise IOError(str(e))
+ flock(fd, LOCK_EX | LOCK_NB)
+
+ self._file_descriptor = fd
self._owns_lock = True
def _obtain_lock(self):
@@ -605,14 +657,21 @@ class LockFile(object):
if not self._has_lock():
return
+ fd = self._file_descriptor
+ lock_file = self._lock_file_path()
+
+ flock(fd, LOCK_UN)
+ os.close(fd)
+
# if someone removed our file beforhand, lets just flag this issue
# instead of failing, to make it more usable.
- lfp = self._lock_file_path()
try:
- rmfile(lfp)
+ rmfile(lock_file)
except OSError:
pass
+
self._owns_lock = False
+ self._file_descriptor = None
class BlockingLockFile(LockFile):
@@ -647,7 +706,7 @@ class BlockingLockFile(LockFile):
try:
super(BlockingLockFile, self)._obtain_lock()
except IOError:
- # synity check: if the directory leading to the lockfile is not
+ # sanity check: if the directory leading to the lockfile is not
# readable anymore, raise an execption
curtime = time.time()
if not os.path.isdir(os.path.dirname(self._lock_file_path())):
diff --git a/setup.py b/setup.py
index c7dd25fc..43b0505b 100755
--- a/setup.py
+++ b/setup.py
@@ -13,6 +13,7 @@ import pkg_resources
import logging
import os
import sys
+import platform
from os import path
with open(path.join(path.dirname(__file__), 'VERSION')) as v:
@@ -21,6 +22,10 @@ with open(path.join(path.dirname(__file__), 'VERSION')) as v:
with open('requirements.txt') as reqs_file:
requirements = reqs_file.read().splitlines()
+if platform.system() == 'Windows':
+ with open('win32-requirements.txt') as reqs_file:
+ requirements += reqs_file.read().splitlines()
+
class build_py(_build_py):
@@ -65,6 +70,8 @@ def _stamp_version(filename):
print("WARNING: Couldn't find version line in file %s" % filename, file=sys.stderr)
install_requires = ['gitdb >= 0.6.4']
+if platform.system() == 'Windows':
+ install_requires.append("pypiwin32 >= 219")
extras_require = {
':python_version == "2.6"': ['ordereddict'],
}
diff --git a/win32-requirements.txt b/win32-requirements.txt
new file mode 100644
index 00000000..0008dcff
--- /dev/null
+++ b/win32-requirements.txt
@@ -0,0 +1,2 @@
+-r requirements.txt
+pypiwin32 >= 219