|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2003 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the version control systems interface to Subversion. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 str = unicode |
|
13 except NameError: |
|
14 pass |
|
15 |
|
16 import os |
|
17 import re |
|
18 import shutil |
|
19 |
|
20 from PyQt5.QtCore import pyqtSignal, QProcess, QRegExp, QCoreApplication |
|
21 from PyQt5.QtWidgets import QLineEdit, QDialog, QInputDialog, QApplication |
|
22 |
|
23 from E5Gui.E5Application import e5App |
|
24 from E5Gui import E5MessageBox |
|
25 |
|
26 from VCS.VersionControl import VersionControl |
|
27 |
|
28 from .SvnDialog import SvnDialog |
|
29 from .SvnUtilities import getConfigPath, amendConfig, createDefaultConfig |
|
30 |
|
31 import Preferences |
|
32 import Utilities |
|
33 |
|
34 |
|
35 class Subversion(VersionControl): |
|
36 """ |
|
37 Class implementing the version control systems interface to Subversion. |
|
38 |
|
39 @signal committed() emitted after the commit action has completed |
|
40 """ |
|
41 committed = pyqtSignal() |
|
42 |
|
43 def __init__(self, plugin, parent=None, name=None): |
|
44 """ |
|
45 Constructor |
|
46 |
|
47 @param plugin reference to the plugin object |
|
48 @param parent parent widget (QWidget) |
|
49 @param name name of this object (string) |
|
50 """ |
|
51 VersionControl.__init__(self, parent, name) |
|
52 self.defaultOptions = { |
|
53 'global': [''], |
|
54 'commit': [''], |
|
55 'checkout': [''], |
|
56 'update': [''], |
|
57 'add': [''], |
|
58 'remove': [''], |
|
59 'diff': [''], |
|
60 'log': [''], |
|
61 'history': [''], |
|
62 'status': [''], |
|
63 'tag': [''], |
|
64 'export': [''] |
|
65 } |
|
66 self.interestingDataKeys = [ |
|
67 "standardLayout", |
|
68 ] |
|
69 |
|
70 self.__plugin = plugin |
|
71 self.__ui = parent |
|
72 |
|
73 self.options = self.defaultOptions |
|
74 self.otherData["standardLayout"] = True |
|
75 self.tagsList = [] |
|
76 self.branchesList = [] |
|
77 self.allTagsBranchesList = [] |
|
78 self.mergeList = [[], [], []] |
|
79 self.showedTags = False |
|
80 self.showedBranches = False |
|
81 |
|
82 self.tagTypeList = [ |
|
83 'tags', |
|
84 'branches', |
|
85 ] |
|
86 |
|
87 self.commandHistory = [] |
|
88 self.wdHistory = [] |
|
89 |
|
90 if "SVN_ASP_DOT_NET_HACK" in os.environ: |
|
91 self.adminDir = '_svn' |
|
92 else: |
|
93 self.adminDir = '.svn' |
|
94 |
|
95 self.log = None |
|
96 self.diff = None |
|
97 self.sbsDiff = None |
|
98 self.status = None |
|
99 self.propList = None |
|
100 self.tagbranchList = None |
|
101 self.blame = None |
|
102 self.repoBrowser = None |
|
103 self.logBrowser = None |
|
104 |
|
105 # regular expression object for evaluation of the status output |
|
106 self.rx_status1 = QRegExp( |
|
107 '(.{8})\\s+([0-9-]+)\\s+([0-9?]+)\\s+(\\S+)\\s+(.+)') |
|
108 self.rx_status2 = QRegExp('(.{8})\\s+(.+)\\s*') |
|
109 self.statusCache = {} |
|
110 |
|
111 self.__commitData = {} |
|
112 self.__commitDialog = None |
|
113 |
|
114 self.__wcng = True |
|
115 # assume new generation working copy metadata format |
|
116 |
|
117 def getPlugin(self): |
|
118 """ |
|
119 Public method to get a reference to the plugin object. |
|
120 |
|
121 @return reference to the plugin object (VcsSubversionPlugin) |
|
122 """ |
|
123 return self.__plugin |
|
124 |
|
125 def vcsShutdown(self): |
|
126 """ |
|
127 Public method used to shutdown the Subversion interface. |
|
128 """ |
|
129 if self.log is not None: |
|
130 self.log.close() |
|
131 if self.diff is not None: |
|
132 self.diff.close() |
|
133 if self.sbsDiff is not None: |
|
134 self.sbsDiff.close() |
|
135 if self.status is not None: |
|
136 self.status.close() |
|
137 if self.propList is not None: |
|
138 self.propList.close() |
|
139 if self.tagbranchList is not None: |
|
140 self.tagbranchList.close() |
|
141 if self.blame is not None: |
|
142 self.blame.close() |
|
143 if self.repoBrowser is not None: |
|
144 self.repoBrowser.close() |
|
145 if self.logBrowser is not None: |
|
146 self.logBrowser.close() |
|
147 |
|
148 def vcsExists(self): |
|
149 """ |
|
150 Public method used to test for the presence of the svn executable. |
|
151 |
|
152 @return flag indicating the existance (boolean) and an error message |
|
153 (string) |
|
154 """ |
|
155 self.versionStr = '' |
|
156 errMsg = "" |
|
157 ioEncoding = Preferences.getSystem("IOEncoding") |
|
158 |
|
159 process = QProcess() |
|
160 process.start('svn', ['--version']) |
|
161 procStarted = process.waitForStarted(5000) |
|
162 if procStarted: |
|
163 finished = process.waitForFinished(30000) |
|
164 if finished and process.exitCode() == 0: |
|
165 output = \ |
|
166 str(process.readAllStandardOutput(), ioEncoding, 'replace') |
|
167 self.versionStr = output.split()[2] |
|
168 v = list(re.match(r'.*?(\d+)\.(\d+)\.?(\d+)?', self.versionStr) |
|
169 .groups()) |
|
170 for i in range(3): |
|
171 try: |
|
172 v[i] = int(v[i]) |
|
173 except TypeError: |
|
174 v[i] = 0 |
|
175 except IndexError: |
|
176 v.append(0) |
|
177 self.version = tuple(v) |
|
178 return True, errMsg |
|
179 else: |
|
180 if finished: |
|
181 errMsg = self.tr( |
|
182 "The svn process finished with the exit code {0}")\ |
|
183 .format(process.exitCode()) |
|
184 else: |
|
185 errMsg = self.tr( |
|
186 "The svn process did not finish within 30s.") |
|
187 else: |
|
188 errMsg = self.tr("Could not start the svn executable.") |
|
189 |
|
190 return False, errMsg |
|
191 |
|
192 def vcsInit(self, vcsDir, noDialog=False): |
|
193 """ |
|
194 Public method used to initialize the subversion repository. |
|
195 |
|
196 The subversion repository has to be initialized from outside eric6 |
|
197 because the respective command always works locally. Therefore we |
|
198 always return TRUE without doing anything. |
|
199 |
|
200 @param vcsDir name of the VCS directory (string) |
|
201 @param noDialog flag indicating quiet operations (boolean) |
|
202 @return always TRUE |
|
203 """ |
|
204 return True |
|
205 |
|
206 def vcsConvertProject(self, vcsDataDict, project): |
|
207 """ |
|
208 Public method to convert an uncontrolled project to a version |
|
209 controlled project. |
|
210 |
|
211 @param vcsDataDict dictionary of data required for the conversion |
|
212 @param project reference to the project object |
|
213 """ |
|
214 success = self.vcsImport(vcsDataDict, project.ppath)[0] |
|
215 if not success: |
|
216 E5MessageBox.critical( |
|
217 self.__ui, |
|
218 self.tr("Create project in repository"), |
|
219 self.tr( |
|
220 """The project could not be created in the repository.""" |
|
221 """ Maybe the given repository doesn't exist or the""" |
|
222 """ repository server is down.""")) |
|
223 else: |
|
224 cwdIsPpath = False |
|
225 if os.getcwd() == project.ppath: |
|
226 os.chdir(os.path.dirname(project.ppath)) |
|
227 cwdIsPpath = True |
|
228 tmpProjectDir = "{0}_tmp".format(project.ppath) |
|
229 shutil.rmtree(tmpProjectDir, True) |
|
230 os.rename(project.ppath, tmpProjectDir) |
|
231 os.makedirs(project.ppath) |
|
232 self.vcsCheckout(vcsDataDict, project.ppath) |
|
233 if cwdIsPpath: |
|
234 os.chdir(project.ppath) |
|
235 self.vcsCommit(project.ppath, vcsDataDict["message"], True) |
|
236 pfn = project.pfile |
|
237 if not os.path.isfile(pfn): |
|
238 pfn += "z" |
|
239 if not os.path.isfile(pfn): |
|
240 E5MessageBox.critical( |
|
241 self.__ui, |
|
242 self.tr("New project"), |
|
243 self.tr( |
|
244 """The project could not be checked out of the""" |
|
245 """ repository.<br />""" |
|
246 """Restoring the original contents.""")) |
|
247 if os.getcwd() == project.ppath: |
|
248 os.chdir(os.path.dirname(project.ppath)) |
|
249 cwdIsPpath = True |
|
250 else: |
|
251 cwdIsPpath = False |
|
252 shutil.rmtree(project.ppath, True) |
|
253 os.rename(tmpProjectDir, project.ppath) |
|
254 project.pdata["VCS"] = 'None' |
|
255 project.vcs = None |
|
256 project.setDirty(True) |
|
257 project.saveProject() |
|
258 project.closeProject() |
|
259 return |
|
260 shutil.rmtree(tmpProjectDir, True) |
|
261 project.closeProject(noSave=True) |
|
262 project.openProject(pfn) |
|
263 |
|
264 def vcsImport(self, vcsDataDict, projectDir, noDialog=False): |
|
265 """ |
|
266 Public method used to import the project into the Subversion |
|
267 repository. |
|
268 |
|
269 @param vcsDataDict dictionary of data required for the import |
|
270 @param projectDir project directory (string) |
|
271 @param noDialog flag indicating quiet operations |
|
272 @return flag indicating an execution without errors (boolean) |
|
273 and a flag indicating the version control status (boolean) |
|
274 """ |
|
275 noDialog = False |
|
276 msg = vcsDataDict["message"] |
|
277 if not msg: |
|
278 msg = '***' |
|
279 |
|
280 vcsDir = self.svnNormalizeURL(vcsDataDict["url"]) |
|
281 if vcsDir.startswith('/'): |
|
282 vcsDir = 'file://{0}'.format(vcsDir) |
|
283 elif vcsDir[1] in ['|', ':']: |
|
284 vcsDir = 'file:///{0}'.format(vcsDir) |
|
285 |
|
286 project = vcsDir[vcsDir.rfind('/') + 1:] |
|
287 |
|
288 # create the dir structure to be imported into the repository |
|
289 tmpDir = '{0}_tmp'.format(projectDir) |
|
290 try: |
|
291 os.makedirs(tmpDir) |
|
292 if self.otherData["standardLayout"]: |
|
293 os.mkdir(os.path.join(tmpDir, project)) |
|
294 os.mkdir(os.path.join(tmpDir, project, 'branches')) |
|
295 os.mkdir(os.path.join(tmpDir, project, 'tags')) |
|
296 shutil.copytree( |
|
297 projectDir, os.path.join(tmpDir, project, 'trunk')) |
|
298 else: |
|
299 shutil.copytree(projectDir, os.path.join(tmpDir, project)) |
|
300 except OSError: |
|
301 if os.path.isdir(tmpDir): |
|
302 shutil.rmtree(tmpDir, True) |
|
303 return False, False |
|
304 |
|
305 args = [] |
|
306 args.append('import') |
|
307 self.addArguments(args, self.options['global']) |
|
308 args.append('-m') |
|
309 args.append(msg) |
|
310 args.append(self.__svnURL(vcsDir)) |
|
311 |
|
312 if noDialog: |
|
313 status = self.startSynchronizedProcess( |
|
314 QProcess(), "svn", args, os.path.join(tmpDir, project)) |
|
315 else: |
|
316 dia = SvnDialog( |
|
317 self.tr('Importing project into Subversion repository')) |
|
318 res = dia.startProcess(args, os.path.join(tmpDir, project)) |
|
319 if res: |
|
320 dia.exec_() |
|
321 status = dia.normalExit() |
|
322 |
|
323 shutil.rmtree(tmpDir, True) |
|
324 return status, False |
|
325 |
|
326 def vcsCheckout(self, vcsDataDict, projectDir, noDialog=False): |
|
327 """ |
|
328 Public method used to check the project out of the Subversion |
|
329 repository. |
|
330 |
|
331 @param vcsDataDict dictionary of data required for the checkout |
|
332 @param projectDir project directory to create (string) |
|
333 @param noDialog flag indicating quiet operations |
|
334 @return flag indicating an execution without errors (boolean) |
|
335 """ |
|
336 noDialog = False |
|
337 try: |
|
338 tag = vcsDataDict["tag"] |
|
339 except KeyError: |
|
340 tag = None |
|
341 vcsDir = self.svnNormalizeURL(vcsDataDict["url"]) |
|
342 if vcsDir.startswith('/'): |
|
343 vcsDir = 'file://{0}'.format(vcsDir) |
|
344 elif vcsDir[1] in ['|', ':']: |
|
345 vcsDir = 'file:///{0}'.format(vcsDir) |
|
346 |
|
347 if self.otherData["standardLayout"]: |
|
348 if tag is None or tag == '': |
|
349 svnUrl = '{0}/trunk'.format(vcsDir) |
|
350 else: |
|
351 if not tag.startswith('tags') and \ |
|
352 not tag.startswith('branches'): |
|
353 tagType, ok = QInputDialog.getItem( |
|
354 None, |
|
355 self.tr("Subversion Checkout"), |
|
356 self.tr( |
|
357 "The tag must be a normal tag (tags) or" |
|
358 " a branch tag (branches)." |
|
359 " Please select from the list."), |
|
360 self.tagTypeList, |
|
361 0, False) |
|
362 if not ok: |
|
363 return False |
|
364 tag = '{0}/{1}'.format(tagType, tag) |
|
365 svnUrl = '{0}/{1}'.format(vcsDir, tag) |
|
366 else: |
|
367 svnUrl = vcsDir |
|
368 |
|
369 args = [] |
|
370 args.append('checkout') |
|
371 self.addArguments(args, self.options['global']) |
|
372 self.addArguments(args, self.options['checkout']) |
|
373 args.append(self.__svnURL(svnUrl)) |
|
374 args.append(projectDir) |
|
375 |
|
376 if noDialog: |
|
377 return self.startSynchronizedProcess(QProcess(), 'svn', args) |
|
378 else: |
|
379 dia = SvnDialog( |
|
380 self.tr('Checking project out of Subversion repository')) |
|
381 res = dia.startProcess(args) |
|
382 if res: |
|
383 dia.exec_() |
|
384 return dia.normalExit() |
|
385 |
|
386 def vcsExport(self, vcsDataDict, projectDir): |
|
387 """ |
|
388 Public method used to export a directory from the Subversion |
|
389 repository. |
|
390 |
|
391 @param vcsDataDict dictionary of data required for the checkout |
|
392 @param projectDir project directory to create (string) |
|
393 @return flag indicating an execution without errors (boolean) |
|
394 """ |
|
395 try: |
|
396 tag = vcsDataDict["tag"] |
|
397 except KeyError: |
|
398 tag = None |
|
399 vcsDir = self.svnNormalizeURL(vcsDataDict["url"]) |
|
400 if vcsDir.startswith('/') or vcsDir[1] == '|': |
|
401 vcsDir = 'file://{0}'.format(vcsDir) |
|
402 |
|
403 if self.otherData["standardLayout"]: |
|
404 if tag is None or tag == '': |
|
405 svnUrl = '{0}/trunk'.format(vcsDir) |
|
406 else: |
|
407 if not tag.startswith('tags') and \ |
|
408 not tag.startswith('branches'): |
|
409 tagType, ok = QInputDialog.getItem( |
|
410 None, |
|
411 self.tr("Subversion Export"), |
|
412 self.tr( |
|
413 "The tag must be a normal tag (tags) or" |
|
414 " a branch tag (branches)." |
|
415 " Please select from the list."), |
|
416 self.tagTypeList, |
|
417 0, False) |
|
418 if not ok: |
|
419 return False |
|
420 tag = '{0}/{1}'.format(tagType, tag) |
|
421 svnUrl = '{0}/{1}'.format(vcsDir, tag) |
|
422 else: |
|
423 svnUrl = vcsDir |
|
424 |
|
425 args = [] |
|
426 args.append('export') |
|
427 self.addArguments(args, self.options['global']) |
|
428 args.append("--force") |
|
429 args.append(self.__svnURL(svnUrl)) |
|
430 args.append(projectDir) |
|
431 |
|
432 dia = SvnDialog( |
|
433 self.tr('Exporting project from Subversion repository')) |
|
434 res = dia.startProcess(args) |
|
435 if res: |
|
436 dia.exec_() |
|
437 return dia.normalExit() |
|
438 |
|
439 def vcsCommit(self, name, message, noDialog=False): |
|
440 """ |
|
441 Public method used to make the change of a file/directory permanent |
|
442 in the Subversion repository. |
|
443 |
|
444 @param name file/directory name to be committed (string or list of |
|
445 strings) |
|
446 @param message message for this operation (string) |
|
447 @param noDialog flag indicating quiet operations |
|
448 """ |
|
449 msg = message |
|
450 |
|
451 if not noDialog and not msg: |
|
452 # call CommitDialog and get message from there |
|
453 if self.__commitDialog is None: |
|
454 from .SvnCommitDialog import SvnCommitDialog |
|
455 self.__commitDialog = SvnCommitDialog(self, self.__ui) |
|
456 self.__commitDialog.accepted.connect(self.__vcsCommit_Step2) |
|
457 self.__commitDialog.show() |
|
458 self.__commitDialog.raise_() |
|
459 self.__commitDialog.activateWindow() |
|
460 |
|
461 self.__commitData["name"] = name |
|
462 self.__commitData["msg"] = msg |
|
463 self.__commitData["noDialog"] = noDialog |
|
464 |
|
465 if noDialog: |
|
466 self.__vcsCommit_Step2() |
|
467 |
|
468 def __vcsCommit_Step2(self): |
|
469 """ |
|
470 Private slot performing the second step of the commit action. |
|
471 """ |
|
472 name = self.__commitData["name"] |
|
473 msg = self.__commitData["msg"] |
|
474 noDialog = self.__commitData["noDialog"] |
|
475 |
|
476 if not noDialog: |
|
477 # check, if there are unsaved changes, that should be committed |
|
478 if isinstance(name, list): |
|
479 nameList = name |
|
480 else: |
|
481 nameList = [name] |
|
482 ok = True |
|
483 for nam in nameList: |
|
484 # check for commit of the project |
|
485 if os.path.isdir(nam): |
|
486 project = e5App().getObject("Project") |
|
487 if nam == project.getProjectPath(): |
|
488 ok &= project.checkAllScriptsDirty( |
|
489 reportSyntaxErrors=True) and \ |
|
490 project.checkDirty() |
|
491 continue |
|
492 elif os.path.isfile(nam): |
|
493 editor = e5App().getObject("ViewManager")\ |
|
494 .getOpenEditor(nam) |
|
495 if editor: |
|
496 ok &= editor.checkDirty() |
|
497 if not ok: |
|
498 break |
|
499 |
|
500 if not ok: |
|
501 res = E5MessageBox.yesNo( |
|
502 self.__ui, |
|
503 self.tr("Commit Changes"), |
|
504 self.tr( |
|
505 """The commit affects files, that have unsaved""" |
|
506 """ changes. Shall the commit be continued?"""), |
|
507 icon=E5MessageBox.Warning) |
|
508 if not res: |
|
509 return |
|
510 |
|
511 if self.__commitDialog is not None: |
|
512 msg = self.__commitDialog.logMessage() |
|
513 if self.__commitDialog.hasChangelists(): |
|
514 changelists, keepChangelists = \ |
|
515 self.__commitDialog.changelistsData() |
|
516 else: |
|
517 changelists, keepChangelists = [], False |
|
518 self.__commitDialog.deleteLater() |
|
519 self.__commitDialog = None |
|
520 else: |
|
521 changelists, keepChangelists = [], False |
|
522 |
|
523 if not msg: |
|
524 msg = '***' |
|
525 |
|
526 args = [] |
|
527 args.append('commit') |
|
528 self.addArguments(args, self.options['global']) |
|
529 self.addArguments(args, self.options['commit']) |
|
530 if keepChangelists: |
|
531 args.append("--keep-changelists") |
|
532 for changelist in changelists: |
|
533 args.append("--changelist") |
|
534 args.append(changelist) |
|
535 args.append("-m") |
|
536 args.append(msg) |
|
537 if isinstance(name, list): |
|
538 dname, fnames = self.splitPathList(name) |
|
539 self.addArguments(args, fnames) |
|
540 else: |
|
541 dname, fname = self.splitPath(name) |
|
542 args.append(fname) |
|
543 |
|
544 if self.svnGetReposName(dname).startswith('http') or \ |
|
545 self.svnGetReposName(dname).startswith('svn'): |
|
546 noDialog = False |
|
547 |
|
548 if noDialog: |
|
549 self.startSynchronizedProcess(QProcess(), "svn", args, dname) |
|
550 else: |
|
551 dia = SvnDialog( |
|
552 self.tr('Commiting changes to Subversion repository')) |
|
553 res = dia.startProcess(args, dname) |
|
554 if res: |
|
555 dia.exec_() |
|
556 self.committed.emit() |
|
557 self.checkVCSStatus() |
|
558 |
|
559 def vcsUpdate(self, name, noDialog=False): |
|
560 """ |
|
561 Public method used to update a file/directory with the Subversion |
|
562 repository. |
|
563 |
|
564 @param name file/directory name to be updated (string or list of |
|
565 strings) |
|
566 @param noDialog flag indicating quiet operations (boolean) |
|
567 @return flag indicating, that the update contained an add |
|
568 or delete (boolean) |
|
569 """ |
|
570 args = [] |
|
571 args.append('update') |
|
572 self.addArguments(args, self.options['global']) |
|
573 self.addArguments(args, self.options['update']) |
|
574 if self.version >= (1, 5, 0): |
|
575 args.append('--accept') |
|
576 args.append('postpone') |
|
577 if isinstance(name, list): |
|
578 dname, fnames = self.splitPathList(name) |
|
579 self.addArguments(args, fnames) |
|
580 else: |
|
581 dname, fname = self.splitPath(name) |
|
582 args.append(fname) |
|
583 |
|
584 if noDialog: |
|
585 self.startSynchronizedProcess(QProcess(), "svn", args, dname) |
|
586 res = False |
|
587 else: |
|
588 dia = SvnDialog( |
|
589 self.tr('Synchronizing with the Subversion repository')) |
|
590 res = dia.startProcess(args, dname, True) |
|
591 if res: |
|
592 dia.exec_() |
|
593 res = dia.hasAddOrDelete() |
|
594 self.checkVCSStatus() |
|
595 return res |
|
596 |
|
597 def vcsAdd(self, name, isDir=False, noDialog=False): |
|
598 """ |
|
599 Public method used to add a file/directory to the Subversion |
|
600 repository. |
|
601 |
|
602 @param name file/directory name to be added (string) |
|
603 @param isDir flag indicating name is a directory (boolean) |
|
604 @param noDialog flag indicating quiet operations |
|
605 """ |
|
606 args = [] |
|
607 args.append('add') |
|
608 self.addArguments(args, self.options['global']) |
|
609 self.addArguments(args, self.options['add']) |
|
610 args.append('--non-recursive') |
|
611 if noDialog and '--force' not in args: |
|
612 args.append('--force') |
|
613 |
|
614 if isinstance(name, list): |
|
615 if isDir: |
|
616 dname, fname = os.path.split(name[0]) |
|
617 else: |
|
618 dname, fnames = self.splitPathList(name) |
|
619 else: |
|
620 if isDir: |
|
621 dname, fname = os.path.split(name) |
|
622 else: |
|
623 dname, fname = self.splitPath(name) |
|
624 tree = [] |
|
625 wdir = dname |
|
626 if self.__wcng: |
|
627 repodir = dname |
|
628 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
629 repodir = os.path.dirname(repodir) |
|
630 if os.path.splitdrive(repodir)[1] == os.sep: |
|
631 return # oops, project is not version controlled |
|
632 while os.path.normcase(dname) != os.path.normcase(repodir) and \ |
|
633 (os.path.normcase(dname) not in self.statusCache or |
|
634 self.statusCache[os.path.normcase(dname)] == |
|
635 self.canBeAdded): |
|
636 # add directories recursively, if they aren't in the |
|
637 # repository already |
|
638 tree.insert(-1, dname) |
|
639 dname = os.path.dirname(dname) |
|
640 wdir = dname |
|
641 else: |
|
642 while not os.path.exists(os.path.join(dname, self.adminDir)): |
|
643 # add directories recursively, if they aren't in the |
|
644 # repository already |
|
645 tree.insert(-1, dname) |
|
646 dname = os.path.dirname(dname) |
|
647 wdir = dname |
|
648 self.addArguments(args, tree) |
|
649 |
|
650 if isinstance(name, list): |
|
651 tree2 = [] |
|
652 for n in name: |
|
653 d = os.path.dirname(n) |
|
654 if self.__wcng: |
|
655 repodir = d |
|
656 while not os.path.isdir( |
|
657 os.path.join(repodir, self.adminDir)): |
|
658 repodir = os.path.dirname(repodir) |
|
659 if os.path.splitdrive(repodir)[1] == os.sep: |
|
660 return # oops, project is not version controlled |
|
661 while os.path.normcase(d) != \ |
|
662 os.path.normcase(repodir) and \ |
|
663 (d not in tree2 + tree) and \ |
|
664 (os.path.normcase(d) not in self.statusCache or |
|
665 self.statusCache[os.path.normcase(d)] == |
|
666 self.canBeAdded): |
|
667 tree2.append(d) |
|
668 d = os.path.dirname(d) |
|
669 else: |
|
670 while not os.path.exists(os.path.join(d, self.adminDir)): |
|
671 if d in tree2 + tree: |
|
672 break |
|
673 tree2.append(d) |
|
674 d = os.path.dirname(d) |
|
675 tree2.reverse() |
|
676 self.addArguments(args, tree2) |
|
677 self.addArguments(args, name) |
|
678 else: |
|
679 args.append(name) |
|
680 |
|
681 if noDialog: |
|
682 self.startSynchronizedProcess(QProcess(), "svn", args, wdir) |
|
683 else: |
|
684 dia = SvnDialog( |
|
685 self.tr('Adding files/directories to the Subversion' |
|
686 ' repository')) |
|
687 res = dia.startProcess(args, wdir) |
|
688 if res: |
|
689 dia.exec_() |
|
690 |
|
691 def vcsAddBinary(self, name, isDir=False): |
|
692 """ |
|
693 Public method used to add a file/directory in binary mode to the |
|
694 Subversion repository. |
|
695 |
|
696 @param name file/directory name to be added (string) |
|
697 @param isDir flag indicating name is a directory (boolean) |
|
698 """ |
|
699 self.vcsAdd(name, isDir) |
|
700 |
|
701 def vcsAddTree(self, path): |
|
702 """ |
|
703 Public method to add a directory tree rooted at path to the Subversion |
|
704 repository. |
|
705 |
|
706 @param path root directory of the tree to be added (string or list of |
|
707 strings)) |
|
708 """ |
|
709 args = [] |
|
710 args.append('add') |
|
711 self.addArguments(args, self.options['global']) |
|
712 self.addArguments(args, self.options['add']) |
|
713 |
|
714 tree = [] |
|
715 if isinstance(path, list): |
|
716 dname, fnames = self.splitPathList(path) |
|
717 for n in path: |
|
718 d = os.path.dirname(n) |
|
719 if self.__wcng: |
|
720 repodir = d |
|
721 while not os.path.isdir( |
|
722 os.path.join(repodir, self.adminDir)): |
|
723 repodir = os.path.dirname(repodir) |
|
724 if os.path.splitdrive(repodir)[1] == os.sep: |
|
725 return # oops, project is not version controlled |
|
726 while os.path.normcase(d) != \ |
|
727 os.path.normcase(repodir) and \ |
|
728 (d not in tree) and \ |
|
729 (os.path.normcase(d) not in self.statusCache or |
|
730 self.statusCache[os.path.normcase(d)] == |
|
731 self.canBeAdded): |
|
732 tree.append(d) |
|
733 d = os.path.dirname(d) |
|
734 else: |
|
735 while not os.path.exists(os.path.join(d, self.adminDir)): |
|
736 # add directories recursively, |
|
737 # if they aren't in the repository already |
|
738 if d in tree: |
|
739 break |
|
740 tree.append(d) |
|
741 d = os.path.dirname(d) |
|
742 tree.reverse() |
|
743 else: |
|
744 dname, fname = os.path.split(path) |
|
745 if self.__wcng: |
|
746 repodir = dname |
|
747 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
748 repodir = os.path.dirname(repodir) |
|
749 if os.path.splitdrive(repodir)[1] == os.sep: |
|
750 return # oops, project is not version controlled |
|
751 while os.path.normcase(dname) != \ |
|
752 os.path.normcase(repodir) and \ |
|
753 (os.path.normcase(dname) not in self.statusCache or |
|
754 self.statusCache[os.path.normcase(dname)] == |
|
755 self.canBeAdded): |
|
756 # add directories recursively, if they aren't in the |
|
757 # repository already |
|
758 tree.insert(-1, dname) |
|
759 dname = os.path.dirname(dname) |
|
760 else: |
|
761 while not os.path.exists(os.path.join(dname, self.adminDir)): |
|
762 # add directories recursively, |
|
763 # if they aren't in the repository already |
|
764 tree.insert(-1, dname) |
|
765 dname = os.path.dirname(dname) |
|
766 if tree: |
|
767 self.vcsAdd(tree, True) |
|
768 |
|
769 if isinstance(path, list): |
|
770 self.addArguments(args, path) |
|
771 else: |
|
772 args.append(path) |
|
773 |
|
774 dia = SvnDialog( |
|
775 self.tr('Adding directory trees to the Subversion repository')) |
|
776 res = dia.startProcess(args, dname) |
|
777 if res: |
|
778 dia.exec_() |
|
779 |
|
780 def vcsRemove(self, name, project=False, noDialog=False): |
|
781 """ |
|
782 Public method used to remove a file/directory from the Subversion |
|
783 repository. |
|
784 |
|
785 The default operation is to remove the local copy as well. |
|
786 |
|
787 @param name file/directory name to be removed (string or list of |
|
788 strings)) |
|
789 @param project flag indicating deletion of a project tree (boolean) |
|
790 (not needed) |
|
791 @param noDialog flag indicating quiet operations |
|
792 @return flag indicating successfull operation (boolean) |
|
793 """ |
|
794 args = [] |
|
795 args.append('delete') |
|
796 self.addArguments(args, self.options['global']) |
|
797 self.addArguments(args, self.options['remove']) |
|
798 if noDialog and '--force' not in args: |
|
799 args.append('--force') |
|
800 |
|
801 if isinstance(name, list): |
|
802 self.addArguments(args, name) |
|
803 else: |
|
804 args.append(name) |
|
805 |
|
806 if noDialog: |
|
807 res = self.startSynchronizedProcess(QProcess(), "svn", args) |
|
808 else: |
|
809 dia = SvnDialog( |
|
810 self.tr('Removing files/directories from the Subversion' |
|
811 ' repository')) |
|
812 res = dia.startProcess(args) |
|
813 if res: |
|
814 dia.exec_() |
|
815 res = dia.normalExit() |
|
816 |
|
817 return res |
|
818 |
|
819 def vcsMove(self, name, project, target=None, noDialog=False): |
|
820 """ |
|
821 Public method used to move a file/directory. |
|
822 |
|
823 @param name file/directory name to be moved (string) |
|
824 @param project reference to the project object |
|
825 @param target new name of the file/directory (string) |
|
826 @param noDialog flag indicating quiet operations |
|
827 @return flag indicating successfull operation (boolean) |
|
828 """ |
|
829 rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+') |
|
830 opts = self.options['global'][:] |
|
831 force = '--force' in opts |
|
832 if force: |
|
833 del opts[opts.index('--force')] |
|
834 |
|
835 res = False |
|
836 if noDialog: |
|
837 if target is None: |
|
838 return False |
|
839 force = True |
|
840 accepted = True |
|
841 else: |
|
842 from .SvnCopyDialog import SvnCopyDialog |
|
843 dlg = SvnCopyDialog(name, None, True, force) |
|
844 accepted = (dlg.exec_() == QDialog.Accepted) |
|
845 if accepted: |
|
846 target, force = dlg.getData() |
|
847 if not target: |
|
848 return False |
|
849 |
|
850 if not rx_prot.exactMatch(target): |
|
851 isDir = os.path.isdir(name) |
|
852 else: |
|
853 isDir = False |
|
854 |
|
855 if accepted: |
|
856 args = [] |
|
857 args.append('move') |
|
858 self.addArguments(args, opts) |
|
859 if force: |
|
860 args.append('--force') |
|
861 if rx_prot.exactMatch(target): |
|
862 args.append('--message') |
|
863 args.append('Moving {0} to {1}'.format(name, target)) |
|
864 target = self.__svnURL(target) |
|
865 args.append(name) |
|
866 args.append(target) |
|
867 |
|
868 if noDialog: |
|
869 res = self.startSynchronizedProcess(QProcess(), "svn", args) |
|
870 else: |
|
871 dia = SvnDialog(self.tr('Moving {0}') |
|
872 .format(name)) |
|
873 res = dia.startProcess(args) |
|
874 if res: |
|
875 dia.exec_() |
|
876 res = dia.normalExit() |
|
877 if res and not rx_prot.exactMatch(target): |
|
878 if target.startswith(project.getProjectPath()): |
|
879 if isDir: |
|
880 project.moveDirectory(name, target) |
|
881 else: |
|
882 project.renameFileInPdata(name, target) |
|
883 else: |
|
884 if isDir: |
|
885 project.removeDirectory(name) |
|
886 else: |
|
887 project.removeFile(name) |
|
888 return res |
|
889 |
|
890 def vcsDiff(self, name): |
|
891 """ |
|
892 Public method used to view the difference of a file/directory to the |
|
893 Subversion repository. |
|
894 |
|
895 If name is a directory and is the project directory, all project files |
|
896 are saved first. If name is a file (or list of files), which is/are |
|
897 being edited and has unsaved modification, they can be saved or the |
|
898 operation may be aborted. |
|
899 |
|
900 @param name file/directory name to be diffed (string) |
|
901 """ |
|
902 if isinstance(name, list): |
|
903 names = name[:] |
|
904 else: |
|
905 names = [name] |
|
906 for nam in names: |
|
907 if os.path.isfile(nam): |
|
908 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
|
909 if editor and not editor.checkDirty(): |
|
910 return |
|
911 else: |
|
912 project = e5App().getObject("Project") |
|
913 if nam == project.ppath and not project.saveAllScripts(): |
|
914 return |
|
915 if self.diff is None: |
|
916 from .SvnDiffDialog import SvnDiffDialog |
|
917 self.diff = SvnDiffDialog(self) |
|
918 self.diff.show() |
|
919 self.diff.raise_() |
|
920 QApplication.processEvents() |
|
921 self.diff.start(name, refreshable=True) |
|
922 |
|
923 def vcsStatus(self, name): |
|
924 """ |
|
925 Public method used to view the status of files/directories in the |
|
926 Subversion repository. |
|
927 |
|
928 @param name file/directory name(s) to show the status of |
|
929 (string or list of strings) |
|
930 """ |
|
931 if self.status is None: |
|
932 from .SvnStatusDialog import SvnStatusDialog |
|
933 self.status = SvnStatusDialog(self) |
|
934 self.status.show() |
|
935 self.status.raise_() |
|
936 self.status.start(name) |
|
937 |
|
938 def vcsTag(self, name): |
|
939 """ |
|
940 Public method used to set the tag of a file/directory in the |
|
941 Subversion repository. |
|
942 |
|
943 @param name file/directory name to be tagged (string) |
|
944 """ |
|
945 dname, fname = self.splitPath(name) |
|
946 |
|
947 reposURL = self.svnGetReposName(dname) |
|
948 if reposURL is None: |
|
949 E5MessageBox.critical( |
|
950 self.__ui, |
|
951 self.tr("Subversion Error"), |
|
952 self.tr( |
|
953 """The URL of the project repository could not be""" |
|
954 """ retrieved from the working copy. The tag operation""" |
|
955 """ will be aborted""")) |
|
956 return |
|
957 |
|
958 if self.otherData["standardLayout"]: |
|
959 url = None |
|
960 else: |
|
961 url = self.svnNormalizeURL(reposURL) |
|
962 from .SvnTagDialog import SvnTagDialog |
|
963 dlg = SvnTagDialog(self.allTagsBranchesList, url, |
|
964 self.otherData["standardLayout"]) |
|
965 if dlg.exec_() == QDialog.Accepted: |
|
966 tag, tagOp = dlg.getParameters() |
|
967 if tag in self.allTagsBranchesList: |
|
968 self.allTagsBranchesList.remove(tag) |
|
969 self.allTagsBranchesList.insert(0, tag) |
|
970 else: |
|
971 return |
|
972 |
|
973 if self.otherData["standardLayout"]: |
|
974 rx_base = QRegExp('(.+)/(trunk|tags|branches).*') |
|
975 if not rx_base.exactMatch(reposURL): |
|
976 E5MessageBox.critical( |
|
977 self.__ui, |
|
978 self.tr("Subversion Error"), |
|
979 self.tr( |
|
980 """The URL of the project repository has an""" |
|
981 """ invalid format. The tag operation will""" |
|
982 """ be aborted""")) |
|
983 return |
|
984 |
|
985 reposRoot = rx_base.cap(1) |
|
986 if tagOp in [1, 4]: |
|
987 url = '{0}/tags/{1}'.format(reposRoot, Utilities.quote(tag)) |
|
988 elif tagOp in [2, 8]: |
|
989 url = '{0}/branches/{1}'.format( |
|
990 reposRoot, Utilities.quote(tag)) |
|
991 else: |
|
992 url = self.__svnURL(tag) |
|
993 |
|
994 args = [] |
|
995 if tagOp in [1, 2]: |
|
996 args.append('copy') |
|
997 self.addArguments(args, self.options['global']) |
|
998 self.addArguments(args, self.options['tag']) |
|
999 args.append('--message') |
|
1000 args.append('Created tag <{0}>'.format(tag)) |
|
1001 args.append(reposURL) |
|
1002 args.append(url) |
|
1003 else: |
|
1004 args.append('delete') |
|
1005 self.addArguments(args, self.options['global']) |
|
1006 self.addArguments(args, self.options['tag']) |
|
1007 args.append('--message') |
|
1008 args.append('Deleted tag <{0}>'.format(tag)) |
|
1009 args.append(url) |
|
1010 |
|
1011 dia = SvnDialog(self.tr('Tagging {0} in the Subversion repository') |
|
1012 .format(name)) |
|
1013 res = dia.startProcess(args) |
|
1014 if res: |
|
1015 dia.exec_() |
|
1016 |
|
1017 def vcsRevert(self, name): |
|
1018 """ |
|
1019 Public method used to revert changes made to a file/directory. |
|
1020 |
|
1021 @param name file/directory name to be reverted (string) |
|
1022 """ |
|
1023 args = [] |
|
1024 args.append('revert') |
|
1025 self.addArguments(args, self.options['global']) |
|
1026 if isinstance(name, list): |
|
1027 self.addArguments(args, name) |
|
1028 names = name[:] |
|
1029 else: |
|
1030 if os.path.isdir(name): |
|
1031 args.append('--recursive') |
|
1032 args.append(name) |
|
1033 names = [name] |
|
1034 |
|
1035 project = e5App().getObject("Project") |
|
1036 names = [project.getRelativePath(nam) for nam in names] |
|
1037 if names[0]: |
|
1038 from UI.DeleteFilesConfirmationDialog import \ |
|
1039 DeleteFilesConfirmationDialog |
|
1040 dlg = DeleteFilesConfirmationDialog( |
|
1041 self.parent(), |
|
1042 self.tr("Revert changes"), |
|
1043 self.tr("Do you really want to revert all changes to" |
|
1044 " these files or directories?"), |
|
1045 names) |
|
1046 yes = dlg.exec_() == QDialog.Accepted |
|
1047 else: |
|
1048 yes = E5MessageBox.yesNo( |
|
1049 None, |
|
1050 self.tr("Revert changes"), |
|
1051 self.tr("""Do you really want to revert all changes of""" |
|
1052 """ the project?""")) |
|
1053 if yes: |
|
1054 dia = SvnDialog(self.tr('Reverting changes')) |
|
1055 res = dia.startProcess(args) |
|
1056 if res: |
|
1057 dia.exec_() |
|
1058 self.checkVCSStatus() |
|
1059 |
|
1060 def vcsSwitch(self, name): |
|
1061 """ |
|
1062 Public method used to switch a directory to a different tag/branch. |
|
1063 |
|
1064 @param name directory name to be switched (string) |
|
1065 @return flag indicating added or changed files (boolean) |
|
1066 """ |
|
1067 dname, fname = self.splitPath(name) |
|
1068 |
|
1069 reposURL = self.svnGetReposName(dname) |
|
1070 if reposURL is None: |
|
1071 E5MessageBox.critical( |
|
1072 self.__ui, |
|
1073 self.tr("Subversion Error"), |
|
1074 self.tr( |
|
1075 """The URL of the project repository could not be""" |
|
1076 """ retrieved from the working copy. The switch""" |
|
1077 """ operation will be aborted""")) |
|
1078 return False |
|
1079 |
|
1080 if self.otherData["standardLayout"]: |
|
1081 url = None |
|
1082 else: |
|
1083 url = self.svnNormalizeURL(reposURL) |
|
1084 from .SvnSwitchDialog import SvnSwitchDialog |
|
1085 dlg = SvnSwitchDialog(self.allTagsBranchesList, url, |
|
1086 self.otherData["standardLayout"]) |
|
1087 if dlg.exec_() == QDialog.Accepted: |
|
1088 tag, tagType = dlg.getParameters() |
|
1089 if tag in self.allTagsBranchesList: |
|
1090 self.allTagsBranchesList.remove(tag) |
|
1091 self.allTagsBranchesList.insert(0, tag) |
|
1092 else: |
|
1093 return False |
|
1094 |
|
1095 if self.otherData["standardLayout"]: |
|
1096 rx_base = QRegExp('(.+)/(trunk|tags|branches).*') |
|
1097 if not rx_base.exactMatch(reposURL): |
|
1098 E5MessageBox.critical( |
|
1099 self.__ui, |
|
1100 self.tr("Subversion Error"), |
|
1101 self.tr( |
|
1102 """The URL of the project repository has an""" |
|
1103 """ invalid format. The switch operation will""" |
|
1104 """ be aborted""")) |
|
1105 return False |
|
1106 |
|
1107 reposRoot = rx_base.cap(1) |
|
1108 tn = tag |
|
1109 if tagType == 1: |
|
1110 url = '{0}/tags/{1}'.format(reposRoot, Utilities.quote(tag)) |
|
1111 elif tagType == 2: |
|
1112 url = '{0}/branches/{1}'.format( |
|
1113 reposRoot, Utilities.quote(tag)) |
|
1114 elif tagType == 4: |
|
1115 url = '{0}/trunk'.format(reposRoot) |
|
1116 tn = 'HEAD' |
|
1117 else: |
|
1118 url = self.__svnURL(tag) |
|
1119 tn = url |
|
1120 |
|
1121 args = [] |
|
1122 args.append('switch') |
|
1123 if self.version >= (1, 5, 0): |
|
1124 args.append('--accept') |
|
1125 args.append('postpone') |
|
1126 args.append(url) |
|
1127 args.append(name) |
|
1128 |
|
1129 dia = SvnDialog(self.tr('Switching to {0}') |
|
1130 .format(tn)) |
|
1131 res = dia.startProcess(args, setLanguage=True) |
|
1132 if res: |
|
1133 dia.exec_() |
|
1134 res = dia.hasAddOrDelete() |
|
1135 self.checkVCSStatus() |
|
1136 return res |
|
1137 |
|
1138 def vcsMerge(self, name): |
|
1139 """ |
|
1140 Public method used to merge a URL/revision into the local project. |
|
1141 |
|
1142 @param name file/directory name to be merged (string) |
|
1143 """ |
|
1144 dname, fname = self.splitPath(name) |
|
1145 |
|
1146 opts = self.options['global'][:] |
|
1147 force = '--force' in opts |
|
1148 if force: |
|
1149 del opts[opts.index('--force')] |
|
1150 |
|
1151 from .SvnMergeDialog import SvnMergeDialog |
|
1152 dlg = SvnMergeDialog( |
|
1153 self.mergeList[0], self.mergeList[1], self.mergeList[2], force) |
|
1154 if dlg.exec_() == QDialog.Accepted: |
|
1155 urlrev1, urlrev2, target, force = dlg.getParameters() |
|
1156 else: |
|
1157 return |
|
1158 |
|
1159 # remember URL or revision |
|
1160 if urlrev1 in self.mergeList[0]: |
|
1161 self.mergeList[0].remove(urlrev1) |
|
1162 self.mergeList[0].insert(0, urlrev1) |
|
1163 if urlrev2 in self.mergeList[1]: |
|
1164 self.mergeList[1].remove(urlrev2) |
|
1165 self.mergeList[1].insert(0, urlrev2) |
|
1166 |
|
1167 rx_rev = QRegExp('\\d+|HEAD') |
|
1168 |
|
1169 args = [] |
|
1170 args.append('merge') |
|
1171 self.addArguments(args, opts) |
|
1172 if self.version >= (1, 5, 0): |
|
1173 args.append('--accept') |
|
1174 args.append('postpone') |
|
1175 if force: |
|
1176 args.append('--force') |
|
1177 if rx_rev.exactMatch(urlrev1): |
|
1178 args.append('-r') |
|
1179 args.append('{0}:{1}'.format(urlrev1, urlrev2)) |
|
1180 if not target: |
|
1181 args.append(name) |
|
1182 else: |
|
1183 args.append(target) |
|
1184 |
|
1185 # remember target |
|
1186 if target in self.mergeList[2]: |
|
1187 self.mergeList[2].remove(target) |
|
1188 self.mergeList[2].insert(0, target) |
|
1189 else: |
|
1190 args.append(self.__svnURL(urlrev1)) |
|
1191 args.append(self.__svnURL(urlrev2)) |
|
1192 args.append(fname) |
|
1193 |
|
1194 dia = SvnDialog(self.tr('Merging {0}').format(name)) |
|
1195 res = dia.startProcess(args, dname) |
|
1196 if res: |
|
1197 dia.exec_() |
|
1198 |
|
1199 def vcsRegisteredState(self, name): |
|
1200 """ |
|
1201 Public method used to get the registered state of a file in the vcs. |
|
1202 |
|
1203 @param name filename to check (string) |
|
1204 @return a combination of canBeCommited and canBeAdded |
|
1205 """ |
|
1206 if self.__wcng: |
|
1207 return self.__vcsRegisteredState_wcng(name) |
|
1208 else: |
|
1209 return self.__vcsRegisteredState_wc(name) |
|
1210 |
|
1211 def __vcsRegisteredState_wcng(self, name): |
|
1212 """ |
|
1213 Private method used to get the registered state of a file in the vcs. |
|
1214 |
|
1215 This is the variant for subversion installations using the new |
|
1216 working copy meta-data format. |
|
1217 |
|
1218 @param name filename to check (string) |
|
1219 @return a combination of canBeCommited and canBeAdded |
|
1220 """ |
|
1221 if name.endswith(os.sep): |
|
1222 name = name[:-1] |
|
1223 name = os.path.normcase(name) |
|
1224 dname, fname = self.splitPath(name) |
|
1225 |
|
1226 if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)): |
|
1227 return self.canBeCommitted |
|
1228 |
|
1229 if name in self.statusCache: |
|
1230 return self.statusCache[name] |
|
1231 |
|
1232 name = os.path.normcase(name) |
|
1233 states = {name: 0} |
|
1234 states = self.vcsAllRegisteredStates(states, dname, False) |
|
1235 if states[name] == self.canBeCommitted: |
|
1236 return self.canBeCommitted |
|
1237 else: |
|
1238 return self.canBeAdded |
|
1239 |
|
1240 def __vcsRegisteredState_wc(self, name): |
|
1241 """ |
|
1242 Private method used to get the registered state of a file in the VCS. |
|
1243 |
|
1244 This is the variant for subversion installations using the old working |
|
1245 copy meta-data format. |
|
1246 |
|
1247 @param name filename to check (string) |
|
1248 @return a combination of canBeCommited and canBeAdded |
|
1249 """ |
|
1250 dname, fname = self.splitPath(name) |
|
1251 |
|
1252 if fname == '.': |
|
1253 if os.path.isdir(os.path.join(dname, self.adminDir)): |
|
1254 return self.canBeCommitted |
|
1255 else: |
|
1256 return self.canBeAdded |
|
1257 |
|
1258 name = os.path.normcase(name) |
|
1259 states = {name: 0} |
|
1260 states = self.vcsAllRegisteredStates(states, dname, False) |
|
1261 if states[name] == self.canBeCommitted: |
|
1262 return self.canBeCommitted |
|
1263 else: |
|
1264 return self.canBeAdded |
|
1265 |
|
1266 def vcsAllRegisteredStates(self, names, dname, shortcut=True): |
|
1267 """ |
|
1268 Public method used to get the registered states of a number of files |
|
1269 in the VCS. |
|
1270 |
|
1271 <b>Note:</b> If a shortcut is to be taken, the code will only check, |
|
1272 if the named directory has been scanned already. If so, it is assumed, |
|
1273 that the states for all files have been populated by the previous run. |
|
1274 |
|
1275 @param names dictionary with all filenames to be checked as keys |
|
1276 @param dname directory to check in (string) |
|
1277 @param shortcut flag indicating a shortcut should be taken (boolean) |
|
1278 @return the received dictionary completed with a combination of |
|
1279 canBeCommited and canBeAdded or None in order to signal an error |
|
1280 """ |
|
1281 if self.__wcng: |
|
1282 return self.__vcsAllRegisteredStates_wcng(names, dname, shortcut) |
|
1283 else: |
|
1284 return self.__vcsAllRegisteredStates_wc(names, dname, shortcut) |
|
1285 |
|
1286 def __vcsAllRegisteredStates_wcng(self, names, dname, shortcut=True): |
|
1287 """ |
|
1288 Private method used to get the registered states of a number of files |
|
1289 in the VCS. |
|
1290 |
|
1291 This is the variant for subversion installations using the new working |
|
1292 copy meta-data format. |
|
1293 |
|
1294 <b>Note:</b> If a shortcut is to be taken, the code will only check, |
|
1295 if the named directory has been scanned already. If so, it is assumed, |
|
1296 that the states for all files has been populated by the previous run. |
|
1297 |
|
1298 @param names dictionary with all filenames to be checked as keys |
|
1299 @param dname directory to check in (string) |
|
1300 @param shortcut flag indicating a shortcut should be taken (boolean) |
|
1301 @return the received dictionary completed with a combination of |
|
1302 canBeCommited and canBeAdded or None in order to signal an error |
|
1303 """ |
|
1304 if dname.endswith(os.sep): |
|
1305 dname = dname[:-1] |
|
1306 dname = os.path.normcase(dname) |
|
1307 |
|
1308 found = False |
|
1309 for name in self.statusCache.keys(): |
|
1310 if name in names: |
|
1311 found = True |
|
1312 names[name] = self.statusCache[name] |
|
1313 |
|
1314 if not found: |
|
1315 # find the root of the repo |
|
1316 repodir = dname |
|
1317 while not os.path.isdir(os.path.join(repodir, self.adminDir)): |
|
1318 repodir = os.path.dirname(repodir) |
|
1319 if os.path.splitdrive(repodir)[1] == os.sep: |
|
1320 return names |
|
1321 |
|
1322 ioEncoding = str(Preferences.getSystem("IOEncoding")) |
|
1323 process = QProcess() |
|
1324 args = [] |
|
1325 args.append('status') |
|
1326 args.append('--verbose') |
|
1327 args.append('--non-interactive') |
|
1328 args.append(dname) |
|
1329 process.start('svn', args) |
|
1330 procStarted = process.waitForStarted(5000) |
|
1331 if procStarted: |
|
1332 finished = process.waitForFinished(30000) |
|
1333 if finished and process.exitCode() == 0: |
|
1334 output = str(process.readAllStandardOutput(), ioEncoding, |
|
1335 'replace') |
|
1336 for line in output.splitlines(): |
|
1337 if self.rx_status1.exactMatch(line): |
|
1338 flags = str(self.rx_status1.cap(1)) |
|
1339 path = self.rx_status1.cap(5).strip() |
|
1340 elif self.rx_status2.exactMatch(line): |
|
1341 flags = str(self.rx_status2.cap(1)) |
|
1342 path = self.rx_status2.cap(2).strip() |
|
1343 else: |
|
1344 continue |
|
1345 name = os.path.normcase(path) |
|
1346 if flags[0] not in "?I": |
|
1347 if name in names: |
|
1348 names[name] = self.canBeCommitted |
|
1349 self.statusCache[name] = self.canBeCommitted |
|
1350 else: |
|
1351 self.statusCache[name] = self.canBeAdded |
|
1352 |
|
1353 return names |
|
1354 |
|
1355 def __vcsAllRegisteredStates_wc(self, names, dname, shortcut=True): |
|
1356 """ |
|
1357 Private method used to get the registered states of a number of files |
|
1358 in the VCS. |
|
1359 |
|
1360 This is the variant for subversion installations using the old working |
|
1361 copy meta-data format. |
|
1362 |
|
1363 <b>Note:</b> If a shortcut is to be taken, the code will only check, |
|
1364 if the named directory has been scanned already. If so, it is assumed, |
|
1365 that the states for all files has been populated by the previous run. |
|
1366 |
|
1367 @param names dictionary with all filenames to be checked as keys |
|
1368 @param dname directory to check in (string) |
|
1369 @param shortcut flag indicating a shortcut should be taken (boolean) |
|
1370 @return the received dictionary completed with a combination of |
|
1371 canBeCommited and canBeAdded or None in order to signal an error |
|
1372 """ |
|
1373 if not os.path.isdir(os.path.join(dname, self.adminDir)): |
|
1374 # not under version control -> do nothing |
|
1375 return names |
|
1376 |
|
1377 found = False |
|
1378 for name in list(self.statusCache.keys()): |
|
1379 if os.path.dirname(name) == dname: |
|
1380 if shortcut: |
|
1381 found = True |
|
1382 break |
|
1383 if name in names: |
|
1384 found = True |
|
1385 names[name] = self.statusCache[name] |
|
1386 |
|
1387 if not found: |
|
1388 ioEncoding = Preferences.getSystem("IOEncoding") |
|
1389 process = QProcess() |
|
1390 args = [] |
|
1391 args.append('status') |
|
1392 args.append('--verbose') |
|
1393 args.append('--non-interactive') |
|
1394 args.append(dname) |
|
1395 process.start('svn', args) |
|
1396 procStarted = process.waitForStarted(5000) |
|
1397 if procStarted: |
|
1398 finished = process.waitForFinished(30000) |
|
1399 if finished and process.exitCode() == 0: |
|
1400 output = str(process.readAllStandardOutput(), ioEncoding, |
|
1401 'replace') |
|
1402 for line in output.splitlines(): |
|
1403 if self.rx_status1.exactMatch(line): |
|
1404 flags = self.rx_status1.cap(1) |
|
1405 path = self.rx_status1.cap(5).strip() |
|
1406 elif self.rx_status2.exactMatch(line): |
|
1407 flags = self.rx_status2.cap(1) |
|
1408 path = self.rx_status2.cap(2).strip() |
|
1409 else: |
|
1410 continue |
|
1411 name = os.path.normcase(path) |
|
1412 if flags[0] not in "?I": |
|
1413 if name in names: |
|
1414 names[name] = self.canBeCommitted |
|
1415 self.statusCache[name] = self.canBeCommitted |
|
1416 else: |
|
1417 self.statusCache[name] = self.canBeAdded |
|
1418 |
|
1419 return names |
|
1420 |
|
1421 def clearStatusCache(self): |
|
1422 """ |
|
1423 Public method to clear the status cache. |
|
1424 """ |
|
1425 self.statusCache = {} |
|
1426 |
|
1427 def vcsInitConfig(self, project): |
|
1428 """ |
|
1429 Public method to initialize the VCS configuration. |
|
1430 |
|
1431 This method ensures, that an ignore file exists. |
|
1432 |
|
1433 @param project reference to the project (Project) |
|
1434 """ |
|
1435 configPath = getConfigPath() |
|
1436 if os.path.exists(configPath): |
|
1437 amendConfig() |
|
1438 else: |
|
1439 createDefaultConfig() |
|
1440 |
|
1441 def vcsName(self): |
|
1442 """ |
|
1443 Public method returning the name of the vcs. |
|
1444 |
|
1445 @return always 'Subversion' (string) |
|
1446 """ |
|
1447 return "Subversion" |
|
1448 |
|
1449 def vcsCleanup(self, name): |
|
1450 """ |
|
1451 Public method used to cleanup the working copy. |
|
1452 |
|
1453 @param name directory name to be cleaned up (string) |
|
1454 """ |
|
1455 args = [] |
|
1456 args.append('cleanup') |
|
1457 self.addArguments(args, self.options['global']) |
|
1458 args.append(name) |
|
1459 |
|
1460 dia = SvnDialog(self.tr('Cleaning up {0}') |
|
1461 .format(name)) |
|
1462 res = dia.startProcess(args) |
|
1463 if res: |
|
1464 dia.exec_() |
|
1465 |
|
1466 def vcsCommandLine(self, name): |
|
1467 """ |
|
1468 Public method used to execute arbitrary subversion commands. |
|
1469 |
|
1470 @param name directory name of the working directory (string) |
|
1471 """ |
|
1472 from .SvnCommandDialog import SvnCommandDialog |
|
1473 dlg = SvnCommandDialog(self.commandHistory, self.wdHistory, name) |
|
1474 if dlg.exec_() == QDialog.Accepted: |
|
1475 command, wd = dlg.getData() |
|
1476 commandList = Utilities.parseOptionString(command) |
|
1477 |
|
1478 # This moves any previous occurrence of these arguments to the head |
|
1479 # of the list. |
|
1480 if command in self.commandHistory: |
|
1481 self.commandHistory.remove(command) |
|
1482 self.commandHistory.insert(0, command) |
|
1483 if wd in self.wdHistory: |
|
1484 self.wdHistory.remove(wd) |
|
1485 self.wdHistory.insert(0, wd) |
|
1486 |
|
1487 args = [] |
|
1488 self.addArguments(args, commandList) |
|
1489 |
|
1490 dia = SvnDialog(self.tr('Subversion command')) |
|
1491 res = dia.startProcess(args, wd) |
|
1492 if res: |
|
1493 dia.exec_() |
|
1494 |
|
1495 def vcsOptionsDialog(self, project, archive, editable=False, parent=None): |
|
1496 """ |
|
1497 Public method to get a dialog to enter repository info. |
|
1498 |
|
1499 @param project reference to the project object |
|
1500 @param archive name of the project in the repository (string) |
|
1501 @param editable flag indicating that the project name is editable |
|
1502 (boolean) |
|
1503 @param parent parent widget (QWidget) |
|
1504 @return reference to the instantiated options dialog (SvnOptionsDialog) |
|
1505 """ |
|
1506 from .SvnOptionsDialog import SvnOptionsDialog |
|
1507 return SvnOptionsDialog(self, project, parent) |
|
1508 |
|
1509 def vcsNewProjectOptionsDialog(self, parent=None): |
|
1510 """ |
|
1511 Public method to get a dialog to enter repository info for getting |
|
1512 a new project. |
|
1513 |
|
1514 @param parent parent widget (QWidget) |
|
1515 @return reference to the instantiated options dialog |
|
1516 (SvnNewProjectOptionsDialog) |
|
1517 """ |
|
1518 from .SvnNewProjectOptionsDialog import SvnNewProjectOptionsDialog |
|
1519 return SvnNewProjectOptionsDialog(self, parent) |
|
1520 |
|
1521 def vcsRepositoryInfos(self, ppath): |
|
1522 """ |
|
1523 Public method to retrieve information about the repository. |
|
1524 |
|
1525 @param ppath local path to get the repository infos (string) |
|
1526 @return string with ready formated info for display (string) |
|
1527 """ |
|
1528 info = { |
|
1529 'committed-rev': '', |
|
1530 'committed-date': '', |
|
1531 'committed-time': '', |
|
1532 'url': '', |
|
1533 'last-author': '', |
|
1534 'revision': '' |
|
1535 } |
|
1536 |
|
1537 ioEncoding = Preferences.getSystem("IOEncoding") |
|
1538 |
|
1539 process = QProcess() |
|
1540 args = [] |
|
1541 args.append('info') |
|
1542 args.append('--non-interactive') |
|
1543 args.append('--xml') |
|
1544 args.append(ppath) |
|
1545 process.start('svn', args) |
|
1546 procStarted = process.waitForStarted(5000) |
|
1547 if procStarted: |
|
1548 finished = process.waitForFinished(30000) |
|
1549 if finished and process.exitCode() == 0: |
|
1550 output = str(process.readAllStandardOutput(), ioEncoding, |
|
1551 'replace') |
|
1552 entryFound = False |
|
1553 commitFound = False |
|
1554 for line in output.splitlines(): |
|
1555 line = line.strip() |
|
1556 if line.startswith('<entry'): |
|
1557 entryFound = True |
|
1558 elif line.startswith('<commit'): |
|
1559 commitFound = True |
|
1560 elif line.startswith('</commit>'): |
|
1561 commitFound = False |
|
1562 elif line.startswith("revision="): |
|
1563 rev = line[line.find('"') + 1:line.rfind('"')] |
|
1564 if entryFound: |
|
1565 info['revision'] = rev |
|
1566 entryFound = False |
|
1567 elif commitFound: |
|
1568 info['committed-rev'] = rev |
|
1569 elif line.startswith('<url>'): |
|
1570 info['url'] = \ |
|
1571 line.replace('<url>', '').replace('</url>', '') |
|
1572 elif line.startswith('<author>'): |
|
1573 info['last-author'] = line.replace('<author>', '')\ |
|
1574 .replace('</author>', '') |
|
1575 elif line.startswith('<date>'): |
|
1576 value = line.replace('<date>', '')\ |
|
1577 .replace('</date>', '') |
|
1578 date, time = value.split('T') |
|
1579 info['committed-date'] = date |
|
1580 info['committed-time'] = "{0}{1}".format( |
|
1581 time.split('.')[0], time[-1]) |
|
1582 |
|
1583 return QCoreApplication.translate( |
|
1584 'subversion', |
|
1585 """<h3>Repository information</h3>""" |
|
1586 """<table>""" |
|
1587 """<tr><td><b>Subversion V.</b></td><td>{0}</td></tr>""" |
|
1588 """<tr><td><b>URL</b></td><td>{1}</td></tr>""" |
|
1589 """<tr><td><b>Current revision</b></td><td>{2}</td></tr>""" |
|
1590 """<tr><td><b>Committed revision</b></td><td>{3}</td></tr>""" |
|
1591 """<tr><td><b>Committed date</b></td><td>{4}</td></tr>""" |
|
1592 """<tr><td><b>Comitted time</b></td><td>{5}</td></tr>""" |
|
1593 """<tr><td><b>Last author</b></td><td>{6}</td></tr>""" |
|
1594 """</table>""" |
|
1595 ).format(self.versionStr, |
|
1596 info['url'], |
|
1597 info['revision'], |
|
1598 info['committed-rev'], |
|
1599 info['committed-date'], |
|
1600 info['committed-time'], |
|
1601 info['last-author']) |
|
1602 |
|
1603 ########################################################################### |
|
1604 ## Public Subversion specific methods are below. |
|
1605 ########################################################################### |
|
1606 |
|
1607 def svnGetReposName(self, path): |
|
1608 """ |
|
1609 Public method used to retrieve the URL of the subversion repository |
|
1610 path. |
|
1611 |
|
1612 @param path local path to get the svn repository path for (string) |
|
1613 @return string with the repository path URL |
|
1614 """ |
|
1615 ioEncoding = Preferences.getSystem("IOEncoding") |
|
1616 |
|
1617 process = QProcess() |
|
1618 args = [] |
|
1619 args.append('info') |
|
1620 args.append('--xml') |
|
1621 args.append('--non-interactive') |
|
1622 args.append(path) |
|
1623 process.start('svn', args) |
|
1624 procStarted = process.waitForStarted(5000) |
|
1625 if procStarted: |
|
1626 finished = process.waitForFinished(30000) |
|
1627 if finished and process.exitCode() == 0: |
|
1628 output = str(process.readAllStandardOutput(), ioEncoding, |
|
1629 'replace') |
|
1630 for line in output.splitlines(): |
|
1631 line = line.strip() |
|
1632 if line.startswith('<url>'): |
|
1633 reposURL = line.replace('<url>', '')\ |
|
1634 .replace('</url>', '') |
|
1635 return reposURL |
|
1636 |
|
1637 return "" |
|
1638 |
|
1639 def svnResolve(self, name): |
|
1640 """ |
|
1641 Public method used to resolve conflicts of a file/directory. |
|
1642 |
|
1643 @param name file/directory name to be resolved (string) |
|
1644 """ |
|
1645 args = [] |
|
1646 if self.version >= (1, 5, 0): |
|
1647 args.append('resolve') |
|
1648 args.append('--accept') |
|
1649 args.append('working') |
|
1650 else: |
|
1651 args.append('resolved') |
|
1652 self.addArguments(args, self.options['global']) |
|
1653 if isinstance(name, list): |
|
1654 self.addArguments(args, name) |
|
1655 else: |
|
1656 if os.path.isdir(name): |
|
1657 args.append('--recursive') |
|
1658 args.append(name) |
|
1659 |
|
1660 dia = SvnDialog(self.tr('Resolving conficts')) |
|
1661 res = dia.startProcess(args) |
|
1662 if res: |
|
1663 dia.exec_() |
|
1664 self.checkVCSStatus() |
|
1665 |
|
1666 def svnCopy(self, name, project): |
|
1667 """ |
|
1668 Public method used to copy a file/directory. |
|
1669 |
|
1670 @param name file/directory name to be copied (string) |
|
1671 @param project reference to the project object |
|
1672 @return flag indicating successfull operation (boolean) |
|
1673 """ |
|
1674 from .SvnCopyDialog import SvnCopyDialog |
|
1675 rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+') |
|
1676 dlg = SvnCopyDialog(name) |
|
1677 res = False |
|
1678 if dlg.exec_() == QDialog.Accepted: |
|
1679 target, force = dlg.getData() |
|
1680 |
|
1681 args = [] |
|
1682 args.append('copy') |
|
1683 self.addArguments(args, self.options['global']) |
|
1684 if rx_prot.exactMatch(target): |
|
1685 args.append('--message') |
|
1686 args.append('Copying {0} to {1}'.format(name, target)) |
|
1687 target = self.__svnURL(target) |
|
1688 args.append(name) |
|
1689 args.append(target) |
|
1690 |
|
1691 dia = SvnDialog(self.tr('Copying {0}') |
|
1692 .format(name)) |
|
1693 res = dia.startProcess(args) |
|
1694 if res: |
|
1695 dia.exec_() |
|
1696 res = dia.normalExit() |
|
1697 if res and \ |
|
1698 not rx_prot.exactMatch(target) and \ |
|
1699 target.startswith(project.getProjectPath()): |
|
1700 if os.path.isdir(name): |
|
1701 project.copyDirectory(name, target) |
|
1702 else: |
|
1703 project.appendFile(target) |
|
1704 return res |
|
1705 |
|
1706 def svnListProps(self, name, recursive=False): |
|
1707 """ |
|
1708 Public method used to list the properties of a file/directory. |
|
1709 |
|
1710 @param name file/directory name (string or list of strings) |
|
1711 @param recursive flag indicating a recursive list is requested |
|
1712 """ |
|
1713 if self.propList is None: |
|
1714 from .SvnPropListDialog import SvnPropListDialog |
|
1715 self.propList = SvnPropListDialog(self) |
|
1716 self.propList.show() |
|
1717 self.propList.raise_() |
|
1718 self.propList.start(name, recursive) |
|
1719 |
|
1720 def svnSetProp(self, name, recursive=False): |
|
1721 """ |
|
1722 Public method used to add a property to a file/directory. |
|
1723 |
|
1724 @param name file/directory name (string or list of strings) |
|
1725 @param recursive flag indicating a recursive list is requested |
|
1726 """ |
|
1727 from .SvnPropSetDialog import SvnPropSetDialog |
|
1728 dlg = SvnPropSetDialog() |
|
1729 if dlg.exec_() == QDialog.Accepted: |
|
1730 propName, fileFlag, propValue = dlg.getData() |
|
1731 if not propName: |
|
1732 E5MessageBox.critical( |
|
1733 self.__ui, |
|
1734 self.tr("Subversion Set Property"), |
|
1735 self.tr("""You have to supply a property name.""" |
|
1736 """ Aborting.""")) |
|
1737 return |
|
1738 |
|
1739 args = [] |
|
1740 args.append('propset') |
|
1741 self.addArguments(args, self.options['global']) |
|
1742 if recursive: |
|
1743 args.append('--recursive') |
|
1744 args.append(propName) |
|
1745 if fileFlag: |
|
1746 args.append('--file') |
|
1747 args.append(propValue) |
|
1748 if isinstance(name, list): |
|
1749 dname, fnames = self.splitPathList(name) |
|
1750 self.addArguments(args, fnames) |
|
1751 else: |
|
1752 dname, fname = self.splitPath(name) |
|
1753 args.append(fname) |
|
1754 |
|
1755 dia = SvnDialog(self.tr('Subversion Set Property')) |
|
1756 res = dia.startProcess(args, dname) |
|
1757 if res: |
|
1758 dia.exec_() |
|
1759 |
|
1760 def svnDelProp(self, name, recursive=False): |
|
1761 """ |
|
1762 Public method used to delete a property of a file/directory. |
|
1763 |
|
1764 @param name file/directory name (string or list of strings) |
|
1765 @param recursive flag indicating a recursive list is requested |
|
1766 """ |
|
1767 propName, ok = QInputDialog.getText( |
|
1768 None, |
|
1769 self.tr("Subversion Delete Property"), |
|
1770 self.tr("Enter property name"), |
|
1771 QLineEdit.Normal) |
|
1772 |
|
1773 if not ok: |
|
1774 return |
|
1775 |
|
1776 if not propName: |
|
1777 E5MessageBox.critical( |
|
1778 self.__ui, |
|
1779 self.tr("Subversion Delete Property"), |
|
1780 self.tr("""You have to supply a property name.""" |
|
1781 """ Aborting.""")) |
|
1782 return |
|
1783 |
|
1784 args = [] |
|
1785 args.append('propdel') |
|
1786 self.addArguments(args, self.options['global']) |
|
1787 if recursive: |
|
1788 args.append('--recursive') |
|
1789 args.append(propName) |
|
1790 if isinstance(name, list): |
|
1791 dname, fnames = self.splitPathList(name) |
|
1792 self.addArguments(args, fnames) |
|
1793 else: |
|
1794 dname, fname = self.splitPath(name) |
|
1795 args.append(fname) |
|
1796 |
|
1797 dia = SvnDialog(self.tr('Subversion Delete Property')) |
|
1798 res = dia.startProcess(args, dname) |
|
1799 if res: |
|
1800 dia.exec_() |
|
1801 |
|
1802 def svnListTagBranch(self, path, tags=True): |
|
1803 """ |
|
1804 Public method used to list the available tags or branches. |
|
1805 |
|
1806 @param path directory name of the project (string) |
|
1807 @param tags flag indicating listing of branches or tags |
|
1808 (False = branches, True = tags) |
|
1809 """ |
|
1810 if self.tagbranchList is None: |
|
1811 from .SvnTagBranchListDialog import SvnTagBranchListDialog |
|
1812 self.tagbranchList = SvnTagBranchListDialog(self) |
|
1813 self.tagbranchList.show() |
|
1814 self.tagbranchList.raise_() |
|
1815 if tags: |
|
1816 if not self.showedTags: |
|
1817 self.showedTags = True |
|
1818 allTagsBranchesList = self.allTagsBranchesList |
|
1819 else: |
|
1820 self.tagsList = [] |
|
1821 allTagsBranchesList = None |
|
1822 self.tagbranchList.start(path, tags, |
|
1823 self.tagsList, allTagsBranchesList) |
|
1824 elif not tags: |
|
1825 if not self.showedBranches: |
|
1826 self.showedBranches = True |
|
1827 allTagsBranchesList = self.allTagsBranchesList |
|
1828 else: |
|
1829 self.branchesList = [] |
|
1830 allTagsBranchesList = None |
|
1831 self.tagbranchList.start( |
|
1832 path, tags, self.branchesList, self.allTagsBranchesList) |
|
1833 |
|
1834 def svnBlame(self, name): |
|
1835 """ |
|
1836 Public method to show the output of the svn blame command. |
|
1837 |
|
1838 @param name file name to show the blame for (string) |
|
1839 """ |
|
1840 if self.blame is None: |
|
1841 from .SvnBlameDialog import SvnBlameDialog |
|
1842 self.blame = SvnBlameDialog(self) |
|
1843 self.blame.show() |
|
1844 self.blame.raise_() |
|
1845 self.blame.start(name) |
|
1846 |
|
1847 def svnExtendedDiff(self, name): |
|
1848 """ |
|
1849 Public method used to view the difference of a file/directory to the |
|
1850 Subversion repository. |
|
1851 |
|
1852 If name is a directory and is the project directory, all project files |
|
1853 are saved first. If name is a file (or list of files), which is/are |
|
1854 being edited and has unsaved modification, they can be saved or the |
|
1855 operation may be aborted. |
|
1856 |
|
1857 This method gives the chance to enter the revisions to be compared. |
|
1858 |
|
1859 @param name file/directory name to be diffed (string) |
|
1860 """ |
|
1861 if isinstance(name, list): |
|
1862 names = name[:] |
|
1863 else: |
|
1864 names = [name] |
|
1865 for nam in names: |
|
1866 if os.path.isfile(nam): |
|
1867 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
|
1868 if editor and not editor.checkDirty(): |
|
1869 return |
|
1870 else: |
|
1871 project = e5App().getObject("Project") |
|
1872 if nam == project.ppath and not project.saveAllScripts(): |
|
1873 return |
|
1874 from .SvnRevisionSelectionDialog import SvnRevisionSelectionDialog |
|
1875 dlg = SvnRevisionSelectionDialog() |
|
1876 if dlg.exec_() == QDialog.Accepted: |
|
1877 revisions = dlg.getRevisions() |
|
1878 from .SvnDiffDialog import SvnDiffDialog |
|
1879 self.diff = SvnDiffDialog(self) |
|
1880 self.diff.show() |
|
1881 self.diff.start(name, revisions) |
|
1882 |
|
1883 def svnUrlDiff(self, name): |
|
1884 """ |
|
1885 Public method used to view the difference of a file/directory of two |
|
1886 repository URLs. |
|
1887 |
|
1888 If name is a directory and is the project directory, all project files |
|
1889 are saved first. If name is a file (or list of files), which is/are |
|
1890 being edited and has unsaved modification, they can be saved or the |
|
1891 operation may be aborted. |
|
1892 |
|
1893 This method gives the chance to enter the revisions to be compared. |
|
1894 |
|
1895 @param name file/directory name to be diffed (string) |
|
1896 """ |
|
1897 if isinstance(name, list): |
|
1898 names = name[:] |
|
1899 else: |
|
1900 names = [name] |
|
1901 for nam in names: |
|
1902 if os.path.isfile(nam): |
|
1903 editor = e5App().getObject("ViewManager").getOpenEditor(nam) |
|
1904 if editor and not editor.checkDirty(): |
|
1905 return |
|
1906 else: |
|
1907 project = e5App().getObject("Project") |
|
1908 if nam == project.ppath and not project.saveAllScripts(): |
|
1909 return |
|
1910 |
|
1911 dname = self.splitPath(names[0])[0] |
|
1912 |
|
1913 from .SvnUrlSelectionDialog import SvnUrlSelectionDialog |
|
1914 dlg = SvnUrlSelectionDialog(self, self.tagsList, self.branchesList, |
|
1915 dname) |
|
1916 if dlg.exec_() == QDialog.Accepted: |
|
1917 urls, summary = dlg.getURLs() |
|
1918 from .SvnDiffDialog import SvnDiffDialog |
|
1919 self.diff = SvnDiffDialog(self) |
|
1920 self.diff.show() |
|
1921 QApplication.processEvents() |
|
1922 self.diff.start(name, urls=urls, summary=summary) |
|
1923 |
|
1924 def __svnGetFileForRevision(self, name, rev=""): |
|
1925 """ |
|
1926 Private method to get a file for a specific revision from the |
|
1927 repository. |
|
1928 |
|
1929 @param name file name to get from the repository (string) |
|
1930 @keyparam rev revision to retrieve (integer or string) |
|
1931 @return contents of the file (string) and an error message (string) |
|
1932 """ |
|
1933 args = [] |
|
1934 args.append("cat") |
|
1935 if rev: |
|
1936 args.append("--revision") |
|
1937 args.append(str(rev)) |
|
1938 args.append(name) |
|
1939 |
|
1940 output = "" |
|
1941 error = "" |
|
1942 |
|
1943 process = QProcess() |
|
1944 process.start('svn', args) |
|
1945 procStarted = process.waitForStarted(5000) |
|
1946 if procStarted: |
|
1947 finished = process.waitForFinished(30000) |
|
1948 if finished: |
|
1949 if process.exitCode() == 0: |
|
1950 output = str( |
|
1951 process.readAllStandardOutput(), |
|
1952 Preferences.getSystem("IOEncoding"), 'replace') |
|
1953 else: |
|
1954 error = str( |
|
1955 process.readAllStandardError(), |
|
1956 Preferences.getSystem("IOEncoding"), 'replace') |
|
1957 else: |
|
1958 error = self.tr( |
|
1959 "The svn process did not finish within 30s.") |
|
1960 else: |
|
1961 error = self.tr( |
|
1962 'The process {0} could not be started. ' |
|
1963 'Ensure, that it is in the search path.').format('svn') |
|
1964 |
|
1965 return output, error |
|
1966 |
|
1967 def svnSbsDiff(self, name, extended=False, revisions=None): |
|
1968 """ |
|
1969 Public method used to view the difference of a file to the Mercurial |
|
1970 repository side-by-side. |
|
1971 |
|
1972 @param name file name to be diffed (string) |
|
1973 @keyparam extended flag indicating the extended variant (boolean) |
|
1974 @keyparam revisions tuple of two revisions (tuple of strings) |
|
1975 @exception ValueError raised to indicate an illegal name parameter type |
|
1976 """ |
|
1977 if isinstance(name, list): |
|
1978 raise ValueError("Wrong parameter type") |
|
1979 |
|
1980 if extended: |
|
1981 from .SvnRevisionSelectionDialog import SvnRevisionSelectionDialog |
|
1982 dlg = SvnRevisionSelectionDialog() |
|
1983 if dlg.exec_() == QDialog.Accepted: |
|
1984 rev1, rev2 = dlg.getRevisions() |
|
1985 if rev1 == "WORKING": |
|
1986 rev1 = "" |
|
1987 if rev2 == "WORKING": |
|
1988 rev2 = "" |
|
1989 else: |
|
1990 return |
|
1991 elif revisions: |
|
1992 rev1, rev2 = revisions[0], revisions[1] |
|
1993 else: |
|
1994 rev1, rev2 = "", "" |
|
1995 |
|
1996 output1, error = self.__svnGetFileForRevision(name, rev=rev1) |
|
1997 if error: |
|
1998 E5MessageBox.critical( |
|
1999 self.__ui, |
|
2000 self.tr("Subversion Side-by-Side Difference"), |
|
2001 error) |
|
2002 return |
|
2003 name1 = "{0} (rev. {1})".format(name, rev1 and rev1 or ".") |
|
2004 |
|
2005 if rev2: |
|
2006 output2, error = self.__svnGetFileForRevision(name, rev=rev2) |
|
2007 if error: |
|
2008 E5MessageBox.critical( |
|
2009 self.__ui, |
|
2010 self.tr("Subversion Side-by-Side Difference"), |
|
2011 error) |
|
2012 return |
|
2013 name2 = "{0} (rev. {1})".format(name, rev2) |
|
2014 else: |
|
2015 try: |
|
2016 f1 = open(name, "r", encoding="utf-8") |
|
2017 output2 = f1.read() |
|
2018 f1.close() |
|
2019 name2 = name |
|
2020 except IOError: |
|
2021 E5MessageBox.critical( |
|
2022 self.__ui, |
|
2023 self.tr("Subversion Side-by-Side Difference"), |
|
2024 self.tr( |
|
2025 """<p>The file <b>{0}</b> could not be read.</p>""") |
|
2026 .format(name)) |
|
2027 return |
|
2028 |
|
2029 if self.sbsDiff is None: |
|
2030 from UI.CompareDialog import CompareDialog |
|
2031 self.sbsDiff = CompareDialog() |
|
2032 self.sbsDiff.show() |
|
2033 self.sbsDiff.raise_() |
|
2034 self.sbsDiff.compare(output1, output2, name1, name2) |
|
2035 |
|
2036 def vcsLogBrowser(self, name, isFile=False): |
|
2037 """ |
|
2038 Public method used to browse the log of a file/directory from the |
|
2039 Subversion repository. |
|
2040 |
|
2041 @param name file/directory name to show the log of (string) |
|
2042 @param isFile flag indicating log for a file is to be shown (boolean) |
|
2043 """ |
|
2044 if self.logBrowser is None: |
|
2045 from .SvnLogBrowserDialog import SvnLogBrowserDialog |
|
2046 self.logBrowser = SvnLogBrowserDialog(self) |
|
2047 self.logBrowser.show() |
|
2048 self.logBrowser.raise_() |
|
2049 self.logBrowser.start(name, isFile=isFile) |
|
2050 |
|
2051 def svnLock(self, name, stealIt=False, parent=None): |
|
2052 """ |
|
2053 Public method used to lock a file in the Subversion repository. |
|
2054 |
|
2055 @param name file/directory name to be locked (string or list of |
|
2056 strings) |
|
2057 @param stealIt flag indicating a forced operation (boolean) |
|
2058 @param parent reference to the parent object of the subversion dialog |
|
2059 (QWidget) |
|
2060 """ |
|
2061 args = [] |
|
2062 args.append('lock') |
|
2063 self.addArguments(args, self.options['global']) |
|
2064 if stealIt: |
|
2065 args.append('--force') |
|
2066 if isinstance(name, list): |
|
2067 dname, fnames = self.splitPathList(name) |
|
2068 self.addArguments(args, fnames) |
|
2069 else: |
|
2070 dname, fname = self.splitPath(name) |
|
2071 args.append(fname) |
|
2072 |
|
2073 dia = SvnDialog( |
|
2074 self.tr('Locking in the Subversion repository'), parent) |
|
2075 res = dia.startProcess(args, dname) |
|
2076 if res: |
|
2077 dia.exec_() |
|
2078 |
|
2079 def svnUnlock(self, name, breakIt=False, parent=None): |
|
2080 """ |
|
2081 Public method used to unlock a file in the Subversion repository. |
|
2082 |
|
2083 @param name file/directory name to be unlocked (string or list of |
|
2084 strings) |
|
2085 @param breakIt flag indicating a forced operation (boolean) |
|
2086 @param parent reference to the parent object of the subversion dialog |
|
2087 (QWidget) |
|
2088 """ |
|
2089 args = [] |
|
2090 args.append('unlock') |
|
2091 self.addArguments(args, self.options['global']) |
|
2092 if breakIt: |
|
2093 args.append('--force') |
|
2094 if isinstance(name, list): |
|
2095 dname, fnames = self.splitPathList(name) |
|
2096 self.addArguments(args, fnames) |
|
2097 else: |
|
2098 dname, fname = self.splitPath(name) |
|
2099 args.append(fname) |
|
2100 |
|
2101 dia = SvnDialog( |
|
2102 self.tr('Unlocking in the Subversion repository'), parent) |
|
2103 res = dia.startProcess(args, dname) |
|
2104 if res: |
|
2105 dia.exec_() |
|
2106 |
|
2107 def svnRelocate(self, projectPath): |
|
2108 """ |
|
2109 Public method to relocate the working copy to a new repository URL. |
|
2110 |
|
2111 @param projectPath path name of the project (string) |
|
2112 """ |
|
2113 from .SvnRelocateDialog import SvnRelocateDialog |
|
2114 currUrl = self.svnGetReposName(projectPath) |
|
2115 dlg = SvnRelocateDialog(currUrl) |
|
2116 if dlg.exec_() == QDialog.Accepted: |
|
2117 newUrl, inside = dlg.getData() |
|
2118 args = [] |
|
2119 args.append('switch') |
|
2120 if not inside: |
|
2121 args.append('--relocate') |
|
2122 args.append(currUrl) |
|
2123 args.append(newUrl) |
|
2124 args.append(projectPath) |
|
2125 |
|
2126 dia = SvnDialog(self.tr('Relocating')) |
|
2127 res = dia.startProcess(args) |
|
2128 if res: |
|
2129 dia.exec_() |
|
2130 |
|
2131 def svnRepoBrowser(self, projectPath=None): |
|
2132 """ |
|
2133 Public method to open the repository browser. |
|
2134 |
|
2135 @param projectPath path name of the project (string) |
|
2136 """ |
|
2137 if projectPath: |
|
2138 url = self.svnGetReposName(projectPath) |
|
2139 else: |
|
2140 url = None |
|
2141 |
|
2142 if url is None: |
|
2143 url, ok = QInputDialog.getText( |
|
2144 None, |
|
2145 self.tr("Repository Browser"), |
|
2146 self.tr("Enter the repository URL."), |
|
2147 QLineEdit.Normal) |
|
2148 if not ok or not url: |
|
2149 return |
|
2150 |
|
2151 if self.repoBrowser is None: |
|
2152 from .SvnRepoBrowserDialog import SvnRepoBrowserDialog |
|
2153 self.repoBrowser = SvnRepoBrowserDialog(self) |
|
2154 self.repoBrowser.show() |
|
2155 self.repoBrowser.raise_() |
|
2156 self.repoBrowser.start(url) |
|
2157 |
|
2158 def svnRemoveFromChangelist(self, names): |
|
2159 """ |
|
2160 Public method to remove a file or directory from its changelist. |
|
2161 |
|
2162 Note: Directories will be removed recursively. |
|
2163 |
|
2164 @param names name or list of names of file or directory to remove |
|
2165 (string) |
|
2166 """ |
|
2167 args = [] |
|
2168 args.append('changelist') |
|
2169 self.addArguments(args, self.options['global']) |
|
2170 args.append('--remove') |
|
2171 args.append('--recursive') |
|
2172 if isinstance(names, list): |
|
2173 dname, fnames = self.splitPathList(names) |
|
2174 self.addArguments(args, fnames) |
|
2175 else: |
|
2176 dname, fname = self.splitPath(names) |
|
2177 args.append(fname) |
|
2178 |
|
2179 dia = SvnDialog(self.tr('Remove from changelist')) |
|
2180 res = dia.startProcess(args, dname) |
|
2181 if res: |
|
2182 dia.exec_() |
|
2183 |
|
2184 def svnAddToChangelist(self, names): |
|
2185 """ |
|
2186 Public method to add a file or directory to a changelist. |
|
2187 |
|
2188 Note: Directories will be added recursively. |
|
2189 |
|
2190 @param names name or list of names of file or directory to add |
|
2191 (string) |
|
2192 """ |
|
2193 clname, ok = QInputDialog.getItem( |
|
2194 None, |
|
2195 self.tr("Add to changelist"), |
|
2196 self.tr("Enter name of the changelist:"), |
|
2197 sorted(self.svnGetChangelists()), |
|
2198 0, True) |
|
2199 if not ok or not clname: |
|
2200 return |
|
2201 |
|
2202 args = [] |
|
2203 args.append('changelist') |
|
2204 self.addArguments(args, self.options['global']) |
|
2205 args.append('--recursive') |
|
2206 args.append(clname) |
|
2207 if isinstance(names, list): |
|
2208 dname, fnames = self.splitPathList(names) |
|
2209 self.addArguments(args, fnames) |
|
2210 else: |
|
2211 dname, fname = self.splitPath(names) |
|
2212 args.append(fname) |
|
2213 |
|
2214 dia = SvnDialog(self.tr('Remove from changelist')) |
|
2215 res = dia.startProcess(args, dname) |
|
2216 if res: |
|
2217 dia.exec_() |
|
2218 |
|
2219 def svnShowChangelists(self, path): |
|
2220 """ |
|
2221 Public method used to inspect the change lists defined for the project. |
|
2222 |
|
2223 @param path directory name to show change lists for (string) |
|
2224 """ |
|
2225 from .SvnChangeListsDialog import SvnChangeListsDialog |
|
2226 self.changeLists = SvnChangeListsDialog(self) |
|
2227 self.changeLists.show() |
|
2228 QApplication.processEvents() |
|
2229 self.changeLists.start(path) |
|
2230 |
|
2231 def svnGetChangelists(self): |
|
2232 """ |
|
2233 Public method to get a list of all defined change lists. |
|
2234 |
|
2235 @return list of defined change list names (list of strings) |
|
2236 """ |
|
2237 changelists = [] |
|
2238 rx_changelist = QRegExp('--- \\S+ .([\\w\\s]+).:\\s*') |
|
2239 # three dashes, Changelist (translated), quote, |
|
2240 # changelist name, quote, : |
|
2241 |
|
2242 args = [] |
|
2243 args.append("status") |
|
2244 args.append("--non-interactive") |
|
2245 args.append(".") |
|
2246 |
|
2247 ppath = e5App().getObject("Project").getProjectPath() |
|
2248 process = QProcess() |
|
2249 process.setWorkingDirectory(ppath) |
|
2250 process.start('svn', args) |
|
2251 procStarted = process.waitForStarted(5000) |
|
2252 if procStarted: |
|
2253 finished = process.waitForFinished(30000) |
|
2254 if finished and process.exitCode() == 0: |
|
2255 output = \ |
|
2256 str(process.readAllStandardOutput(), |
|
2257 Preferences.getSystem("IOEncoding"), |
|
2258 'replace') |
|
2259 if output: |
|
2260 for line in output.splitlines(): |
|
2261 if rx_changelist.exactMatch(line): |
|
2262 changelist = rx_changelist.cap(1) |
|
2263 if changelist not in changelists: |
|
2264 changelists.append(changelist) |
|
2265 |
|
2266 return changelists |
|
2267 |
|
2268 def svnUpgrade(self, path): |
|
2269 """ |
|
2270 Public method to upgrade the working copy format. |
|
2271 |
|
2272 @param path directory name to show change lists for (string) |
|
2273 """ |
|
2274 args = [] |
|
2275 args.append("upgrade") |
|
2276 args.append(".") |
|
2277 |
|
2278 dia = SvnDialog(self.tr('Upgrade')) |
|
2279 res = dia.startProcess(args, path) |
|
2280 if res: |
|
2281 dia.exec_() |
|
2282 |
|
2283 ########################################################################### |
|
2284 ## Private Subversion specific methods are below. |
|
2285 ########################################################################### |
|
2286 |
|
2287 def __svnURL(self, url): |
|
2288 """ |
|
2289 Private method to format a url for subversion. |
|
2290 |
|
2291 @param url unformatted url string (string) |
|
2292 @return properly formated url for subversion (string) |
|
2293 """ |
|
2294 url = self.svnNormalizeURL(url) |
|
2295 url = url.split(':', 2) |
|
2296 if len(url) == 3: |
|
2297 scheme = url[0] |
|
2298 host = url[1] |
|
2299 port, path = url[2].split("/", 1) |
|
2300 return "{0}:{1}:{2}/{3}".format( |
|
2301 scheme, host, port, Utilities.quote(path)) |
|
2302 else: |
|
2303 scheme = url[0] |
|
2304 if scheme == "file": |
|
2305 return "{0}:{1}".format(scheme, Utilities.quote(url[1])) |
|
2306 else: |
|
2307 try: |
|
2308 host, path = url[1][2:].split("/", 1) |
|
2309 except ValueError: |
|
2310 host = url[1][2:] |
|
2311 path = "" |
|
2312 return "{0}://{1}/{2}".format( |
|
2313 scheme, host, Utilities.quote(path)) |
|
2314 |
|
2315 def svnNormalizeURL(self, url): |
|
2316 """ |
|
2317 Public method to normalize a url for subversion. |
|
2318 |
|
2319 @param url url string (string) |
|
2320 @return properly normalized url for subversion (string) |
|
2321 """ |
|
2322 protocol, url = url.split("://", 1) |
|
2323 if url.startswith("\\\\"): |
|
2324 url = url[2:] |
|
2325 if protocol == "file": |
|
2326 url = os.path.normcase(url) |
|
2327 url = url.replace('\\', '/') |
|
2328 if url.endswith('/'): |
|
2329 url = url[:-1] |
|
2330 if not url.startswith("/") and url[1] in [":", "|"]: |
|
2331 url = "/{0}".format(url) |
|
2332 return "{0}://{1}".format(protocol, url) |
|
2333 |
|
2334 ########################################################################### |
|
2335 ## Methods to get the helper objects are below. |
|
2336 ########################################################################### |
|
2337 |
|
2338 def vcsGetProjectBrowserHelper(self, browser, project, |
|
2339 isTranslationsBrowser=False): |
|
2340 """ |
|
2341 Public method to instanciate a helper object for the different |
|
2342 project browsers. |
|
2343 |
|
2344 @param browser reference to the project browser object |
|
2345 @param project reference to the project object |
|
2346 @param isTranslationsBrowser flag indicating, the helper is requested |
|
2347 for the translations browser (this needs some special treatment) |
|
2348 @return the project browser helper object |
|
2349 """ |
|
2350 from .ProjectBrowserHelper import SvnProjectBrowserHelper |
|
2351 return SvnProjectBrowserHelper(self, browser, project, |
|
2352 isTranslationsBrowser) |
|
2353 |
|
2354 def vcsGetProjectHelper(self, project): |
|
2355 """ |
|
2356 Public method to instanciate a helper object for the project. |
|
2357 |
|
2358 @param project reference to the project object |
|
2359 @return the project helper object |
|
2360 """ |
|
2361 helper = self.__plugin.getProjectHelper() |
|
2362 helper.setObjects(self, project) |
|
2363 self.__wcng = \ |
|
2364 os.path.exists( |
|
2365 os.path.join(project.getProjectPath(), ".svn", "format")) or \ |
|
2366 os.path.exists( |
|
2367 os.path.join(project.getProjectPath(), "_svn", "format")) or \ |
|
2368 os.path.exists( |
|
2369 os.path.join(project.getProjectPath(), ".svn", "wc.db")) or \ |
|
2370 os.path.exists( |
|
2371 os.path.join(project.getProjectPath(), "_svn", "wc.db")) |
|
2372 return helper |
|
2373 |
|
2374 ########################################################################### |
|
2375 ## Status Monitor Thread methods |
|
2376 ########################################################################### |
|
2377 |
|
2378 def _createStatusMonitorThread(self, interval, project): |
|
2379 """ |
|
2380 Protected method to create an instance of the VCS status monitor |
|
2381 thread. |
|
2382 |
|
2383 @param interval check interval for the monitor thread in seconds |
|
2384 (integer) |
|
2385 @param project reference to the project object |
|
2386 @return reference to the monitor thread (QThread) |
|
2387 """ |
|
2388 from .SvnStatusMonitorThread import SvnStatusMonitorThread |
|
2389 return SvnStatusMonitorThread(interval, project, self) |