summaryrefslogtreecommitdiff
path: root/lib/git/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/git/utils.py')
-rw-r--r--lib/git/utils.py159
1 files changed, 159 insertions, 0 deletions
diff --git a/lib/git/utils.py b/lib/git/utils.py
index cdc7c55b..dd1117b6 100644
--- a/lib/git/utils.py
+++ b/lib/git/utils.py
@@ -5,6 +5,8 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import os
+import sys
+import tempfile
try:
import hashlib
@@ -58,6 +60,163 @@ class SHA1Writer(object):
return self.f.tell()
+class LockFile(object):
+ """
+ Provides methods to obtain, check for, and release a file based lock which
+ should be used to handle concurrent access to the same file.
+
+ 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")
+
+ def __init__(self, file_path):
+ self._file_path = file_path
+ self._owns_lock = False
+
+ def __del__(self):
+ self._release_lock()
+
+ def _lock_file_path(self):
+ """
+ Return
+ Path to lockfile
+ """
+ return "%s.lock" % (self._file_path)
+
+ def _has_lock(self):
+ """
+ Return
+ True if we have a lock and if the lockfile still exists
+
+ Raise
+ AssertionError if our lock-file does not exist
+ """
+ if not self._owns_lock:
+ return False
+
+ lock_file = self._lock_file_path()
+ try:
+ fp = open(lock_file, "r")
+ pid = int(fp.read())
+ fp.close()
+ except IOError:
+ raise AssertionError("GitConfigParser has a lock but the lock file at %s could not be read" % lock_file)
+
+ if pid != os.getpid():
+ raise AssertionError("We claim to own the lock at %s, but it was not owned by our process: %i" % (lock_file, os.getpid()))
+
+ return True
+
+ def _obtain_lock_or_raise(self):
+ """
+ Create a lock file as flag for other instances, mark our instance as lock-holder
+
+ 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.exists(lock_file):
+ raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" % (self._file_path, lock_file))
+
+ fp = open(lock_file, "w")
+ fp.write(str(os.getpid()))
+ fp.close()
+
+ self._owns_lock = True
+
+ def _release_lock(self):
+ """
+ Release our lock if we have one
+ """
+ if not self._has_lock():
+ return
+
+ os.remove(self._lock_file_path())
+ self._owns_lock = False
+
+
+class ConcurrentWriteOperation(LockFile):
+ """
+ This class facilitates a safe write operation to a file on disk such that we:
+
+ - lock the original file
+ - write to a temporary file
+ - rename temporary file back to the original one on close
+ - unlock the original file
+
+ This type handles error correctly in that it will assure a consistent state
+ on destruction
+ """
+ __slots__ = "_temp_write_fp"
+
+ def __init__(self, file_path):
+ """
+ Initialize an instance able to write the given file_path
+ """
+ super(ConcurrentWriteOperation, self).__init__(file_path)
+ self._temp_write_fp = None
+
+ def __del__(self):
+ self._end_writing(successful=False)
+
+ def _begin_writing(self):
+ """
+ Begin writing our file, hence we get a lock and start writing
+ a temporary file in the same directory.
+
+ Returns
+ File Object to write to. It is still maintained by this instance
+ and you do not need to manually close
+ """
+ # already writing ?
+ if self._temp_write_fp is not None:
+ return self._temp_write_fp
+
+ self._obtain_lock_or_raise()
+ dirname, basename = os.path.split(self._file_path)
+ self._temp_write_fp = open(tempfile.mktemp(basename, '', dirname), "w")
+ return self._temp_write_fp
+
+ def _is_writing(self):
+ """
+ Returns
+ True if we are currently writing a file
+ """
+ return self._temp_write_fp is not None
+
+ def _end_writing(self, successful=True):
+ """
+ Indicate you successfully finished writing the file to:
+
+ - close the underlying stream
+ - rename the remporary file to the original one
+ - release our lock
+ """
+ # did we start a write operation ?
+ if self._temp_write_fp is None:
+ return
+
+ self._temp_write_fp.close()
+ if successful:
+ # on windows, rename does not silently overwrite the existing one
+ if sys.platform == "win32":
+ os.remove(self._file_path)
+ os.rename(self._temp_write_fp.name, self._file_path)
+ else:
+ # just delete the file so far, we failed
+ os.remove(self._temp_write_fp.name)
+ # END successful handling
+
+ # finally reset our handle
+ self._release_lock()
+ self._temp_write_fp = None
+
+
class LazyMixin(object):
"""
Base class providing an interface to lazily retrieve attribute values upon