1 # file.py -- Safe access to git files
2 # Copyright (C) 2010 Google, Inc.
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # of the License or (at your option) a later version of the License.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 """Safe access to git files."""
25 def ensure_dir_exists(dirname):
26 """Ensure a directory exists, creating if necessary."""
30 if e.errno != errno.EEXIST:
33 def fancy_rename(oldname, newname):
34 """Rename file with temporary backup file to rollback if rename fails"""
35 if not os.path.exists(newname):
37 os.rename(oldname, newname)
42 # destination file exists
44 (fd, tmpfile) = tempfile.mkstemp(".tmp", prefix=oldname+".", dir=".")
48 # either file could not be created (e.g. permission problem)
49 # or could not be deleted (e.g. rude virus scanner)
52 os.rename(newname, tmpfile)
54 raise # no rename occurred
56 os.rename(oldname, newname)
58 os.rename(tmpfile, newname)
63 def GitFile(filename, mode='rb', bufsize=-1):
64 """Create a file object that obeys the git file locking protocol.
66 :return: a builtin file object or a _GitFile object
68 :note: See _GitFile for a description of the file locking protocol.
70 Only read-only and write-only (binary) modes are supported; r+, w+, and a
71 are not. To read and write from the same file, you can take advantage of
72 the fact that opening a file for write does not actually open the file you
75 >>> write_file = GitFile('filename', 'wb')
76 >>> read_file = GitFile('filename', 'rb')
77 >>> read_file.readlines()
78 ['contents\n', 'of\n', 'the\n', 'file\n']
79 >>> write_file.write('foo')
81 >>> write_file.close()
82 >>> new_file = GitFile('filename', 'rb')
85 >>> other_file = GitFile('filename', 'wb')
86 Traceback (most recent call last):
88 OSError: [Errno 17] File exists: 'filename.lock'
91 raise IOError('append mode not supported for Git files')
93 raise IOError('read/write mode not supported for Git files')
95 raise IOError('text mode not supported for Git files')
97 return _GitFile(filename, mode, bufsize)
99 return file(filename, mode, bufsize)
102 class _GitFile(object):
103 """File that follows the git locking protocol for writes.
105 All writes to a file foo will be written into foo.lock in the same
106 directory, and the lockfile will be renamed to overwrite the original file
109 :note: You *must* call close() or abort() on a _GitFile for the lock to be
110 released. Typically this will happen in a finally block.
113 PROXY_PROPERTIES = set(['closed', 'encoding', 'errors', 'mode', 'name',
114 'newlines', 'softspace'])
115 PROXY_METHODS = ('__iter__', 'flush', 'fileno', 'isatty', 'next', 'read',
116 'readline', 'readlines', 'xreadlines', 'seek', 'tell',
117 'truncate', 'write', 'writelines')
118 def __init__(self, filename, mode, bufsize):
119 self._filename = filename
120 self._lockfilename = '%s.lock' % self._filename
121 fd = os.open(self._lockfilename,
122 os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0))
123 self._file = os.fdopen(fd, mode, bufsize)
126 for method in self.PROXY_METHODS:
127 setattr(self, method, getattr(self._file, method))
130 """Close and discard the lockfile without overwriting the target.
132 If the file is already closed, this is a no-op.
138 os.remove(self._lockfilename)
141 # The file may have been removed already, which is ok.
142 if e.errno != errno.ENOENT:
147 """Close this file, saving the lockfile over the original.
149 :note: If this method fails, it will attempt to delete the lockfile.
150 However, it is not guaranteed to do so (e.g. if a filesystem becomes
151 suddenly read-only), which will prevent future writes to this file
152 until the lockfile is removed manually.
153 :raises OSError: if the original file could not be overwritten. The lock
154 file is still closed, so further attempts to write to the same file
155 object will raise ValueError.
162 os.rename(self._lockfilename, self._filename)
164 # Windows versions prior to Vista don't support atomic renames
165 if e.errno != errno.EEXIST:
167 fancy_rename(self._lockfilename, self._filename)
171 def __getattr__(self, name):
172 """Proxy property calls to the underlying file."""
173 if name in self.PROXY_PROPERTIES:
174 return getattr(self._file, name)
175 raise AttributeError(name)