Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/gitdb/db/loose.py: 18%

120 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors 

2# 

3# This module is part of GitDB and is released under 

4# the New BSD License: http://www.opensource.org/licenses/bsd-license.php 

5from gitdb.db.base import ( 

6 FileDBBase, 

7 ObjectDBR, 

8 ObjectDBW 

9) 

10 

11 

12from gitdb.exc import ( 

13 BadObject, 

14 AmbiguousObjectName 

15) 

16 

17from gitdb.stream import ( 

18 DecompressMemMapReader, 

19 FDCompressedSha1Writer, 

20 FDStream, 

21 Sha1Writer 

22) 

23 

24from gitdb.base import ( 

25 OStream, 

26 OInfo 

27) 

28 

29from gitdb.util import ( 

30 file_contents_ro_filepath, 

31 ENOENT, 

32 hex_to_bin, 

33 bin_to_hex, 

34 exists, 

35 chmod, 

36 isdir, 

37 isfile, 

38 remove, 

39 mkdir, 

40 rename, 

41 dirname, 

42 basename, 

43 join 

44) 

45 

46from gitdb.fun import ( 

47 chunk_size, 

48 loose_object_header_info, 

49 write_object, 

50 stream_copy 

51) 

52 

53from gitdb.utils.encoding import force_bytes 

54 

55import tempfile 

56import os 

57import sys 

58 

59 

60__all__ = ('LooseObjectDB', ) 

61 

62 

63class LooseObjectDB(FileDBBase, ObjectDBR, ObjectDBW): 

64 

65 """A database which operates on loose object files""" 

66 

67 # CONFIGURATION 

68 # chunks in which data will be copied between streams 

69 stream_chunk_size = chunk_size 

70 

71 # On windows we need to keep it writable, otherwise it cannot be removed 

72 # either 

73 new_objects_mode = int("444", 8) 

74 if os.name == 'nt': 74 ↛ 75line 74 didn't jump to line 75, because the condition on line 74 was never true

75 new_objects_mode = int("644", 8) 

76 

77 def __init__(self, root_path): 

78 super().__init__(root_path) 

79 self._hexsha_to_file = dict() 

80 # Additional Flags - might be set to 0 after the first failure 

81 # Depending on the root, this might work for some mounts, for others not, which 

82 # is why it is per instance 

83 self._fd_open_flags = getattr(os, 'O_NOATIME', 0) 

84 

85 #{ Interface 

86 def object_path(self, hexsha): 

87 """ 

88 :return: path at which the object with the given hexsha would be stored, 

89 relative to the database root""" 

90 return join(hexsha[:2], hexsha[2:]) 

91 

92 def readable_db_object_path(self, hexsha): 

93 """ 

94 :return: readable object path to the object identified by hexsha 

95 :raise BadObject: If the object file does not exist""" 

96 try: 

97 return self._hexsha_to_file[hexsha] 

98 except KeyError: 

99 pass 

100 # END ignore cache misses 

101 

102 # try filesystem 

103 path = self.db_path(self.object_path(hexsha)) 

104 if exists(path): 

105 self._hexsha_to_file[hexsha] = path 

106 return path 

107 # END handle cache 

108 raise BadObject(hexsha) 

109 

110 def partial_to_complete_sha_hex(self, partial_hexsha): 

111 """:return: 20 byte binary sha1 string which matches the given name uniquely 

112 :param name: hexadecimal partial name (bytes or ascii string) 

113 :raise AmbiguousObjectName: 

114 :raise BadObject: """ 

115 candidate = None 

116 for binsha in self.sha_iter(): 

117 if bin_to_hex(binsha).startswith(force_bytes(partial_hexsha)): 

118 # it can't ever find the same object twice 

119 if candidate is not None: 

120 raise AmbiguousObjectName(partial_hexsha) 

121 candidate = binsha 

122 # END for each object 

123 if candidate is None: 

124 raise BadObject(partial_hexsha) 

125 return candidate 

126 

127 #} END interface 

128 

129 def _map_loose_object(self, sha): 

130 """ 

131 :return: memory map of that file to allow random read access 

132 :raise BadObject: if object could not be located""" 

133 db_path = self.db_path(self.object_path(bin_to_hex(sha))) 

134 try: 

