summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/git/index.py15
-rw-r--r--lib/git/utils.py40
-rw-r--r--test/git/test_utils.py14
3 files changed, 65 insertions, 4 deletions
diff --git a/lib/git/index.py b/lib/git/index.py
index 64a25a70..991c77da 100644
--- a/lib/git/index.py
+++ b/lib/git/index.py
@@ -57,7 +57,11 @@ class _TemporaryFileSwap(object):
def __init__(self, file_path):
self.file_path = file_path
self.tmp_file_path = self.file_path + tempfile.mktemp('','','')
- os.rename(self.file_path, self.tmp_file_path)
+ # it may be that the source does not exist
+ try:
+ os.rename(self.file_path, self.tmp_file_path)
+ except OSError:
+ pass
def __del__(self):
if os.path.isfile(self.tmp_file_path):
@@ -556,6 +560,7 @@ class IndexFile(LazyMixin, diff.Diffable):
repo.git.read_tree(*arg_list, **kwargs)
index = cls(repo, tmp_index)
index.entries # force it to read the file as we will delete the temp-file
+ del(index_handler) # release as soon as possible
finally:
if os.path.exists(tmp_index):
os.remove(tmp_index)
@@ -763,9 +768,6 @@ class IndexFile(LazyMixin, diff.Diffable):
Returns
Tree object representing this index
"""
- index_path = self._index_path()
- tmp_index_mover = _TemporaryFileSwap(index_path)
-
# IMPORTANT: If we have TREE extension data, it will actually
# ignore the index and write the stored tree instead. Hence we
# temporarily forget about it, and in fact I don't know what git
@@ -776,9 +778,14 @@ class IndexFile(LazyMixin, diff.Diffable):
self._extension_data = ''
# END extension data special handling
+ index_path = self._index_path()
+ tmp_index_mover = _TemporaryFileSwap(index_path)
+
self.write(index_path)
tree_sha = self.repo.git.write_tree(missing_ok=missing_ok)
+ del(tmp_index_mover) # as soon as possible
+
if stored_ext_data:
self._extension_data = stored_ext_data
# END reset stored exstension data
diff --git a/lib/git/utils.py b/lib/git/utils.py
index 1a624ca2..b5364c4c 100644
--- a/lib/git/utils.py
+++ b/lib/git/utils.py
@@ -6,6 +6,7 @@
import os
import sys
+import time
import tempfile
try:
@@ -172,6 +173,45 @@ class LockFile(object):
os.remove(self._lock_file_path())
self._owns_lock = False
+class BlockingLockFile(LockFile):
+ """The lock file will block until a lock could be obtained, or fail after
+ a specified timeout"""
+ __slots__ = ("_check_interval", "_max_block_time")
+ def __init__(self, file_path, check_interval_s=0.3, max_block_time_s=sys.maxint):
+ """Configure the instance
+
+ ``check_interval_s``
+ Period of time to sleep until the lock is checked the next time.
+ By default, it waits a nearly unlimited time
+
+ ``max_block_time_s``
+ Maximum amount of seconds we may lock
+ """
+ super(BlockingLockFile, self).__init__(file_path)
+ self._check_interval = check_interval_s
+ self._max_block_time = max_block_time_s
+
+ def _obtain_lock(self):
+ """This method blocks until it obtained the lock, or raises IOError if
+ it ran out of time.
+ If this method returns, you are guranteed to own the lock"""
+ starttime = time.time()
+ maxtime = starttime + float(self._max_block_time)
+ while True:
+ try:
+ self._obtain_lock_or_raise()
+ except IOError:
+ curtime = time.time()
+ if curtime >= maxtime:
+ msg = "Waited %f seconds for lock at %r" % ( maxtime - starttime, self._lock_file_path())
+ raise IOError(msg)
+ # END abort if we wait too long
+ time.sleep(self._check_interval)
+ else:
+ break
+ # END endless loop
+
+
class ConcurrentWriteOperation(LockFile):
"""
diff --git a/test/git/test_utils.py b/test/git/test_utils.py
index 69a9297d..ade2bffa 100644
--- a/test/git/test_utils.py
+++ b/test/git/test_utils.py
@@ -11,6 +11,7 @@ from test.testlib import *
from git.utils import *
from git import *
from git.cmd import dashify
+import time
class TestUtils(TestCase):
@@ -53,6 +54,19 @@ class TestUtils(TestCase):
lock_file._obtain_lock_or_raise()
lock_file._release_lock()
+ def test_blocking_lock_file(self):
+ my_file = tempfile.mktemp()
+ lock_file = BlockingLockFile(my_file)
+ lock_file._obtain_lock()
+
+ # next one waits for the lock
+ start = time.time()
+ wait_time = 0.1
+ wait_lock = BlockingLockFile(my_file, 0.05, wait_time)
+ self.failUnlessRaises(IOError, wait_lock._obtain_lock)
+ elapsed = time.time() - start
+ assert elapsed <= wait_time + 0.02 # some extra time it may cost
+
def _cmp_contents(self, file_path, data):
# raise if data from file at file_path
# does not match data string