Coverage for pyquickhelper/loghelper/repositories/pysvn_helper.py: 100%

7 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-03 02:21 +0200

1# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Gather all pysvn functionalities here. There might be some differences 

5between SVN version, pysvn client version, TortoiseSVN version and Python. 

6If such a case happens, most of the function will call ``svn`` using the command line. 

7""" 

8 

9import os 

10import sys 

11import datetime 

12import xml.etree.ElementTree as ET 

13 

14from ..flog import fLOG, run_cmd 

15from ..convert_helper import str2datetime 

16 

17 

18def IsRepo(location, commandline=True): # pragma: no cover 

19 """ 

20 says if it a repository SVN 

21 

22 @param location (str) location 

23 @param commandline (bool) use commandline or not 

24 @return bool 

25 """ 

26 if location is None: 

27 location = os.path.normpath(os.path.abspath( 

28 os.path.join(os.path.split(__file__)[0], "..", "..", "..", ".."))) 

29 try: 

30 r = get_repo_version(location, commandline, log=False) 

31 return True and r is not None 

32 except Exception: 

33 return False 

34 

35 

36class RepoFile: # pragma: no cover 

37 

38 """ 

39 mimic a svn file 

40 """ 

41 

42 def __init__(self, **args): 

43 """ 

44 constructor 

45 @param args list of members to add 

46 """ 

47 for k, v in args.items(): 

48 self.__dict__[k] = v 

49 

50 if hasattr(self, "name") and '"' in self.name: # pylint: disable=E0203 

51 # defa = sys.stdout.encoding if sys.stdout != None else "utf8" 

52 self.name = self.name.replace('"', "") 

53 # self.name = self.name.encode(defa).decode("utf-8") 

54 

55 def __str__(self): 

56 """ 

57 usual 

58 """ 

59 return self.name 

60 

61 

62def repo_ls(full, commandline=True): # pragma: no cover 

63 """ 

64 run ``ls`` on a path 

65 @param full full path 

66 @param commandline use command line instead of pysvn 

67 @return output of client.ls 

68 

69 When a path includes a symbol ``@``, another one must added to the path 

70 to avoid the following error to happen: 

71 

72 :: 

73 

74 svn: E205000: Syntax error parsing peg revision 'something@somewhere.fr-' 

75 """ 

76 if not commandline: 

77 try: 

78 import pysvn 

79 client = pysvn.Client() 

80 entry = client.ls(full) 

81 return entry 

82 except Exception as e: 

83 typstr = str 

84 if "This client is too old to work with the working copy at" in typstr(e) or \ 

85 "No module named 'pysvn'" in typstr(e): 

86 if "@" in full: 

87 clean = full 

88 full += "@" 

89 else: 

90 clean = None 

91 cmd = "svn ls -r HEAD \"%s\"" % full.replace("\\", "/") 

92 out, err = run_cmd(cmd, 

93 wait=True, 

94 encerror="strict", 

95 encoding=sys.stdout.encoding if sys.stdout is not None else "utf8") 

96 if len(err) > 0: 

97 fLOG("problem with file ", full, err) 

98 raise RuntimeError(err) 

99 

100 def cleanf(s): 

101 if clean is None: 

102 return s 

103 else: 

104 return s.replace(clean + "@", clean) 

105 res = [RepoFile(name=os.path.join(cleanf(full), _.strip())) 

106 for _ in out.split("\n") if len(_) > 0] 

107 return res 

108 else: 

109 raise RuntimeError("problem with file " + full) from e 

110 else: 

111 if "@" in full: 

112 clean = full 

113 full += "@" 

114 else: 

115 clean = None 

116 cmd = "svn ls -r HEAD \"%s\"" % full.replace("\\", "/") 

117 try: 

118 out, err = run_cmd(cmd, 

119 wait=True, 

120 encerror="strict", 

121 encoding=sys.stdout.encoding if sys.stdout is not None else "utf8") 

122 except Exception as e: 

123 raise RuntimeError("issue with file or folder " + full) from e 

124 

125 if len(err) > 0: 

126 fLOG("problem with file ", full, err) 

127 raise RuntimeError(err) 

128 

129 def cleanf(s): 

130 if clean is None: 

131 return s 

132 else: 

133 return s.replace(clean + "@", clean) 

134 res = [RepoFile(name=os.path.join(cleanf(full), _.strip())) 

135 for _ in out.split("\n") if len(_) > 0] 

136 return res 

137 

138 

139def __get_version_from_version_txt(path): # pragma: no cover 

140 """ 

