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
« 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"""
9import os
10import sys
11import datetime
12import xml.etree.ElementTree as ET
14from ..flog import fLOG, run_cmd
15from ..convert_helper import str2datetime
18def IsRepo(location, commandline=True): # pragma: no cover
19 """
20 says if it a repository SVN
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
36class RepoFile: # pragma: no cover
38 """
39 mimic a svn file
40 """
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
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")
55 def __str__(self):
56 """
57 usual
58 """
59 return self.name
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
69 When a path includes a symbol ``@``, another one must added to the path
70 to avoid the following error to happen:
72 ::
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)
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
125 if len(err) > 0:
126 fLOG("problem with file ", full, err)
127 raise RuntimeError(err)
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
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
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))
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
176 The function use a command line if an error occurred. It uses the xml format:
178 ::
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>
186 When a path includes a symbol ``@``, another one must added to the path
187 to avoid the following error to happen:
189 ::
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], "..", "..", "..")))
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
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)
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
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
270 return message
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)
281 When a path includes a symbol ``@``, another one must added to the path
282 to avoid the following error to happen:
284 ::
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], "..", "..", "..")))
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]
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")
342 return int(res)
345def get_master_location(path=None, commandline=True): # pragma: no cover
346 """
347 raises an exception
348 """
349 raise NotImplementedError()
352def get_nb_commits(path=None, commandline=True): # pragma: no cover
353 """
354 returns the number of commit
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()