135 return file_contents_ro_filepath(db_path, flags=self._fd_open_flags) 

136 except OSError as e: 

137 if e.errno != ENOENT: 

138 # try again without noatime 

139 try: 

140 return file_contents_ro_filepath(db_path) 

141 except OSError as new_e: 

142 raise BadObject(sha) from new_e 

143 # didn't work because of our flag, don't try it again 

144 self._fd_open_flags = 0 

145 else: 

146 raise BadObject(sha) from e 

147 # END handle error 

148 # END exception handling 

149 

150 def set_ostream(self, stream): 

151 """:raise TypeError: if the stream does not support the Sha1Writer interface""" 

152 if stream is not None and not isinstance(stream, Sha1Writer): 

153 raise TypeError("Output stream musst support the %s interface" % Sha1Writer.__name__) 

154 return super().set_ostream(stream) 

155 

156 def info(self, sha): 

157 m = self._map_loose_object(sha) 

158 try: 

159 typ, size = loose_object_header_info(m) 

160 return OInfo(sha, typ, size) 

161 finally: 

162 if hasattr(m, 'close'): 

163 m.close() 

164 # END assure release of system resources 

165 

166 def stream(self, sha): 

167 m = self._map_loose_object(sha) 

168 type, size, stream = DecompressMemMapReader.new(m, close_on_deletion=True) 

169 return OStream(sha, type, size, stream) 

170 

171 def has_object(self, sha): 

172 try: 

173 self.readable_db_object_path(bin_to_hex(sha)) 

174 return True 

175 except BadObject: 

176 return False 

177 # END check existence 

178 

179 def store(self, istream): 

180 """note: The sha we produce will be hex by nature""" 

181 tmp_path = None 

182 writer = self.ostream() 

183 if writer is None: 

184 # open a tmp file to write the data to 

185 fd, tmp_path = tempfile.mkstemp(prefix='obj', dir=self._root_path) 

186 

187 if istream.binsha is None: 

188 writer = FDCompressedSha1Writer(fd) 

189 else: 

190 writer = FDStream(fd) 

191 # END handle direct stream copies 

192 # END handle custom writer 

193 

194 try: 

195 try: 

196 if istream.binsha is not None: 

197 # copy as much as possible, the actual uncompressed item size might 

198 # be smaller than the compressed version 

199 stream_copy(istream.read, writer.write, sys.maxsize, self.stream_chunk_size) 

200 else: 

201 # write object with header, we have to make a new one 

202 write_object(istream.type, istream.size, istream.read, writer.write, 

203 chunk_size=self.stream_chunk_size) 

204 # END handle direct stream copies 

205 finally: 

206 if tmp_path: 

207 writer.close() 

208 # END assure target stream is closed 

209 except: 

210 if tmp_path: 

211 os.remove(tmp_path) 

212 raise 

213 # END assure tmpfile removal on error 

214 

215 hexsha = None 

216 if istream.binsha: 

217 hexsha = istream.hexsha 

218 else: 

219 hexsha = writer.sha(as_hex=True) 

220 # END handle sha 

221 

222 if tmp_path: 

223 obj_path = self.db_path(self.object_path(hexsha)) 

224 obj_dir = dirname(obj_path) 

225 if not isdir(obj_dir): 

226 mkdir(obj_dir) 

227 # END handle destination directory 

228 # rename onto existing doesn't work on NTFS 

229 if isfile(obj_path): 

230 remove(tmp_path) 

231 else: 

232 rename(tmp_path, obj_path) 

233 # end rename only if needed 

234 

235 # make sure its readable for all ! It started out as rw-- tmp file 

236 # but needs to be rwrr 

237 chmod(obj_path, self.new_objects_mode) 

238 # END handle dry_run 

239 

240 istream.binsha = hex_to_bin(hexsha) 

241 return istream 

242 

243 def sha_iter(self): 

244 # find all files which look like an object, extract sha from there 

245 for root, dirs, files in os.walk(self.root_path()): 

246 root_base = basename(root) 

247 if len(root_base) != 2: 

248 continue 

249 

250 for f in files: 

251 if len(f) != 38: 

252 continue 

253 yield hex_to_bin(root_base + f) 

254 # END for each file 

255 # END for each walk iteration 

256 

257 def size(self): 

258 return len(tuple(self.sha_iter()))