gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / file.py @ 959
History | View | Annotate | Download (5.93 KB)
1 |
# file.py -- Safe access to git files
|
---|---|
2 |
# Copyright (C) 2010 Google, Inc.
|
3 |
#
|
4 |
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
|
5 |
# General Public License as public by the Free Software Foundation; version 2.0
|
6 |
# or (at your option) any later version. You can redistribute it and/or
|
7 |
# modify it under the terms of either of these two licenses.
|
8 |
#
|
9 |
# Unless required by applicable law or agreed to in writing, software
|
10 |
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
# See the License for the specific language governing permissions and
|
13 |
# limitations under the License.
|
14 |
#
|
15 |
# You should have received a copy of the licenses; if not, see
|
16 |
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
|
17 |
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
|
18 |
# License, Version 2.0.
|
19 |
#
|
20 |
|
21 |
"""Safe access to git files."""
|
22 |
|
23 |
import errno |
24 |
import io |
25 |
import os |
26 |
import sys |
27 |
import tempfile |
28 |
|
29 |
def ensure_dir_exists(dirname): |
30 |
"""Ensure a directory exists, creating if necessary."""
|
31 |
try:
|
32 |
os.makedirs(dirname) |
33 |
except OSError as e: |
34 |
if e.errno != errno.EEXIST:
|
35 |
raise
|
36 |
|
37 |
|
38 |
def _fancy_rename(oldname, newname): |
39 |
"""Rename file with temporary backup file to rollback if rename fails"""
|
40 |
if not os.path.exists(newname): |
41 |
try:
|
42 |
os.rename(oldname, newname) |
43 |
except OSError: |
44 |
raise
|
45 |
return
|
46 |
|
47 |
# destination file exists
|
48 |
try:
|
49 |
(fd, tmpfile) = tempfile.mkstemp(".tmp", prefix=oldname+".", dir=".") |
50 |
os.close(fd) |
51 |
os.remove(tmpfile) |
52 |
except OSError: |
53 |
# either file could not be created (e.g. permission problem)
|
54 |
# or could not be deleted (e.g. rude virus scanner)
|
55 |
raise
|
56 |
try:
|
57 |
os.rename(newname, tmpfile) |
58 |
except OSError: |
59 |
raise # no rename occurred |
60 |
try:
|
61 |
os.rename(oldname, newname) |
62 |
except OSError: |
63 |
os.rename(tmpfile, newname) |
64 |
raise
|
65 |
os.remove(tmpfile) |
66 |
|
67 |
|
68 |
def GitFile(filename, mode='rb', bufsize=-1): |
69 |
"""Create a file object that obeys the git file locking protocol.
|
70 |
|
71 |
:return: a builtin file object or a _GitFile object
|
72 |
|
73 |
:note: See _GitFile for a description of the file locking protocol.
|
74 |
|
75 |
Only read-only and write-only (binary) modes are supported; r+, w+, and a
|
76 |
are not. To read and write from the same file, you can take advantage of
|
77 |
the fact that opening a file for write does not actually open the file you
|
78 |
request.
|
79 |
"""
|
80 |
if 'a' in mode: |
81 |
raise IOError('append mode not supported for Git files') |
82 |
if '+' in mode: |
83 |
raise IOError('read/write mode not supported for Git files') |
84 |
if 'b' not in mode: |
85 |
raise IOError('text mode not supported for Git files') |
86 |
if 'w' in mode: |
87 |
return _GitFile(filename, mode, bufsize)
|
88 |
else:
|
89 |
return io.open(filename, mode, bufsize)
|
90 |
|
91 |
|
92 |
class _GitFile(object): |
93 |
"""File that follows the git locking protocol for writes.
|
94 |
|
95 |
All writes to a file foo will be written into foo.lock in the same
|
96 |
directory, and the lockfile will be renamed to overwrite the original file
|
97 |
on close.
|
98 |
|
99 |
:note: You *must* call close() or abort() on a _GitFile for the lock to be
|
100 |
released. Typically this will happen in a finally block.
|
101 |
"""
|
102 |
|
103 |
PROXY_PROPERTIES = set(['closed', 'encoding', 'errors', 'mode', 'name', |
104 |
'newlines', 'softspace']) |
105 |
PROXY_METHODS = ('__iter__', 'flush', 'fileno', 'isatty', 'read', |
106 |
'readline', 'readlines', 'seek', 'tell', |
107 |
'truncate', 'write', 'writelines') |
108 |
def __init__(self, filename, mode, bufsize): |
109 |
self._filename = filename
|
110 |
self._lockfilename = '%s.lock' % self._filename |
111 |
fd = os.open(self._lockfilename,
|
112 |
os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0)) |
113 |
self._file = os.fdopen(fd, mode, bufsize)
|
114 |
self._closed = False |
115 |
|
116 |
for method in self.PROXY_METHODS: |
117 |
setattr(self, method, getattr(self._file, method)) |
118 |
|
119 |
def abort(self): |
120 |
"""Close and discard the lockfile without overwriting the target.
|
121 |
|
122 |
If the file is already closed, this is a no-op.
|
123 |
"""
|
124 |
if self._closed: |
125 |
return
|
126 |
self._file.close()
|
127 |
try:
|
128 |
os.remove(self._lockfilename)
|
129 |
self._closed = True |
130 |
except OSError as e: |
131 |
# The file may have been removed already, which is ok.
|
132 |
if e.errno != errno.ENOENT:
|
133 |
raise
|
134 |
self._closed = True |
135 |
|
136 |
def close(self): |
137 |
"""Close this file, saving the lockfile over the original.
|
138 |
|
139 |
:note: If this method fails, it will attempt to delete the lockfile.
|
140 |
However, it is not guaranteed to do so (e.g. if a filesystem becomes
|
141 |
suddenly read-only), which will prevent future writes to this file
|
142 |
until the lockfile is removed manually.
|
143 |
:raises OSError: if the original file could not be overwritten. The lock
|
144 |
file is still closed, so further attempts to write to the same file
|
145 |
object will raise ValueError.
|
146 |
"""
|
147 |
if self._closed: |
148 |
return
|
149 |
self._file.close()
|
150 |
try:
|
151 |
try:
|
152 |
os.rename(self._lockfilename, self._filename) |
153 |
except OSError as e: |
154 |
if sys.platform == 'win32' and e.errno == errno.EEXIST: |
155 |
# Windows versions prior to Vista don't support atomic renames
|
156 |
_fancy_rename(self._lockfilename, self._filename) |
157 |
else:
|
158 |
raise
|
159 |
finally:
|
160 |
self.abort()
|
161 |
|
162 |
def __enter__(self): |
163 |
return self |
164 |
|
165 |
def __exit__(self, exc_type, exc_val, exc_tb): |
166 |
self.close()
|
167 |
|
168 |
def __getattr__(self, name): |
169 |
"""Proxy property calls to the underlying file."""
|
170 |
if name in self.PROXY_PROPERTIES: |
171 |
return getattr(self._file, name) |
172 |
raise AttributeError(name) |