141 private function, tries to find a file ``version.txt`` which should 

142 contains the version number (if svn is not present) 

143 @param path folder to look, it will look to the the path of this file, 

144 some parents directories and finally this path 

145 @return the version number 

146 

147 @warning If ``version.txt`` was not found, it throws an exception. 

148 """ 

149 file = os.path.split(__file__)[0] 

150 paths = [file, 

151 os.path.join(file, ".."), 

152 os.path.join(file, "..", ".."), 

153 os.path.join(file, "..", "..", ".."), 

154 path] 

155 for p in paths: 

156 fp = os.path.join(p, "version.txt") 

157 if os.path.exists(fp): 

158 with open(fp, "r") as f: 

159 return int(f.read().strip(" \n\r\t")) 

160 raise FileNotFoundError( 

161 "unable to find version.txt in\n" + "\n".join(paths)) 

162 

163 

164def get_repo_log(path=None, file_detail=False, commandline=True): # pragma: no cover 

165 """ 

166 get the latest changes operated on a file in a folder or a subfolder 

167 @param path path to look 

168 @param file_detail if True, add impacted files 

169 @param commandline if True, use the command line to get the version number, otherwise it uses pysvn 

170 @return list of changes, each change is a list of 4-uple: 

171 - author 

172 - change number (int) 

173 - date (datetime) 

174 - comment 

175 

176 The function use a command line if an error occurred. It uses the xml format: 

177 

178 :: 

179 

180 <logentry revision="161"> 

181 <author>xavier dupre</author> 

182 <date>2013-03-23T15:02:50.311828Z</date> 

183 <msg>pyquickhelper: first version</msg> 

184 </logentry> 

185 

186 When a path includes a symbol ``@``, another one must added to the path 

187 to avoid the following error to happen: 

188 

189 :: 

190 

191 svn: E205000: Syntax error parsing peg revision 'something@somewhere.fr-' 

192 """ 

193 if path is None: 

194 path = os.path.normpath( 

195 os.path.abspath(os.path.join(os.path.split(__file__)[0], "..", "..", ".."))) 

196 

197 if not commandline: 

198 try: 

199 import pysvn 

200 svnClient = pysvn.Client() 

201 if "@" in path: 

202 path += "@" 

203 version = get_repo_version(path) 

204 log = svnClient.log( 

205 path, 

206 revision_start=pysvn.Revision( 

207 pysvn.opt_revision_kind.number, 0), 

208 revision_end=pysvn.Revision( 

209 pysvn.opt_revision_kind.number, version), 

210 discover_changed_paths=True, 

211 strict_node_history=True, 

212 limit=0, 

213 include_merged_revisions=False, 

214 ) 

215 except Exception as e: 

216 typstr = str 

217 if "is not a working copy" in typstr(e): 

218 return [ 

219 ("", 

220 __get_version_from_version_txt(path), 

221 datetime.datetime.now(), 

222 "no repository")] 

223 elif "This client is too old to work with the working copy at" in typstr(e) or \ 

224 "No module named 'pysvn'" in typstr(e): 

225 return get_repo_log(path, file_detail, commandline=True) 

226 else: 

227 raise e 

228 

229 else: 

230 if "@" in path: 

231 path += "@" 

232 cmd = "svn log -r HEAD:1 --xml \"%s\"" % path.replace("\\", "/") 

233 out, err = run_cmd(cmd, 

234 wait=True, 

235 encerror="strict", 

236 encoding=sys.stdout.encoding if sys.stdout is not None else "utf8") 

237 if len(err) > 0: 

238 fLOG("problem with file ", path, err) 

239 raise RuntimeError(err) 

240 

241 root = ET.fromstring(out) 

242 res = [] 

243 for i in root.iter('logentry'): 

244 revision = int(i.attrib['revision'].strip()) 

245 author = i.find("author").text.strip() 

246 t = i.find("msg").text 

247 msg = t.strip() if t is not None else "-" 

248 sdate = i.find("date").text.strip() 

249 dt = str2datetime(sdate.replace("T", " ").strip("Z ")) 

250 row = [author, revision, dt, msg] 

251 res.append(row) 

252 return res 

253 

254 message = [] 

255 for info in log: 

256 message.append(("", 

257 info.revision.number, 

258 datetime.datetime.utcfromtimestamp(info.date), 

259 info.message)) 

260 if file_detail: 

261 for i, pt in enumerate(info.changed_paths): 

262 message.append(("file", 

263 info.revision.numbe, 

264 pt.data["action"], 

265 pt.data["path"])) 

266 if i > 100: 

267 message.append(" ...") 

268 break 

269 

270 return message 

271 

272 

273def get_repo_version(path=None, commandline=True, log=False): # pragma: no cover 

274 """ 

