8 """ |
8 """ |
9 |
9 |
10 import struct |
10 import struct |
11 import io |
11 import io |
12 |
12 |
13 from PyQt6.QtCore import ( |
13 from PyQt6.QtCore import QProcess, QObject, QByteArray, QCoreApplication, QThread |
14 QProcess, QObject, QByteArray, QCoreApplication, QThread |
|
15 ) |
|
16 from PyQt6.QtWidgets import QDialog |
14 from PyQt6.QtWidgets import QDialog |
17 |
15 |
18 from .HgUtilities import prepareProcess, getHgExecutable |
16 from .HgUtilities import prepareProcess, getHgExecutable |
19 |
17 |
20 |
18 |
21 class HgClient(QObject): |
19 class HgClient(QObject): |
22 """ |
20 """ |
23 Class implementing the Mercurial command server interface. |
21 Class implementing the Mercurial command server interface. |
24 """ |
22 """ |
|
23 |
25 InputFormat = ">I" |
24 InputFormat = ">I" |
26 OutputFormat = ">cI" |
25 OutputFormat = ">cI" |
27 OutputFormatSize = struct.calcsize(OutputFormat) |
26 OutputFormatSize = struct.calcsize(OutputFormat) |
28 ReturnFormat = ">i" |
27 ReturnFormat = ">i" |
29 |
28 |
30 Channels = (b"I", b"L", b"o", b"e", b"r", b"d") |
29 Channels = (b"I", b"L", b"o", b"e", b"r", b"d") |
31 |
30 |
32 def __init__(self, repoPath, encoding, vcs, parent=None): |
31 def __init__(self, repoPath, encoding, vcs, parent=None): |
33 """ |
32 """ |
34 Constructor |
33 Constructor |
35 |
34 |
36 @param repoPath root directory of the repository |
35 @param repoPath root directory of the repository |
37 @type str |
36 @type str |
38 @param encoding encoding to be used by the command server |
37 @param encoding encoding to be used by the command server |
39 @type str |
38 @type str |
40 @param vcs reference to the VCS object |
39 @param vcs reference to the VCS object |
41 @type Hg |
40 @type Hg |
42 @param parent reference to the parent object |
41 @param parent reference to the parent object |
43 @type QObject |
42 @type QObject |
44 """ |
43 """ |
45 super().__init__(parent) |
44 super().__init__(parent) |
46 |
45 |
47 self.__server = None |
46 self.__server = None |
48 self.__started = False |
47 self.__started = False |
49 self.__version = None |
48 self.__version = None |
50 self.__encoding = vcs.getEncoding() |
49 self.__encoding = vcs.getEncoding() |
51 self.__cancel = False |
50 self.__cancel = False |
52 self.__commandRunning = False |
51 self.__commandRunning = False |
53 self.__repoPath = repoPath |
52 self.__repoPath = repoPath |
54 |
53 |
55 # generate command line and environment |
54 # generate command line and environment |
56 self.__serverArgs = vcs.initCommand("serve") |
55 self.__serverArgs = vcs.initCommand("serve") |
57 self.__serverArgs.append("--cmdserver") |
56 self.__serverArgs.append("--cmdserver") |
58 self.__serverArgs.append("pipe") |
57 self.__serverArgs.append("pipe") |
59 self.__serverArgs.append("--config") |
58 self.__serverArgs.append("--config") |
60 self.__serverArgs.append("ui.interactive=True") |
59 self.__serverArgs.append("ui.interactive=True") |
61 if repoPath: |
60 if repoPath: |
62 self.__serverArgs.append("--repository") |
61 self.__serverArgs.append("--repository") |
63 self.__serverArgs.append(repoPath) |
62 self.__serverArgs.append(repoPath) |
64 |
63 |
65 if encoding: |
64 if encoding: |
66 self.__encoding = encoding |
65 self.__encoding = encoding |
67 if "--encoding" in self.__serverArgs: |
66 if "--encoding" in self.__serverArgs: |
68 # use the defined encoding via the environment |
67 # use the defined encoding via the environment |
69 index = self.__serverArgs.index("--encoding") |
68 index = self.__serverArgs.index("--encoding") |
70 del self.__serverArgs[index:index + 2] |
69 del self.__serverArgs[index : index + 2] |
71 |
70 |
72 def startServer(self): |
71 def startServer(self): |
73 """ |
72 """ |
74 Public method to start the command server. |
73 Public method to start the command server. |
75 |
74 |
76 @return tuple of flag indicating a successful start and an error |
75 @return tuple of flag indicating a successful start and an error |
77 message in case of failure |
76 message in case of failure |
78 @rtype tuple of (bool, str) |
77 @rtype tuple of (bool, str) |
79 """ |
78 """ |
80 self.__server = QProcess() |
79 self.__server = QProcess() |
81 self.__server.setWorkingDirectory(self.__repoPath) |
80 self.__server.setWorkingDirectory(self.__repoPath) |
82 |
81 |
83 # connect signals |
82 # connect signals |
84 self.__server.finished.connect(self.__serverFinished) |
83 self.__server.finished.connect(self.__serverFinished) |
85 |
84 |
86 prepareProcess(self.__server, self.__encoding) |
85 prepareProcess(self.__server, self.__encoding) |
87 |
86 |
88 exe = getHgExecutable() |
87 exe = getHgExecutable() |
89 self.__server.start(exe, self.__serverArgs) |
88 self.__server.start(exe, self.__serverArgs) |
90 serverStarted = self.__server.waitForStarted(15000) |
89 serverStarted = self.__server.waitForStarted(15000) |
91 if not serverStarted: |
90 if not serverStarted: |
92 return False, self.tr( |
91 return False, self.tr( |
93 'The process {0} could not be started. ' |
92 "The process {0} could not be started. " |
94 'Ensure, that it is in the search path.' |
93 "Ensure, that it is in the search path." |
95 ).format(exe) |
94 ).format(exe) |
96 |
95 |
97 self.__server.setReadChannel(QProcess.ProcessChannel.StandardOutput) |
96 self.__server.setReadChannel(QProcess.ProcessChannel.StandardOutput) |
98 ok, error = self.__readHello() |
97 ok, error = self.__readHello() |
99 self.__started = ok |
98 self.__started = ok |
100 return ok, error |
99 return ok, error |
101 |
100 |
102 def stopServer(self): |
101 def stopServer(self): |
103 """ |
102 """ |
104 Public method to stop the command server. |
103 Public method to stop the command server. |
105 """ |
104 """ |
106 if self.__server is not None: |
105 if self.__server is not None: |
110 self.__server.terminate() |
109 self.__server.terminate() |
111 res = self.__server.waitForFinished(3000) |
110 res = self.__server.waitForFinished(3000) |
112 if not res: |
111 if not res: |
113 self.__server.kill() |
112 self.__server.kill() |
114 self.__server.waitForFinished(3000) |
113 self.__server.waitForFinished(3000) |
115 |
114 |
116 self.__started = False |
115 self.__started = False |
117 self.__server.deleteLater() |
116 self.__server.deleteLater() |
118 self.__server = None |
117 self.__server = None |
119 |
118 |
120 def restartServer(self): |
119 def restartServer(self): |
121 """ |
120 """ |
122 Public method to restart the command server. |
121 Public method to restart the command server. |
123 |
122 |
124 @return tuple of flag indicating a successful start and an error |
123 @return tuple of flag indicating a successful start and an error |
125 message in case of failure |
124 message in case of failure |
126 @rtype tuple of (bool, str) |
125 @rtype tuple of (bool, str) |
127 """ |
126 """ |
128 self.stopServer() |
127 self.stopServer() |
129 return self.startServer() |
128 return self.startServer() |
130 |
129 |
131 def __readHello(self): |
130 def __readHello(self): |
132 """ |
131 """ |
133 Private method to read the hello message sent by the command server. |
132 Private method to read the hello message sent by the command server. |
134 |
133 |
135 @return tuple of flag indicating success and an error message in case |
134 @return tuple of flag indicating success and an error message in case |
136 of failure |
135 of failure |
137 @rtype tuple of (bool, str) |
136 @rtype tuple of (bool, str) |
138 """ |
137 """ |
139 ch, msg = self.__readChannel() |
138 ch, msg = self.__readChannel() |
140 if not ch: |
139 if not ch: |
141 return False, self.tr("Did not receive the 'hello' message.") |
140 return False, self.tr("Did not receive the 'hello' message.") |
142 elif ch != "o": |
141 elif ch != "o": |
143 return False, self.tr("Received data on unexpected channel.") |
142 return False, self.tr("Received data on unexpected channel.") |
144 |
143 |
145 msg = msg.split("\n") |
144 msg = msg.split("\n") |
146 |
145 |
147 if not msg[0].startswith("capabilities: "): |
146 if not msg[0].startswith("capabilities: "): |
148 return False, self.tr( |
147 return False, self.tr( |
149 "Bad 'hello' message, expected 'capabilities: '" |
148 "Bad 'hello' message, expected 'capabilities: '" " but got '{0}'." |
150 " but got '{0}'.").format(msg[0]) |
149 ).format(msg[0]) |
151 self.__capabilities = msg[0][len('capabilities: '):] |
150 self.__capabilities = msg[0][len("capabilities: ") :] |
152 if not self.__capabilities: |
151 if not self.__capabilities: |
153 return False, self.tr("'capabilities' message did not contain" |
152 return False, self.tr( |
154 " any capability.") |
153 "'capabilities' message did not contain" " any capability." |
155 |
154 ) |
|
155 |
156 self.__capabilities = set(self.__capabilities.split()) |
156 self.__capabilities = set(self.__capabilities.split()) |
157 if "runcommand" not in self.__capabilities: |
157 if "runcommand" not in self.__capabilities: |
158 return False, "'capabilities' did not contain 'runcommand'." |
158 return False, "'capabilities' did not contain 'runcommand'." |
159 |
159 |
160 if not msg[1].startswith("encoding: "): |
160 if not msg[1].startswith("encoding: "): |
161 return False, self.tr( |
161 return False, self.tr( |
162 "Bad 'hello' message, expected 'encoding: '" |
162 "Bad 'hello' message, expected 'encoding: '" " but got '{0}'." |
163 " but got '{0}'.").format(msg[1]) |
163 ).format(msg[1]) |
164 encoding = msg[1][len('encoding: '):] |
164 encoding = msg[1][len("encoding: ") :] |
165 if not encoding: |
165 if not encoding: |
166 return False, self.tr("'encoding' message did not contain" |
166 return False, self.tr("'encoding' message did not contain" " any encoding.") |
167 " any encoding.") |
|
168 self.__encoding = encoding |
167 self.__encoding = encoding |
169 |
168 |
170 return True, "" |
169 return True, "" |
171 |
170 |
172 def __serverFinished(self, exitCode, exitStatus): |
171 def __serverFinished(self, exitCode, exitStatus): |
173 """ |
172 """ |
174 Private slot connected to the finished signal. |
173 Private slot connected to the finished signal. |
175 |
174 |
176 @param exitCode exit code of the process |
175 @param exitCode exit code of the process |
177 @type int |
176 @type int |
178 @param exitStatus exit status of the process |
177 @param exitStatus exit status of the process |
179 @type QProcess.ExitStatus |
178 @type QProcess.ExitStatus |
180 """ |
179 """ |
181 self.__started = False |
180 self.__started = False |
182 |
181 |
183 def __readChannel(self): |
182 def __readChannel(self): |
184 """ |
183 """ |
185 Private method to read data from the command server. |
184 Private method to read data from the command server. |
186 |
185 |
187 @return tuple of channel designator and channel data |
186 @return tuple of channel designator and channel data |
188 @rtype tuple of (str, int or str or bytes) |
187 @rtype tuple of (str, int or str or bytes) |
189 """ |
188 """ |
190 if ( |
189 if self.__server.bytesAvailable() > 0 or self.__server.waitForReadyRead(10000): |
191 self.__server.bytesAvailable() > 0 or |
|
192 self.__server.waitForReadyRead(10000) |
|
193 ): |
|
194 data = bytes(self.__server.peek(HgClient.OutputFormatSize)) |
190 data = bytes(self.__server.peek(HgClient.OutputFormatSize)) |
195 if not data or len(data) < HgClient.OutputFormatSize: |
191 if not data or len(data) < HgClient.OutputFormatSize: |
196 return "", "" |
192 return "", "" |
197 |
193 |
198 channel, length = struct.unpack(HgClient.OutputFormat, data) |
194 channel, length = struct.unpack(HgClient.OutputFormat, data) |
199 channel = channel.decode(self.__encoding) |
195 channel = channel.decode(self.__encoding) |
200 if channel in "IL": |
196 if channel in "IL": |
201 self.__server.read(HgClient.OutputFormatSize) |
197 self.__server.read(HgClient.OutputFormatSize) |
202 return channel, length |
198 return channel, length |
203 else: |
199 else: |
204 if ( |
200 if self.__server.bytesAvailable() < HgClient.OutputFormatSize + length: |
205 self.__server.bytesAvailable() < |
|
206 HgClient.OutputFormatSize + length |
|
207 ): |
|
208 return "", "" |
201 return "", "" |
209 self.__server.read(HgClient.OutputFormatSize) |
202 self.__server.read(HgClient.OutputFormatSize) |
210 data = self.__server.read(length) |
203 data = self.__server.read(length) |
211 if channel == "r": |
204 if channel == "r": |
212 return (channel, data) |
205 return (channel, data) |
213 else: |
206 else: |
214 return (channel, str(data, self.__encoding, "replace")) |
207 return (channel, str(data, self.__encoding, "replace")) |
215 else: |
208 else: |
216 return "", "" |
209 return "", "" |
217 |
210 |
218 def __writeDataBlock(self, data): |
211 def __writeDataBlock(self, data): |
219 """ |
212 """ |
220 Private slot to write some data to the command server. |
213 Private slot to write some data to the command server. |
221 |
214 |
222 @param data data to be sent |
215 @param data data to be sent |
223 @type str |
216 @type str |
224 """ |
217 """ |
225 if not isinstance(data, bytes): |
218 if not isinstance(data, bytes): |
226 data = data.encode(self.__encoding) |
219 data = data.encode(self.__encoding) |
227 self.__server.write(struct.pack(HgClient.InputFormat, len(data))) |
220 self.__server.write(struct.pack(HgClient.InputFormat, len(data))) |
228 self.__server.write(data) |
221 self.__server.write(data) |
229 self.__server.waitForBytesWritten() |
222 self.__server.waitForBytesWritten() |
230 |
223 |
231 def __runcommand(self, args, inputChannels, outputChannels): |
224 def __runcommand(self, args, inputChannels, outputChannels): |
232 """ |
225 """ |
233 Private method to run a command in the server (low level). |
226 Private method to run a command in the server (low level). |
234 |
227 |
235 @param args list of arguments for the command |
228 @param args list of arguments for the command |
236 @type list of str |
229 @type list of str |
237 @param inputChannels dictionary of input channels. The dictionary must |
230 @param inputChannels dictionary of input channels. The dictionary must |
238 have the keys 'I' and 'L' and each entry must be a function |
231 have the keys 'I' and 'L' and each entry must be a function |
239 receiving the number of bytes to write. |
232 receiving the number of bytes to write. |
248 @exception RuntimeError raised to indicate an unexpected command |
241 @exception RuntimeError raised to indicate an unexpected command |
249 channel |
242 channel |
250 """ |
243 """ |
251 if not self.__started: |
244 if not self.__started: |
252 return -1 |
245 return -1 |
253 |
246 |
254 self.__server.write(QByteArray(b'runcommand\n')) |
247 self.__server.write(QByteArray(b"runcommand\n")) |
255 self.__writeDataBlock('\0'.join(args)) |
248 self.__writeDataBlock("\0".join(args)) |
256 |
249 |
257 while True: |
250 while True: |
258 QCoreApplication.processEvents() |
251 QCoreApplication.processEvents() |
259 |
252 |
260 if self.__cancel: |
253 if self.__cancel: |
261 return -10 |
254 return -10 |
262 |
255 |
263 if self.__server is None: |
256 if self.__server is None: |
264 return -1 |
257 return -1 |
265 |
258 |
266 if self.__server.bytesAvailable() == 0: |
259 if self.__server.bytesAvailable() == 0: |
267 QThread.msleep(50) |
260 QThread.msleep(50) |
268 continue |
261 continue |
269 channel, data = self.__readChannel() |
262 channel, data = self.__readChannel() |
270 |
263 |
271 # input channels |
264 # input channels |
272 if channel in inputChannels: |
265 if channel in inputChannels: |
273 if channel == "L": |
266 if channel == "L": |
274 inputData, isPassword = inputChannels[channel](data) |
267 inputData, isPassword = inputChannels[channel](data) |
275 # echo the input to the output if it was a prompt |
268 # echo the input to the output if it was a prompt |
276 if not isPassword: |
269 if not isPassword: |
277 outputChannels["o"](inputData) |
270 outputChannels["o"](inputData) |
278 else: |
271 else: |
279 inputData = inputChannels[channel](data) |
272 inputData = inputChannels[channel](data) |
280 self.__writeDataBlock(inputData) |
273 self.__writeDataBlock(inputData) |
281 |
274 |
282 # output channels |
275 # output channels |
283 elif channel in outputChannels: |
276 elif channel in outputChannels: |
284 outputChannels[channel](data) |
277 outputChannels[channel](data) |
285 |
278 |
286 # result channel, command is finished |
279 # result channel, command is finished |
287 elif channel == "r": |
280 elif channel == "r": |
288 return struct.unpack(HgClient.ReturnFormat, data)[0] |
281 return struct.unpack(HgClient.ReturnFormat, data)[0] |
289 |
282 |
290 # unexpected but required channel |
283 # unexpected but required channel |
291 elif channel.isupper(): |
284 elif channel.isupper(): |
292 raise RuntimeError( |
285 raise RuntimeError( |
293 "Unexpected but required channel '{0}'.".format(channel)) |
286 "Unexpected but required channel '{0}'.".format(channel) |
294 |
287 ) |
|
288 |
295 # optional channels or no channel at all |
289 # optional channels or no channel at all |
296 else: |
290 else: |
297 pass |
291 pass |
298 |
292 |
299 def __prompt(self, size, message): |
293 def __prompt(self, size, message): |
300 """ |
294 """ |
301 Private method to prompt the user for some input. |
295 Private method to prompt the user for some input. |
302 |
296 |
303 @param size maximum length of the requested input |
297 @param size maximum length of the requested input |
304 @type int |
298 @type int |
305 @param message message sent by the server |
299 @param message message sent by the server |
306 @type str |
300 @type str |
307 @return tuple containing data entered by the user and |
301 @return tuple containing data entered by the user and |
308 a flag indicating a password input |
302 a flag indicating a password input |
309 @rtype tuple of (str, bool) |
303 @rtype tuple of (str, bool) |
310 """ |
304 """ |
311 from .HgClientPromptDialog import HgClientPromptDialog |
305 from .HgClientPromptDialog import HgClientPromptDialog |
|
306 |
312 inputData = "" |
307 inputData = "" |
313 isPassword = False |
308 isPassword = False |
314 dlg = HgClientPromptDialog(size, message) |
309 dlg = HgClientPromptDialog(size, message) |
315 if dlg.exec() == QDialog.DialogCode.Accepted: |
310 if dlg.exec() == QDialog.DialogCode.Accepted: |
316 inputData = dlg.getInput() + '\n' |
311 inputData = dlg.getInput() + "\n" |
317 isPassword = dlg.isPassword() |
312 isPassword = dlg.isPassword() |
318 return inputData, isPassword |
313 return inputData, isPassword |
319 |
314 |
320 def runcommand(self, args, prompt=None, inputData=None, output=None, |
315 def runcommand(self, args, prompt=None, inputData=None, output=None, error=None): |
321 error=None): |
|
322 """ |
316 """ |
323 Public method to execute a command via the command server. |
317 Public method to execute a command via the command server. |
324 |
318 |
325 @param args list of arguments for the command |
319 @param args list of arguments for the command |
326 @type list of str |
320 @type list of str |
327 @param prompt function to reply to prompts by the server. It |
321 @param prompt function to reply to prompts by the server. It |
328 receives the max number of bytes to return and the contents |
322 receives the max number of bytes to return and the contents |
329 of the output channel received so far. If an output function is |
323 of the output channel received so far. If an output function is |
348 if not self.__started: |
342 if not self.__started: |
349 # try to start the Mercurial command server |
343 # try to start the Mercurial command server |
350 ok, startError = self.startServer() |
344 ok, startError = self.startServer() |
351 if not ok: |
345 if not ok: |
352 return "", startError |
346 return "", startError |
353 |
347 |
354 self.__commandRunning = True |
348 self.__commandRunning = True |
355 outputChannels = {} |
349 outputChannels = {} |
356 outputBuffer = None |
350 outputBuffer = None |
357 errorBuffer = None |
351 errorBuffer = None |
358 |
352 |
359 if output is None: |
353 if output is None: |
360 outputBuffer = io.StringIO() |
354 outputBuffer = io.StringIO() |
361 outputChannels["o"] = outputBuffer.write |
355 outputChannels["o"] = outputBuffer.write |
362 else: |
356 else: |
363 outputChannels["o"] = output |
357 outputChannels["o"] = output |
364 if error: |
358 if error: |
365 outputChannels["e"] = error |
359 outputChannels["e"] = error |
366 else: |
360 else: |
367 errorBuffer = io.StringIO() |
361 errorBuffer = io.StringIO() |
368 outputChannels["e"] = errorBuffer.write |
362 outputChannels["e"] = errorBuffer.write |
369 |
363 |
370 inputChannels = {} |
364 inputChannels = {} |
371 if prompt is not None: |
365 if prompt is not None: |
|
366 |
372 def func(size): |
367 def func(size): |
373 msg = "" if outputBuffer is None else outputBuffer.getvalue() |
368 msg = "" if outputBuffer is None else outputBuffer.getvalue() |
374 reply, isPassword = prompt(size, msg) |
369 reply, isPassword = prompt(size, msg) |
375 return reply, isPassword |
370 return reply, isPassword |
|
371 |
376 inputChannels["L"] = func |
372 inputChannels["L"] = func |
377 else: |
373 else: |
|
374 |
378 def myprompt(size): |
375 def myprompt(size): |
379 msg = (self.tr("For message see output dialog.") |
376 msg = ( |
380 if outputBuffer is None else outputBuffer.getvalue()) |
377 self.tr("For message see output dialog.") |
|
378 if outputBuffer is None |
|
379 else outputBuffer.getvalue() |
|
380 ) |
381 reply, isPassword = self.__prompt(size, msg) |
381 reply, isPassword = self.__prompt(size, msg) |
382 return reply, isPassword |
382 return reply, isPassword |
|
383 |
383 inputChannels["L"] = myprompt |
384 inputChannels["L"] = myprompt |
384 if inputData is not None: |
385 if inputData is not None: |
385 inputChannels["I"] = inputData |
386 inputChannels["I"] = inputData |
386 |
387 |
387 self.__cancel = False |
388 self.__cancel = False |
388 self.__runcommand(args, inputChannels, outputChannels) |
389 self.__runcommand(args, inputChannels, outputChannels) |
389 |
390 |
390 out = outputBuffer.getvalue() if outputBuffer else "" |
391 out = outputBuffer.getvalue() if outputBuffer else "" |
391 err = errorBuffer.getvalue() if errorBuffer else "" |
392 err = errorBuffer.getvalue() if errorBuffer else "" |
392 |
393 |
393 self.__commandRunning = False |
394 self.__commandRunning = False |
394 |
395 |
395 return out, err |
396 return out, err |
396 |
397 |
397 def cancel(self): |
398 def cancel(self): |
398 """ |
399 """ |
399 Public method to cancel the running command. |
400 Public method to cancel the running command. |
400 """ |
401 """ |
401 self.__cancel = True |
402 self.__cancel = True |
402 self.restartServer() |
403 self.restartServer() |
403 |
404 |
404 def wasCanceled(self): |
405 def wasCanceled(self): |
405 """ |
406 """ |
406 Public method to check, if the last command was canceled. |
407 Public method to check, if the last command was canceled. |
407 |
408 |
408 @return flag indicating the cancel state |
409 @return flag indicating the cancel state |
409 @rtype bool |
410 @rtype bool |
410 """ |
411 """ |
411 return self.__cancel |
412 return self.__cancel |
412 |
413 |
413 def isExecuting(self): |
414 def isExecuting(self): |
414 """ |
415 """ |
415 Public method to check, if the server is executing a command. |
416 Public method to check, if the server is executing a command. |
416 |
417 |
417 @return flag indicating the execution of a command |
418 @return flag indicating the execution of a command |
418 @rtype bool |
419 @rtype bool |
419 """ |
420 """ |
420 return self.__commandRunning |
421 return self.__commandRunning |
421 |
422 |
422 def getRepository(self): |
423 def getRepository(self): |
423 """ |
424 """ |
424 Public method to get the repository path this client is serving. |
425 Public method to get the repository path this client is serving. |
425 |
426 |
426 @return repository path |
427 @return repository path |
427 @rtype str |
428 @rtype str |
428 """ |
429 """ |
429 return self.__repoPath |
430 return self.__repoPath |