eric7/Plugins/VcsPlugins/vcsSubversion/subversion.py

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

eric ide

mercurial