275 get the latest check in number for a specific path 

276 @param path path to look 

277 @param commandline if True, use the command line to get the version number, otherwise it uses pysvn 

278 @param log if True, returns the output instead of a boolean 

279 @return integer (check in number) 

280 

281 When a path includes a symbol ``@``, another one must added to the path 

282 to avoid the following error to happen: 

283 

284 :: 

285 

286 svn: E205000: Syntax error parsing peg revision 'something@somewhere.fr-' 

287 """ 

288 if path is None: 

289 path = os.path.normpath( 

290 os.path.abspath(os.path.join(os.path.split(__file__)[0], "..", "..", ".."))) 

291 

292 if not commandline: 

293 try: 

294 import pysvn 

295 svnClient = pysvn.Client() 

296 path = "." if path is None else path.replace("\\", "/") 

297 if "@" in path: 

298 path += "@" 

299 info = svnClient.info2() 

300 infos = [_[1] for _ in info] 

301 revv = [_["rev"].number for _ in infos] 

302 revision = max(revv) 

303 return revision 

304 except Exception as e: 

305 typstr = str 

306 if "This client is too old to work with the working copy at" in typstr(e) or \ 

307 "No module named 'pysvn'" in typstr(e): 

308 return get_repo_version(path, commandline=True) 

309 elif "is not a working copy" in typstr(e): 

310 return __get_version_from_version_txt(path) 

311 else: 

312 raise e 

313 else: 

314 cmd = "svn info -r HEAD" 

315 if "@" in path: 

316 path += "@" 

317 if path is not None: 

318 cmd += " \"%s\"" % path.replace("\\", "/") 

319 out, err = run_cmd(cmd, 

320 wait=True, 

321 encerror="ignore", 

322 encoding=sys.stdout.encoding if sys.stdout is not None else "utf8", 

323 log_error=False) 

324 if len(err) > 0: 

325 if log: 

326 fLOG("problem with file ", path, err) 

327 if log: 

328 return f"OUT\n{out}\n[svnerror]{err}\nCMD:\n{cmd}" 

329 else: 

330 raise RuntimeError(err) 

331 lines = out.split("\n") 

332 lines = [_ for _ in lines if "Revision" in _] 

333 lines = lines[0].split(":") 

334 res = lines[1] 

335 

336 if len(res) == 0: 

337 o, e = run_cmd("svn help", wait=True, log_error=False) 

338 if len(o) < 3: 

339 raise RuntimeError( 

340 "the command 'svn help' should return something") 

341 

342 return int(res) 

343 

344 

345def get_master_location(path=None, commandline=True): # pragma: no cover 

346 """ 

347 raises an exception 

348 """ 

349 raise NotImplementedError() 

350 

351 

352def get_nb_commits(path=None, commandline=True): # pragma: no cover 

353 """ 

354 returns the number of commit 

355 

356 @param path path to look 

357 @param commandline if True, use the command line to get the version number, otherwise it uses pysvn 

358 @return integer 

359 """ 

360 raise NotImplementedError()