src/eric7/Plugins/VcsPlugins/vcsSubversion/subversion.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2022 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 PyQt6.QtCore import pyqtSignal, QProcess, QCoreApplication
16 from PyQt6.QtWidgets import QLineEdit, QDialog, QInputDialog, QApplication
17
18 from EricWidgets.EricApplication import ericApp
19 from EricWidgets import EricMessageBox
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 EricMessageBox.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 EricMessageBox.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 = ericApp().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 = ericApp().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 = EricMessageBox.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=EricMessageBox.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 vcsCommitMessages(self):
575 """
576 Public method to get the list of saved commit messages.
577
578 @return list of saved commit messages
579 @rtype list of str
580 """
581 # try per project commit history first
582 messages = self._vcsProjectCommitMessages()
583 if not messages:
584 # empty list returned, try the vcs specific one
585 messages = self.getPlugin().getPreferences("Commits")
586
587 return messages
588
589 def vcsAddCommitMessage(self, message):
590 """
591 Public method to add a commit message to the list of saved messages.
592
593 @param message message to be added
594 @type str
595 """
596 if not self._vcsAddProjectCommitMessage(message):
597 commitMessages = self.vcsCommitMessages()
598 if message in commitMessages:
599 commitMessages.remove(message)
600 commitMessages.insert(0, message)
601 no = Preferences.getVCS("CommitMessages")
602 del commitMessages[no:]
603 self.getPlugin().setPreferences("Commits", commitMessages)
604
605 def vcsClearCommitMessages(self):
606 """
607 Public method to clear the list of saved messages.
608 """
609 if not self._vcsClearProjectCommitMessages():
610 self.getPlugin().setPreferences('Commits', [])
611
612 def vcsUpdate(self, name, noDialog=False):
613 """
614 Public method used to update a file/directory with the Subversion
615 repository.
616
617 @param name file/directory name to be updated (string or list of
618 strings)
619 @param noDialog flag indicating quiet operations (boolean)
620 @return flag indicating, that the update contained an add
621 or delete (boolean)
622 """
623 args = []
624 args.append('update')
625 self.addArguments(args, self.options['global'])
626 self.addArguments(args, self.options['update'])
627 if self.version >= (1, 5, 0):
628 args.append('--accept')
629 args.append('postpone')
630 if isinstance(name, list):
631 dname, fnames = self.splitPathList(name)
632 self.addArguments(args, fnames)
633 else:
634 dname, fname = self.splitPath(name)
635 args.append(fname)
636
637 if noDialog:
638 self.startSynchronizedProcess(QProcess(), "svn", args, dname)
639 res = False
640 else:
641 dia = SvnDialog(
642 self.tr('Synchronizing with the Subversion repository'))
643 res = dia.startProcess(args, dname, True)
644 if res:
645 dia.exec()
646 res = dia.hasAddOrDelete()
647 self.checkVCSStatus()
648 return res
649
650 def vcsAdd(self, name, isDir=False, noDialog=False):
651 """
652 Public method used to add a file/directory to the Subversion
653 repository.
654
655 @param name file/directory name to be added (string)
656 @param isDir flag indicating name is a directory (boolean)
657 @param noDialog flag indicating quiet operations
658 """
659 args = []
660 args.append('add')
661 self.addArguments(args, self.options['global'])
662 self.addArguments(args, self.options['add'])
663 args.append('--non-recursive')
664 if noDialog and '--force' not in args:
665 args.append('--force')
666
667 if isinstance(name, list):
668 if isDir:
669 dname, fname = os.path.split(name[0])
670 else:
671 dname, fnames = self.splitPathList(name)
672 else:
673 if isDir:
674 dname, fname = os.path.split(name)
675 else:
676 dname, fname = self.splitPath(name)
677 tree = []
678 wdir = dname
679 if self.__wcng:
680 repodir = dname
681 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
682 repodir = os.path.dirname(repodir)
683 if os.path.splitdrive(repodir)[1] == os.sep:
684 return # oops, project is not version controlled
685 while (
686 os.path.normcase(dname) != os.path.normcase(repodir) and
687 (os.path.normcase(dname) not in self.statusCache or
688 self.statusCache[os.path.normcase(dname)] ==
689 self.canBeAdded)
690 ):
691 # add directories recursively, if they aren't in the
692 # repository already
693 tree.insert(-1, dname)
694 dname = os.path.dirname(dname)
695 wdir = dname
696 else:
697 while not os.path.exists(os.path.join(dname, self.adminDir)):
698 # add directories recursively, if they aren't in the
699 # repository already
700 tree.insert(-1, dname)
701 dname = os.path.dirname(dname)
702 wdir = dname
703 self.addArguments(args, tree)
704
705 if isinstance(name, list):
706 tree2 = []
707 for n in name:
708 d = os.path.dirname(n)
709 if self.__wcng:
710 repodir = d
711 while not os.path.isdir(
712 os.path.join(repodir, self.adminDir)):
713 repodir = os.path.dirname(repodir)
714 if os.path.splitdrive(repodir)[1] == os.sep:
715 return # oops, project is not version controlled
716 while (
717 os.path.normcase(d) != os.path.normcase(repodir) and
718 (d not in tree2 + tree) and
719 (os.path.normcase(d) not in self.statusCache or
720 self.statusCache[os.path.normcase(d)] ==
721 self.canBeAdded)
722 ):
723 tree2.append(d)
724 d = os.path.dirname(d)
725 else:
726 while not os.path.exists(os.path.join(d, self.adminDir)):
727 if d in tree2 + tree:
728 break
729 tree2.append(d)
730 d = os.path.dirname(d)
731 tree2.reverse()
732 self.addArguments(args, tree2)
733 self.addArguments(args, name)
734 else:
735 args.append(name)
736
737 if noDialog:
738 self.startSynchronizedProcess(QProcess(), "svn", args, wdir)
739 else:
740 dia = SvnDialog(
741 self.tr('Adding files/directories to the Subversion'
742 ' repository'))
743 res = dia.startProcess(args, wdir)
744 if res:
745 dia.exec()
746
747 def vcsAddBinary(self, name, isDir=False):
748 """
749 Public method used to add a file/directory in binary mode to the
750 Subversion repository.
751
752 @param name file/directory name to be added (string)
753 @param isDir flag indicating name is a directory (boolean)
754 """
755 self.vcsAdd(name, isDir)
756
757 def vcsAddTree(self, path):
758 """
759 Public method to add a directory tree rooted at path to the Subversion
760 repository.
761
762 @param path root directory of the tree to be added (string or list of
763 strings))
764 """
765 args = []
766 args.append('add')
767 self.addArguments(args, self.options['global'])
768 self.addArguments(args, self.options['add'])
769
770 tree = []
771 if isinstance(path, list):
772 dname, fnames = self.splitPathList(path)
773 for n in path:
774 d = os.path.dirname(n)
775 if self.__wcng:
776 repodir = d
777 while not os.path.isdir(
778 os.path.join(repodir, self.adminDir)):
779 repodir = os.path.dirname(repodir)
780 if os.path.splitdrive(repodir)[1] == os.sep:
781 return # oops, project is not version controlled
782 while (
783 os.path.normcase(d) != os.path.normcase(repodir) and
784 (d not in tree) and
785 (os.path.normcase(d) not in self.statusCache or
786 self.statusCache[os.path.normcase(d)] ==
787 self.canBeAdded)
788 ):
789 tree.append(d)
790 d = os.path.dirname(d)
791 else:
792 while not os.path.exists(os.path.join(d, self.adminDir)):
793 # add directories recursively,
794 # if they aren't in the repository already
795 if d in tree:
796 break
797 tree.append(d)
798 d = os.path.dirname(d)
799 tree.reverse()
800 else:
801 dname, fname = os.path.split(path)
802 if self.__wcng:
803 repodir = dname
804 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
805 repodir = os.path.dirname(repodir)
806 if os.path.splitdrive(repodir)[1] == os.sep:
807 return # oops, project is not version controlled
808 while (
809 os.path.normcase(dname) != os.path.normcase(repodir) and
810 (os.path.normcase(dname) not in self.statusCache or
811 self.statusCache[os.path.normcase(dname)] ==
812 self.canBeAdded)
813 ):
814 # add directories recursively, if they aren't in the
815 # repository already
816 tree.insert(-1, dname)
817 dname = os.path.dirname(dname)
818 else:
819 while not os.path.exists(os.path.join(dname, self.adminDir)):
820 # add directories recursively,
821 # if they aren't in the repository already
822 tree.insert(-1, dname)
823 dname = os.path.dirname(dname)
824 if tree:
825 self.vcsAdd(tree, True)
826
827 if isinstance(path, list):
828 self.addArguments(args, path)
829 else:
830 args.append(path)
831
832 dia = SvnDialog(
833 self.tr('Adding directory trees to the Subversion repository'))
834 res = dia.startProcess(args, dname)
835 if res:
836 dia.exec()
837
838 def vcsRemove(self, name, project=False, noDialog=False):
839 """
840 Public method used to remove a file/directory from the Subversion
841 repository.
842
843 The default operation is to remove the local copy as well.
844
845 @param name file/directory name to be removed (string or list of
846 strings))
847 @param project flag indicating deletion of a project tree (boolean)
848 (not needed)
849 @param noDialog flag indicating quiet operations
850 @return flag indicating successfull operation (boolean)
851 """
852 args = []
853 args.append('delete')
854 self.addArguments(args, self.options['global'])
855 self.addArguments(args, self.options['remove'])
856 if noDialog and '--force' not in args:
857 args.append('--force')
858
859 if isinstance(name, list):
860 self.addArguments(args, name)
861 else:
862 args.append(name)
863
864 if noDialog:
865 res = self.startSynchronizedProcess(QProcess(), "svn", args)
866 else:
867 dia = SvnDialog(
868 self.tr('Removing files/directories from the Subversion'
869 ' repository'))
870 res = dia.startProcess(args)
871 if res:
872 dia.exec()
873 res = dia.normalExit()
874
875 return res
876
877 def vcsMove(self, name, project, target=None, noDialog=False):
878 """
879 Public method used to move a file/directory.
880
881 @param name file/directory name to be moved (string)
882 @param project reference to the project object
883 @param target new name of the file/directory (string)
884 @param noDialog flag indicating quiet operations
885 @return flag indicating successfull operation (boolean)
886 """
887 rx_prot = re.compile('(file:|svn:|svn+ssh:|http:|https:).+')
888 opts = self.options['global'][:]
889 force = '--force' in opts
890 if force:
891 del opts[opts.index('--force')]
892
893 res = False
894 if noDialog:
895 if target is None:
896 return False
897 force = True
898 accepted = True
899 else:
900 from .SvnCopyDialog import SvnCopyDialog
901 dlg = SvnCopyDialog(name, None, True, force)
902 accepted = (dlg.exec() == QDialog.DialogCode.Accepted)
903 if accepted:
904 target, force = dlg.getData()
905 if not target:
906 return False
907
908 isDir = (os.path.isdir(name) if rx_prot.fullmatch(target) is None
909 else False)
910
911 if accepted:
912 args = []
913 args.append('move')
914 self.addArguments(args, opts)
915 if force:
916 args.append('--force')
917 if rx_prot.fullmatch(target) is not None:
918 args.append('--message')
919 args.append('Moving {0} to {1}'.format(name, target))
920 target = self.__svnURL(target)
921 args.append(name)
922 args.append(target)
923
924 if noDialog:
925 res = self.startSynchronizedProcess(QProcess(), "svn", args)
926 else:
927 dia = SvnDialog(self.tr('Moving {0}')
928 .format(name))
929 res = dia.startProcess(args)
930 if res:
931 dia.exec()
932 res = dia.normalExit()
933 if res and rx_prot.fullmatch(target) is None:
934 if target.startswith(project.getProjectPath()):
935 if isDir:
936 project.moveDirectory(name, target)
937 else:
938 project.renameFileInPdata(name, target)
939 else:
940 if isDir:
941 project.removeDirectory(name)
942 else:
943 project.removeFile(name)
944 return res
945
946 def vcsDiff(self, name):
947 """
948 Public method used to view the difference of a file/directory to the
949 Subversion repository.
950
951 If name is a directory and is the project directory, all project files
952 are saved first. If name is a file (or list of files), which is/are
953 being edited and has unsaved modification, they can be saved or the
954 operation may be aborted.
955
956 @param name file/directory name to be diffed (string)
957 """
958 names = name[:] if isinstance(name, list) else [name]
959 for nam in names:
960 if os.path.isfile(nam):
961 editor = ericApp().getObject("ViewManager").getOpenEditor(nam)
962 if editor and not editor.checkDirty():
963 return
964 else:
965 project = ericApp().getObject("Project")
966 if nam == project.ppath and not project.saveAllScripts():
967 return
968 if self.diff is None:
969 from .SvnDiffDialog import SvnDiffDialog
970 self.diff = SvnDiffDialog(self)
971 self.diff.show()
972 self.diff.raise_()
973 QApplication.processEvents()
974 self.diff.start(name, refreshable=True)
975
976 def vcsStatus(self, name):
977 """
978 Public method used to view the status of files/directories in the
979 Subversion repository.
980
981 @param name file/directory name(s) to show the status of
982 (string or list of strings)
983 """
984 if self.status is None:
985 from .SvnStatusDialog import SvnStatusDialog
986 self.status = SvnStatusDialog(self)
987 self.status.show()
988 self.status.raise_()
989 self.status.start(name)
990
991 def vcsTag(self, name):
992 """
993 Public method used to set the tag of a file/directory in the
994 Subversion repository.
995
996 @param name file/directory name to be tagged (string)
997 """
998 dname, fname = self.splitPath(name)
999
1000 reposURL = self.svnGetReposName(dname)
1001 if reposURL is None:
1002 EricMessageBox.critical(
1003 self.__ui,
1004 self.tr("Subversion Error"),
1005 self.tr(
1006 """The URL of the project repository could not be"""
1007 """ retrieved from the working copy. The tag operation"""
1008 """ will be aborted"""))
1009 return
1010
1011 url = (
1012 None
1013 if self.otherData["standardLayout"] else
1014 self.svnNormalizeURL(reposURL)
1015 )
1016 from .SvnTagDialog import SvnTagDialog
1017 dlg = SvnTagDialog(self.allTagsBranchesList, url,
1018 self.otherData["standardLayout"])
1019 if dlg.exec() == QDialog.DialogCode.Accepted:
1020 tag, tagOp = dlg.getParameters()
1021 if tag in self.allTagsBranchesList:
1022 self.allTagsBranchesList.remove(tag)
1023 self.allTagsBranchesList.insert(0, tag)
1024 else:
1025 return
1026
1027 if self.otherData["standardLayout"]:
1028 rx_base = re.compile('(.+)/(trunk|tags|branches).*')
1029 match = rx_base.fullmatch(reposURL)
1030 if match is None:
1031 EricMessageBox.critical(
1032 self.__ui,
1033 self.tr("Subversion Error"),
1034 self.tr(
1035 """The URL of the project repository has an"""
1036 """ invalid format. The tag operation will"""
1037 """ be aborted"""))
1038 return
1039
1040 reposRoot = match.group(1)
1041 if tagOp in [1, 4]:
1042 url = '{0}/tags/{1}'.format(reposRoot, quote(tag))
1043 elif tagOp in [2, 8]:
1044 url = '{0}/branches/{1}'.format(reposRoot, quote(tag))
1045 else:
1046 url = self.__svnURL(tag)
1047
1048 args = []
1049 if tagOp in [1, 2]:
1050 args.append('copy')
1051 self.addArguments(args, self.options['global'])
1052 self.addArguments(args, self.options['tag'])
1053 args.append('--message')
1054 args.append('Created tag <{0}>'.format(tag))
1055 args.append(reposURL)
1056 args.append(url)
1057 else:
1058 args.append('delete')
1059 self.addArguments(args, self.options['global'])
1060 self.addArguments(args, self.options['tag'])
1061 args.append('--message')
1062 args.append('Deleted tag <{0}>'.format(tag))
1063 args.append(url)
1064
1065 dia = SvnDialog(self.tr('Tagging {0} in the Subversion repository')
1066 .format(name))
1067 res = dia.startProcess(args)
1068 if res:
1069 dia.exec()
1070
1071 def vcsRevert(self, name):
1072 """
1073 Public method used to revert changes made to a file/directory.
1074
1075 @param name file/directory name to be reverted
1076 @type str
1077 @return flag indicating, that the update contained an add
1078 or delete
1079 @rtype bool
1080 """
1081 args = []
1082 args.append('revert')
1083 self.addArguments(args, self.options['global'])
1084 if isinstance(name, list):
1085 self.addArguments(args, name)
1086 names = name[:]
1087 else:
1088 if os.path.isdir(name):
1089 args.append('--recursive')
1090 args.append(name)
1091 names = [name]
1092
1093 project = ericApp().getObject("Project")
1094 names = [project.getRelativePath(nam) for nam in names]
1095 if names[0]:
1096 from UI.DeleteFilesConfirmationDialog import (
1097 DeleteFilesConfirmationDialog
1098 )
1099 dlg = DeleteFilesConfirmationDialog(
1100 self.parent(),
1101 self.tr("Revert changes"),
1102 self.tr("Do you really want to revert all changes to"
1103 " these files or directories?"),
1104 names)
1105 yes = dlg.exec() == QDialog.DialogCode.Accepted
1106 else:
1107 yes = EricMessageBox.yesNo(
1108 None,
1109 self.tr("Revert changes"),
1110 self.tr("""Do you really want to revert all changes of"""
1111 """ the project?"""))
1112 if yes:
1113 dia = SvnDialog(self.tr('Reverting changes'))
1114 res = dia.startProcess(args)
1115 if res:
1116 dia.exec()
1117 self.checkVCSStatus()
1118
1119 return False
1120
1121 def vcsForget(self, name):
1122 """
1123 Public method used to remove a file from the repository.
1124
1125 Note: svn does not support this operation. The method is implemented
1126 as a NoOp.
1127
1128 @param name file/directory name to be removed
1129 @type str or list of str
1130 """
1131 pass
1132
1133 def vcsSwitch(self, name):
1134 """
1135 Public method used to switch a directory to a different tag/branch.
1136
1137 @param name directory name to be switched (string)
1138 @return flag indicating added or changed files (boolean)
1139 """
1140 dname, fname = self.splitPath(name)
1141
1142 reposURL = self.svnGetReposName(dname)
1143 if reposURL is None:
1144 EricMessageBox.critical(
1145 self.__ui,
1146 self.tr("Subversion Error"),
1147 self.tr(
1148 """The URL of the project repository could not be"""
1149 """ retrieved from the working copy. The switch"""
1150 """ operation will be aborted"""))
1151 return False
1152
1153 url = (
1154 None
1155 if self.otherData["standardLayout"] else
1156 self.svnNormalizeURL(reposURL)
1157 )
1158 from .SvnSwitchDialog import SvnSwitchDialog
1159 dlg = SvnSwitchDialog(self.allTagsBranchesList, url,
1160 self.otherData["standardLayout"])
1161 if dlg.exec() == QDialog.DialogCode.Accepted:
1162 tag, tagType = dlg.getParameters()
1163 if tag in self.allTagsBranchesList:
1164 self.allTagsBranchesList.remove(tag)
1165 self.allTagsBranchesList.insert(0, tag)
1166 else:
1167 return False
1168
1169 if self.otherData["standardLayout"]:
1170 rx_base = re.compile('(.+)/(trunk|tags|branches).*')
1171 match = rx_base.fullmatch(reposURL)
1172 if match is None:
1173 EricMessageBox.critical(
1174 self.__ui,
1175 self.tr("Subversion Error"),
1176 self.tr(
1177 """The URL of the project repository has an"""
1178 """ invalid format. The switch operation will"""
1179 """ be aborted"""))
1180 return False
1181
1182 reposRoot = match.group(1)
1183 tn = tag
1184 if tagType == 1:
1185 url = '{0}/tags/{1}'.format(reposRoot, quote(tag))
1186 elif tagType == 2:
1187 url = '{0}/branches/{1}'.format(reposRoot, quote(tag))
1188 elif tagType == 4:
1189 url = '{0}/trunk'.format(reposRoot)
1190 tn = 'HEAD'
1191 else:
1192 url = self.__svnURL(tag)
1193 tn = url
1194
1195 args = []
1196 args.append('switch')
1197 if self.version >= (1, 5, 0):
1198 args.append('--accept')
1199 args.append('postpone')
1200 args.append(url)
1201 args.append(name)
1202
1203 dia = SvnDialog(self.tr('Switching to {0}')
1204 .format(tn))
1205 res = dia.startProcess(args, setLanguage=True)
1206 if res:
1207 dia.exec()
1208 res = dia.hasAddOrDelete()
1209 self.checkVCSStatus()
1210 return res
1211
1212 def vcsMerge(self, name):
1213 """
1214 Public method used to merge a URL/revision into the local project.
1215
1216 @param name file/directory name to be merged (string)
1217 """
1218 dname, fname = self.splitPath(name)
1219
1220 opts = self.options['global'][:]
1221 force = '--force' in opts
1222 if force:
1223 del opts[opts.index('--force')]
1224
1225 from .SvnMergeDialog import SvnMergeDialog
1226 dlg = SvnMergeDialog(
1227 self.mergeList[0], self.mergeList[1], self.mergeList[2], force)
1228 if dlg.exec() == QDialog.DialogCode.Accepted:
1229 urlrev1, urlrev2, target, force = dlg.getParameters()
1230 else:
1231 return
1232
1233 # remember URL or revision
1234 if urlrev1 in self.mergeList[0]:
1235 self.mergeList[0].remove(urlrev1)
1236 self.mergeList[0].insert(0, urlrev1)
1237 if urlrev2 in self.mergeList[1]:
1238 self.mergeList[1].remove(urlrev2)
1239 self.mergeList[1].insert(0, urlrev2)
1240
1241 rx_rev = re.compile('\\d+|HEAD')
1242
1243 args = []
1244 args.append('merge')
1245 self.addArguments(args, opts)
1246 if self.version >= (1, 5, 0):
1247 args.append('--accept')
1248 args.append('postpone')
1249 if force:
1250 args.append('--force')
1251 if rx_rev.fullmatch(urlrev1) is not None:
1252 args.append('-r')
1253 args.append('{0}:{1}'.format(urlrev1, urlrev2))
1254 if not target:
1255 args.append(name)
1256 else:
1257 args.append(target)
1258
1259 # remember target
1260 if target in self.mergeList[2]:
1261 self.mergeList[2].remove(target)
1262 self.mergeList[2].insert(0, target)
1263 else:
1264 args.append(self.__svnURL(urlrev1))
1265 args.append(self.__svnURL(urlrev2))
1266 args.append(fname)
1267
1268 dia = SvnDialog(self.tr('Merging {0}').format(name))
1269 res = dia.startProcess(args, dname)
1270 if res:
1271 dia.exec()
1272
1273 def vcsRegisteredState(self, name):
1274 """
1275 Public method used to get the registered state of a file in the vcs.
1276
1277 @param name filename to check (string)
1278 @return a combination of canBeCommited and canBeAdded
1279 """
1280 if self.__wcng:
1281 return self.__vcsRegisteredState_wcng(name)
1282 else:
1283 return self.__vcsRegisteredState_wc(name)
1284
1285 def __vcsRegisteredState_wcng(self, name):
1286 """
1287 Private method used to get the registered state of a file in the vcs.
1288
1289 This is the variant for subversion installations using the new
1290 working copy meta-data format.
1291
1292 @param name filename to check (string)
1293 @return a combination of canBeCommited and canBeAdded
1294 """
1295 if name.endswith(os.sep):
1296 name = name[:-1]
1297 name = os.path.normcase(name)
1298 dname, fname = self.splitPath(name)
1299
1300 if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)):
1301 return self.canBeCommitted
1302
1303 if name in self.statusCache:
1304 return self.statusCache[name]
1305
1306 name = os.path.normcase(name)
1307 states = {name: 0}
1308 states = self.vcsAllRegisteredStates(states, dname, False)
1309 if states[name] == self.canBeCommitted:
1310 return self.canBeCommitted
1311 else:
1312 return self.canBeAdded
1313
1314 def __vcsRegisteredState_wc(self, name):
1315 """
1316 Private method used to get the registered state of a file in the VCS.
1317
1318 This is the variant for subversion installations using the old working
1319 copy meta-data format.
1320
1321 @param name filename to check (string)
1322 @return a combination of canBeCommited and canBeAdded
1323 """
1324 dname, fname = self.splitPath(name)
1325
1326 if fname == '.':
1327 if os.path.isdir(os.path.join(dname, self.adminDir)):
1328 return self.canBeCommitted
1329 else:
1330 return self.canBeAdded
1331
1332 name = os.path.normcase(name)
1333 states = {name: 0}
1334 states = self.vcsAllRegisteredStates(states, dname, False)
1335 if states[name] == self.canBeCommitted:
1336 return self.canBeCommitted
1337 else:
1338 return self.canBeAdded
1339
1340 def vcsAllRegisteredStates(self, names, dname, shortcut=True):
1341 """
1342 Public method used to get the registered states of a number of files
1343 in the VCS.
1344
1345 <b>Note:</b> If a shortcut is to be taken, the code will only check,
1346 if the named directory has been scanned already. If so, it is assumed,
1347 that the states for all files have been populated by the previous run.
1348
1349 @param names dictionary with all filenames to be checked as keys
1350 @param dname directory to check in (string)
1351 @param shortcut flag indicating a shortcut should be taken (boolean)
1352 @return the received dictionary completed with a combination of
1353 canBeCommited and canBeAdded or None in order to signal an error
1354 """
1355 if self.__wcng:
1356 return self.__vcsAllRegisteredStates_wcng(names, dname, shortcut)
1357 else:
1358 return self.__vcsAllRegisteredStates_wc(names, dname, shortcut)
1359
1360 def __vcsAllRegisteredStates_wcng(self, names, dname, shortcut=True):
1361 """
1362 Private method used to get the registered states of a number of files
1363 in the VCS.
1364
1365 This is the variant for subversion installations using the new working
1366 copy meta-data format.
1367
1368 <b>Note:</b> If a shortcut is to be taken, the code will only check,
1369 if the named directory has been scanned already. If so, it is assumed,
1370 that the states for all files has been populated by the previous run.
1371
1372 @param names dictionary with all filenames to be checked as keys
1373 @param dname directory to check in (string)
1374 @param shortcut flag indicating a shortcut should be taken (boolean)
1375 @return the received dictionary completed with a combination of
1376 canBeCommited and canBeAdded or None in order to signal an error
1377 """
1378 if dname.endswith(os.sep):
1379 dname = dname[:-1]
1380 dname = os.path.normcase(dname)
1381
1382 found = False
1383 for name in self.statusCache:
1384 if name in names:
1385 found = True
1386 names[name] = self.statusCache[name]
1387
1388 if not found:
1389 # find the root of the repo
1390 repodir = dname
1391 while not os.path.isdir(os.path.join(repodir, self.adminDir)):
1392 repodir = os.path.dirname(repodir)
1393 if os.path.splitdrive(repodir)[1] == os.sep:
1394 return names
1395
1396 ioEncoding = str(Preferences.getSystem("IOEncoding"))
1397 process = QProcess()
1398 args = []
1399 args.append('status')
1400 args.append('--verbose')
1401 args.append('--non-interactive')
1402 args.append(dname)
1403 process.start('svn', args)
1404 procStarted = process.waitForStarted(5000)
1405 if procStarted:
1406 finished = process.waitForFinished(30000)
1407 if finished and process.exitCode() == 0:
1408 output = str(process.readAllStandardOutput(), ioEncoding,
1409 'replace')
1410 for line in output.splitlines():
1411 match = self.rx_status1.fullmatch(line)
1412 if match is not None:
1413 flags = match.group(1)
1414 path = match.group(5).strip()
1415 else:
1416 match = self.rx_status2.fullmatch(line)
1417 if match is not None:
1418 flags = match.group(1)
1419 path = match.group(2).strip()
1420 else:
1421 continue
1422 name = os.path.normcase(path)
1423 if flags[0] not in "?I":
1424 if name in names:
1425 names[name] = self.canBeCommitted
1426 self.statusCache[name] = self.canBeCommitted
1427 else:
1428 self.statusCache[name] = self.canBeAdded
1429
1430 return names
1431
1432 def __vcsAllRegisteredStates_wc(self, names, dname, shortcut=True):
1433 """
1434 Private method used to get the registered states of a number of files
1435 in the VCS.
1436
1437 This is the variant for subversion installations using the old working
1438 copy meta-data format.
1439
1440 <b>Note:</b> If a shortcut is to be taken, the code will only check,
1441 if the named directory has been scanned already. If so, it is assumed,
1442 that the states for all files has been populated by the previous run.
1443
1444 @param names dictionary with all filenames to be checked as keys
1445 @param dname directory to check in (string)
1446 @param shortcut flag indicating a shortcut should be taken (boolean)
1447 @return the received dictionary completed with a combination of
1448 canBeCommited and canBeAdded or None in order to signal an error
1449 """
1450 if not os.path.isdir(os.path.join(dname, self.adminDir)):
1451 # not under version control -> do nothing
1452 return names
1453
1454 found = False
1455 for name in list(self.statusCache.keys()):
1456 if os.path.dirname(name) == dname:
1457 if shortcut:
1458 found = True
1459 break
1460 if name in names:
1461 found = True
1462 names[name] = self.statusCache[name]
1463
1464 if not found:
1465 ioEncoding = Preferences.getSystem("IOEncoding")
1466 process = QProcess()
1467 args = []
1468 args.append('status')
1469 args.append('--verbose')
1470 args.append('--non-interactive')
1471 args.append(dname)
1472 process.start('svn', args)
1473 procStarted = process.waitForStarted(5000)
1474 if procStarted:
1475 finished = process.waitForFinished(30000)
1476 if finished and process.exitCode() == 0:
1477 output = str(process.readAllStandardOutput(), ioEncoding,
1478 'replace')
1479 for line in output.splitlines():
1480 match = self.rx_status1.fullmatch(line)
1481 if match is not None:
1482 flags = match.group(1)
1483 path = match.group(5).strip()
1484 else:
1485 match = self.rx_status2.fullmatch(line)
1486 if match is not None:
1487 flags = match.group(1)
1488 path = match.group(2).strip()
1489 else:
1490 continue
1491 name = os.path.normcase(path)
1492 if flags[0] not in "?I":
1493 if name in names:
1494 names[name] = self.canBeCommitted
1495 self.statusCache[name] = self.canBeCommitted
1496 else:
1497 self.statusCache[name] = self.canBeAdded
1498
1499 return names
1500
1501 def clearStatusCache(self):
1502 """
1503 Public method to clear the status cache.
1504 """
1505 self.statusCache = {}
1506
1507 def vcsInitConfig(self, project):
1508 """
1509 Public method to initialize the VCS configuration.
1510
1511 This method ensures, that an ignore file exists.
1512
1513 @param project reference to the project (Project)
1514 """
1515 configPath = getConfigPath()
1516 if os.path.exists(configPath):
1517 amendConfig()
1518 else:
1519 createDefaultConfig()
1520
1521 def vcsName(self):
1522 """
1523 Public method returning the name of the vcs.
1524
1525 @return always 'Subversion' (string)
1526 """
1527 return "Subversion"
1528
1529 def vcsCleanup(self, name):
1530 """
1531 Public method used to cleanup the working copy.
1532
1533 @param name directory name to be cleaned up (string)
1534 """
1535 args = []
1536 args.append('cleanup')
1537 self.addArguments(args, self.options['global'])
1538 args.append(name)
1539
1540 dia = SvnDialog(self.tr('Cleaning up {0}')
1541 .format(name))
1542 res = dia.startProcess(args)
1543 if res:
1544 dia.exec()
1545
1546 def vcsCommandLine(self, name):
1547 """
1548 Public method used to execute arbitrary subversion commands.
1549
1550 @param name directory name of the working directory (string)
1551 """
1552 from .SvnCommandDialog import SvnCommandDialog
1553 dlg = SvnCommandDialog(self.commandHistory, self.wdHistory, name)
1554 if dlg.exec() == QDialog.DialogCode.Accepted:
1555 command, wd = dlg.getData()
1556 commandList = Utilities.parseOptionString(command)
1557
1558 # This moves any previous occurrence of these arguments to the head
1559 # of the list.
1560 if command in self.commandHistory:
1561 self.commandHistory.remove(command)
1562 self.commandHistory.insert(0, command)
1563 if wd in self.wdHistory:
1564 self.wdHistory.remove(wd)
1565 self.wdHistory.insert(0, wd)
1566
1567 args = []
1568 self.addArguments(args, commandList)
1569
1570 dia = SvnDialog(self.tr('Subversion command'))
1571 res = dia.startProcess(args, wd)
1572 if res:
1573 dia.exec()
1574
1575 def vcsOptionsDialog(self, project, archive, editable=False, parent=None):
1576 """
1577 Public method to get a dialog to enter repository info.
1578
1579 @param project reference to the project object
1580 @param archive name of the project in the repository (string)
1581 @param editable flag indicating that the project name is editable
1582 (boolean)
1583 @param parent parent widget (QWidget)
1584 @return reference to the instantiated options dialog (SvnOptionsDialog)
1585 """
1586 from .SvnOptionsDialog import SvnOptionsDialog
1587 return SvnOptionsDialog(self, project, parent)
1588
1589 def vcsNewProjectOptionsDialog(self, parent=None):
1590 """
1591 Public method to get a dialog to enter repository info for getting
1592 a new project.
1593
1594 @param parent parent widget (QWidget)
1595 @return reference to the instantiated options dialog
1596 (SvnNewProjectOptionsDialog)
1597 """
1598 from .SvnNewProjectOptionsDialog import SvnNewProjectOptionsDialog
1599 return SvnNewProjectOptionsDialog(self, parent)
1600
1601 def vcsRepositoryInfos(self, ppath):
1602 """
1603 Public method to retrieve information about the repository.
1604
1605 @param ppath local path to get the repository infos (string)
1606 @return string with ready formated info for display (string)
1607 """
1608 info = {
1609 'committed-rev': '',
1610 'committed-date': '',
1611 'committed-time': '',
1612 'url': '',
1613 'last-author': '',
1614 'revision': ''
1615 }
1616
1617 ioEncoding = Preferences.getSystem("IOEncoding")
1618
1619 process = QProcess()
1620 args = []
1621 args.append('info')
1622 args.append('--non-interactive')
1623 args.append('--xml')
1624 args.append(ppath)
1625 process.start('svn', args)
1626 procStarted = process.waitForStarted(5000)
1627 if procStarted:
1628 finished = process.waitForFinished(30000)
1629 if finished and process.exitCode() == 0:
1630 output = str(process.readAllStandardOutput(), ioEncoding,
1631 'replace')
1632 entryFound = False
1633 commitFound = False
1634 for line in output.splitlines():
1635 line = line.strip()
1636 if line.startswith('<entry'):
1637 entryFound = True
1638 elif line.startswith('<commit'):
1639 commitFound = True
1640 elif line.startswith('</commit>'):
1641 commitFound = False
1642 elif line.startswith("revision="):
1643 rev = line[line.find('"') + 1:line.rfind('"')]
1644 if entryFound:
1645 info['revision'] = rev
1646 entryFound = False
1647 elif commitFound:
1648 info['committed-rev'] = rev
1649 elif line.startswith('<url>'):
1650 info['url'] = (
1651 line.replace('<url>', '').replace('</url>', '')
1652 )
1653 elif line.startswith('<author>'):
1654 info['last-author'] = (
1655 line.replace('<author>', '')
1656 .replace('</author>', '')
1657 )
1658 elif line.startswith('<date>'):
1659 value = (
1660 line.replace('<date>', '').replace('</date>', '')
1661 )
1662 date, time = value.split('T')
1663 info['committed-date'] = date
1664 info['committed-time'] = "{0}{1}".format(
1665 time.split('.')[0], time[-1])
1666
1667 return QCoreApplication.translate(
1668 'subversion',
1669 """<h3>Repository information</h3>"""
1670 """<table>"""
1671 """<tr><td><b>Subversion V.</b></td><td>{0}</td></tr>"""
1672 """<tr><td><b>URL</b></td><td>{1}</td></tr>"""
1673 """<tr><td><b>Current revision</b></td><td>{2}</td></tr>"""
1674 """<tr><td><b>Committed revision</b></td><td>{3}</td></tr>"""
1675 """<tr><td><b>Committed date</b></td><td>{4}</td></tr>"""
1676 """<tr><td><b>Comitted time</b></td><td>{5}</td></tr>"""
1677 """<tr><td><b>Last author</b></td><td>{6}</td></tr>"""
1678 """</table>"""
1679 ).format(self.versionStr,
1680 info['url'],
1681 info['revision'],
1682 info['committed-rev'],
1683 info['committed-date'],
1684 info['committed-time'],
1685 info['last-author'])
1686
1687 ###########################################################################
1688 ## Public Subversion specific methods are below.
1689 ###########################################################################
1690
1691 def svnGetReposName(self, path):
1692 """
1693 Public method used to retrieve the URL of the subversion repository
1694 path.
1695
1696 @param path local path to get the svn repository path for (string)
1697 @return string with the repository path URL
1698 """
1699 ioEncoding = Preferences.getSystem("IOEncoding")
1700
1701 process = QProcess()
1702 args = []
1703 args.append('info')
1704 args.append('--xml')
1705 args.append('--non-interactive')
1706 args.append(path)
1707 process.start('svn', args)
1708 procStarted = process.waitForStarted(5000)
1709 if procStarted:
1710 finished = process.waitForFinished(30000)
1711 if finished and process.exitCode() == 0:
1712 output = str(process.readAllStandardOutput(), ioEncoding,
1713 'replace')
1714 for line in output.splitlines():
1715 line = line.strip()
1716 if line.startswith('<url>'):
1717 reposURL = (
1718 line.replace('<url>', '').replace('</url>', '')
1719 )
1720 return reposURL
1721
1722 return ""
1723
1724 def vcsResolved(self, name):
1725 """
1726 Public method used to resolve conflicts of a file/directory.
1727
1728 @param name file/directory name to be resolved (string)
1729 """
1730 args = []
1731 if self.version >= (1, 5, 0):
1732 args.append('resolve')
1733 args.append('--accept')
1734 args.append('working')
1735 else:
1736 args.append('resolved')
1737 self.addArguments(args, self.options['global'])
1738 if isinstance(name, list):
1739 self.addArguments(args, name)
1740 else:
1741 if os.path.isdir(name):
1742 args.append('--recursive')
1743 args.append(name)
1744
1745 dia = SvnDialog(self.tr('Resolving conficts'))
1746 res = dia.startProcess(args)
1747 if res:
1748 dia.exec()
1749 self.checkVCSStatus()
1750
1751 def svnCopy(self, name, project):
1752 """
1753 Public method used to copy a file/directory.
1754
1755 @param name file/directory name to be copied (string)
1756 @param project reference to the project object
1757 @return flag indicating successfull operation (boolean)
1758 """
1759 from .SvnCopyDialog import SvnCopyDialog
1760 rx_prot = re.compile('(file:|svn:|svn+ssh:|http:|https:).+')
1761 dlg = SvnCopyDialog(name)
1762 res = False
1763 if dlg.exec() == QDialog.DialogCode.Accepted:
1764 target, force = dlg.getData()
1765
1766 args = []
1767 args.append('copy')
1768 self.addArguments(args, self.options['global'])
1769 match = rx_prot.fullmatch(target)
1770 if match is not None:
1771 args.append('--message')
1772 args.append('Copying {0} to {1}'.format(name, target))
1773 target = self.__svnURL(target)
1774 args.append(name)
1775 args.append(target)
1776
1777 dia = SvnDialog(self.tr('Copying {0}')
1778 .format(name))
1779 res = dia.startProcess(args)
1780 if res:
1781 dia.exec()
1782 res = dia.normalExit()
1783 if (
1784 res and
1785 match is None and
1786 target.startswith(project.getProjectPath())
1787 ):
1788 if os.path.isdir(name):
1789 project.copyDirectory(name, target)
1790 else:
1791 project.appendFile(target)
1792 return res
1793
1794 def svnListProps(self, name, recursive=False):
1795 """
1796 Public method used to list the properties of a file/directory.
1797
1798 @param name file/directory name (string or list of strings)
1799 @param recursive flag indicating a recursive list is requested
1800 """
1801 if self.propList is None:
1802 from .SvnPropListDialog import SvnPropListDialog
1803 self.propList = SvnPropListDialog(self)
1804 self.propList.show()
1805 self.propList.raise_()
1806 self.propList.start(name, recursive)
1807
1808 def svnSetProp(self, name, recursive=False):
1809 """
1810 Public method used to add a property to a file/directory.
1811
1812 @param name file/directory name (string or list of strings)
1813 @param recursive flag indicating a recursive list is requested
1814 """
1815 from .SvnPropSetDialog import SvnPropSetDialog
1816 dlg = SvnPropSetDialog()
1817 if dlg.exec() == QDialog.DialogCode.Accepted:
1818 propName, fileFlag, propValue = dlg.getData()
1819 if not propName:
1820 EricMessageBox.critical(
1821 self.__ui,
1822 self.tr("Subversion Set Property"),
1823 self.tr("""You have to supply a property name."""
1824 """ Aborting."""))
1825 return
1826
1827 args = []
1828 args.append('propset')
1829 self.addArguments(args, self.options['global'])
1830 if recursive:
1831 args.append('--recursive')
1832 args.append(propName)
1833 if fileFlag:
1834 args.append('--file')
1835 args.append(propValue)
1836 if isinstance(name, list):
1837 dname, fnames = self.splitPathList(name)
1838 self.addArguments(args, fnames)
1839 else:
1840 dname, fname = self.splitPath(name)
1841 args.append(fname)
1842
1843 dia = SvnDialog(self.tr('Subversion Set Property'))
1844 res = dia.startProcess(args, dname)
1845 if res:
1846 dia.exec()
1847
1848 def svnDelProp(self, name, recursive=False):
1849 """
1850 Public method used to delete a property of a file/directory.
1851
1852 @param name file/directory name (string or list of strings)
1853 @param recursive flag indicating a recursive list is requested
1854 """
1855 propName, ok = QInputDialog.getText(
1856 None,
1857 self.tr("Subversion Delete Property"),
1858 self.tr("Enter property name"),
1859 QLineEdit.EchoMode.Normal)
1860
1861 if not ok:
1862 return
1863
1864 if not propName:
1865 EricMessageBox.critical(
1866 self.__ui,
1867 self.tr("Subversion Delete Property"),
1868 self.tr("""You have to supply a property name."""
1869 """ Aborting."""))
1870 return
1871
1872 args = []
1873 args.append('propdel')
1874 self.addArguments(args, self.options['global'])
1875 if recursive:
1876 args.append('--recursive')
1877 args.append(propName)
1878 if isinstance(name, list):
1879 dname, fnames = self.splitPathList(name)
1880 self.addArguments(args, fnames)
1881 else:
1882 dname, fname = self.splitPath(name)
1883 args.append(fname)
1884
1885 dia = SvnDialog(self.tr('Subversion Delete Property'))
1886 res = dia.startProcess(args, dname)
1887 if res:
1888 dia.exec()
1889
1890 def svnListTagBranch(self, path, tags=True):
1891 """
1892 Public method used to list the available tags or branches.
1893
1894 @param path directory name of the project (string)
1895 @param tags flag indicating listing of branches or tags
1896 (False = branches, True = tags)
1897 """
1898 if self.tagbranchList is None:
1899 from .SvnTagBranchListDialog import SvnTagBranchListDialog
1900 self.tagbranchList = SvnTagBranchListDialog(self)
1901 self.tagbranchList.show()
1902 self.tagbranchList.raise_()
1903 if tags:
1904 if not self.showedTags:
1905 self.showedTags = True
1906 allTagsBranchesList = self.allTagsBranchesList
1907 else:
1908 self.tagsList = []
1909 allTagsBranchesList = None
1910 self.tagbranchList.start(path, tags,
1911 self.tagsList, allTagsBranchesList)
1912 elif not tags:
1913 if not self.showedBranches:
1914 self.showedBranches = True
1915 allTagsBranchesList = self.allTagsBranchesList
1916 else:
1917 self.branchesList = []
1918 allTagsBranchesList = None
1919 self.tagbranchList.start(
1920 path, tags, self.branchesList, self.allTagsBranchesList)
1921
1922 def svnBlame(self, name):
1923 """
1924 Public method to show the output of the svn blame command.
1925
1926 @param name file name to show the blame for (string)
1927 """
1928 if self.blame is None:
1929 from .SvnBlameDialog import SvnBlameDialog
1930 self.blame = SvnBlameDialog(self)
1931 self.blame.show()
1932 self.blame.raise_()
1933 self.blame.start(name)
1934
1935 def svnExtendedDiff(self, name):
1936 """
1937 Public method used to view the difference of a file/directory to the
1938 Subversion repository.
1939
1940 If name is a directory and is the project directory, all project files
1941 are saved first. If name is a file (or list of files), which is/are
1942 being edited and has unsaved modification, they can be saved or the
1943 operation may be aborted.
1944
1945 This method gives the chance to enter the revisions to be compared.
1946
1947 @param name file/directory name to be diffed (string)
1948 """
1949 names = name[:] if isinstance(name, list) else [name]
1950 for nam in names:
1951 if os.path.isfile(nam):
1952 editor = ericApp().getObject("ViewManager").getOpenEditor(nam)
1953 if editor and not editor.checkDirty():
1954 return
1955 else:
1956 project = ericApp().getObject("Project")
1957 if nam == project.ppath and not project.saveAllScripts():
1958 return
1959 from .SvnRevisionSelectionDialog import SvnRevisionSelectionDialog
1960 dlg = SvnRevisionSelectionDialog()
1961 if dlg.exec() == QDialog.DialogCode.Accepted:
1962 revisions = dlg.getRevisions()
1963 from .SvnDiffDialog import SvnDiffDialog
1964 self.diff = SvnDiffDialog(self)
1965 self.diff.show()
1966 self.diff.start(name, revisions)
1967
1968 def svnUrlDiff(self, name):
1969 """
1970 Public method used to view the difference of a file/directory of two
1971 repository URLs.
1972
1973 If name is a directory and is the project directory, all project files
1974 are saved first. If name is a file (or list of files), which is/are
1975 being edited and has unsaved modification, they can be saved or the
1976 operation may be aborted.
1977
1978 This method gives the chance to enter the revisions to be compared.
1979
1980 @param name file/directory name to be diffed (string)
1981 """
1982 names = name[:] if isinstance(name, list) else [name]
1983 for nam in names:
1984 if os.path.isfile(nam):
1985 editor = ericApp().getObject("ViewManager").getOpenEditor(nam)
1986 if editor and not editor.checkDirty():
1987 return
1988 else:
1989 project = ericApp().getObject("Project")
1990 if nam == project.ppath and not project.saveAllScripts():
1991 return
1992
1993 dname = self.splitPath(names[0])[0]
1994
1995 from .SvnUrlSelectionDialog import SvnUrlSelectionDialog
1996 dlg = SvnUrlSelectionDialog(self, self.tagsList, self.branchesList,
1997 dname)
1998 if dlg.exec() == QDialog.DialogCode.Accepted:
1999 urls, summary = dlg.getURLs()
2000 from .SvnDiffDialog import SvnDiffDialog
2001 self.diff = SvnDiffDialog(self)
2002 self.diff.show()
2003 QApplication.processEvents()
2004 self.diff.start(name, urls=urls, summary=summary)
2005
2006 def __svnGetFileForRevision(self, name, rev=""):
2007 """
2008 Private method to get a file for a specific revision from the
2009 repository.
2010
2011 @param name file name to get from the repository (string)
2012 @param rev revision to retrieve (integer or string)
2013 @return contents of the file (string) and an error message (string)
2014 """
2015 args = []
2016 args.append("cat")
2017 if rev:
2018 args.append("--revision")
2019 args.append(str(rev))
2020 args.append(name)
2021
2022 output = ""
2023 error = ""
2024
2025 process = QProcess()
2026 process.start('svn', args)
2027 procStarted = process.waitForStarted(5000)
2028 if procStarted:
2029 finished = process.waitForFinished(30000)
2030 if finished:
2031 if process.exitCode() == 0:
2032 output = str(
2033 process.readAllStandardOutput(),
2034 Preferences.getSystem("IOEncoding"), 'replace')
2035 else:
2036 error = str(
2037 process.readAllStandardError(),
2038 Preferences.getSystem("IOEncoding"), 'replace')
2039 else:
2040 error = self.tr(
2041 "The svn process did not finish within 30s.")
2042 else:
2043 error = self.tr(
2044 'The process {0} could not be started. '
2045 'Ensure, that it is in the search path.').format('svn')
2046
2047 return output, error
2048
2049 def vcsSbsDiff(self, name, extended=False, revisions=None):
2050 """
2051 Public method used to view the difference of a file to the Mercurial
2052 repository side-by-side.
2053
2054 @param name file name to be diffed (string)
2055 @param extended flag indicating the extended variant (boolean)
2056 @param revisions tuple of two revisions (tuple of strings)
2057 @exception ValueError raised to indicate an illegal name parameter type
2058 """
2059 if isinstance(name, list):
2060 raise ValueError("Wrong parameter type")
2061
2062 if extended:
2063 from .SvnRevisionSelectionDialog import SvnRevisionSelectionDialog
2064 dlg = SvnRevisionSelectionDialog()
2065 if dlg.exec() == QDialog.DialogCode.Accepted:
2066 rev1, rev2 = dlg.getRevisions()
2067 if rev1 == "WORKING":
2068 rev1 = ""
2069 if rev2 == "WORKING":
2070 rev2 = ""
2071 else:
2072 return
2073 elif revisions:
2074 rev1, rev2 = revisions[0], revisions[1]
2075 else:
2076 rev1, rev2 = "", ""
2077
2078 output1, error = self.__svnGetFileForRevision(name, rev=rev1)
2079 if error:
2080 EricMessageBox.critical(
2081 self.__ui,
2082 self.tr("Subversion Side-by-Side Difference"),
2083 error)
2084 return
2085 name1 = "{0} (rev. {1})".format(name, rev1 and rev1 or ".")
2086
2087 if rev2:
2088 output2, error = self.__svnGetFileForRevision(name, rev=rev2)
2089 if error:
2090 EricMessageBox.critical(
2091 self.__ui,
2092 self.tr("Subversion Side-by-Side Difference"),
2093 error)
2094 return
2095 name2 = "{0} (rev. {1})".format(name, rev2)
2096 else:
2097 try:
2098 with open(name, "r", encoding="utf-8") as f1:
2099 output2 = f1.read()
2100 name2 = name
2101 except OSError:
2102 EricMessageBox.critical(
2103 self.__ui,
2104 self.tr("Subversion Side-by-Side Difference"),
2105 self.tr(
2106 """<p>The file <b>{0}</b> could not be read.</p>""")
2107 .format(name))
2108 return
2109
2110 if self.sbsDiff is None:
2111 from UI.CompareDialog import CompareDialog
2112 self.sbsDiff = CompareDialog()
2113 self.sbsDiff.show()
2114 self.sbsDiff.raise_()
2115 self.sbsDiff.compare(output1, output2, name1, name2)
2116
2117 def vcsLogBrowser(self, name, isFile=False):
2118 """
2119 Public method used to browse the log of a file/directory from the
2120 Subversion repository.
2121
2122 @param name file/directory name to show the log of (string)
2123 @param isFile flag indicating log for a file is to be shown (boolean)
2124 """
2125 if self.logBrowser is None:
2126 from .SvnLogBrowserDialog import SvnLogBrowserDialog
2127 self.logBrowser = SvnLogBrowserDialog(self)
2128 self.logBrowser.show()
2129 self.logBrowser.raise_()
2130 self.logBrowser.start(name, isFile=isFile)
2131
2132 def svnLock(self, name, stealIt=False, parent=None):
2133 """
2134 Public method used to lock a file in the Subversion repository.
2135
2136 @param name file/directory name to be locked (string or list of
2137 strings)
2138 @param stealIt flag indicating a forced operation (boolean)
2139 @param parent reference to the parent object of the subversion dialog
2140 (QWidget)
2141 """
2142 args = []
2143 args.append('lock')
2144 self.addArguments(args, self.options['global'])
2145 if stealIt:
2146 args.append('--force')
2147 if isinstance(name, list):
2148 dname, fnames = self.splitPathList(name)
2149 self.addArguments(args, fnames)
2150 else:
2151 dname, fname = self.splitPath(name)
2152 args.append(fname)
2153
2154 dia = SvnDialog(
2155 self.tr('Locking in the Subversion repository'), parent)
2156 res = dia.startProcess(args, dname)
2157 if res:
2158 dia.exec()
2159
2160 def svnUnlock(self, name, breakIt=False, parent=None):
2161 """
2162 Public method used to unlock a file in the Subversion repository.
2163
2164 @param name file/directory name to be unlocked (string or list of
2165 strings)
2166 @param breakIt flag indicating a forced operation (boolean)
2167 @param parent reference to the parent object of the subversion dialog
2168 (QWidget)
2169 """
2170 args = []
2171 args.append('unlock')
2172 self.addArguments(args, self.options['global'])
2173 if breakIt:
2174 args.append('--force')
2175 if isinstance(name, list):
2176 dname, fnames = self.splitPathList(name)
2177 self.addArguments(args, fnames)
2178 else:
2179 dname, fname = self.splitPath(name)
2180 args.append(fname)
2181
2182 dia = SvnDialog(
2183 self.tr('Unlocking in the Subversion repository'), parent)
2184 res = dia.startProcess(args, dname)
2185 if res:
2186 dia.exec()
2187
2188 def svnRelocate(self, projectPath):
2189 """
2190 Public method to relocate the working copy to a new repository URL.
2191
2192 @param projectPath path name of the project (string)
2193 """
2194 from .SvnRelocateDialog import SvnRelocateDialog
2195 currUrl = self.svnGetReposName(projectPath)
2196 dlg = SvnRelocateDialog(currUrl)
2197 if dlg.exec() == QDialog.DialogCode.Accepted:
2198 newUrl, inside = dlg.getData()
2199 args = []
2200 args.append('switch')
2201 if not inside:
2202 args.append('--relocate')
2203 args.append(currUrl)
2204 args.append(newUrl)
2205 args.append(projectPath)
2206
2207 dia = SvnDialog(self.tr('Relocating'))
2208 res = dia.startProcess(args)
2209 if res:
2210 dia.exec()
2211
2212 def svnRepoBrowser(self, projectPath=None):
2213 """
2214 Public method to open the repository browser.
2215
2216 @param projectPath path name of the project (string)
2217 """
2218 url = self.svnGetReposName(projectPath) if projectPath else None
2219
2220 if url is None:
2221 url, ok = QInputDialog.getText(
2222 None,
2223 self.tr("Repository Browser"),
2224 self.tr("Enter the repository URL."),
2225 QLineEdit.EchoMode.Normal)
2226 if not ok or not url:
2227 return
2228
2229 if self.repoBrowser is None:
2230 from .SvnRepoBrowserDialog import SvnRepoBrowserDialog
2231 self.repoBrowser = SvnRepoBrowserDialog(self)
2232 self.repoBrowser.show()
2233 self.repoBrowser.raise_()
2234 self.repoBrowser.start(url)
2235
2236 def svnRemoveFromChangelist(self, names):
2237 """
2238 Public method to remove a file or directory from its changelist.
2239
2240 Note: Directories will be removed recursively.
2241
2242 @param names name or list of names of file or directory to remove
2243 (string)
2244 """
2245 args = []
2246 args.append('changelist')
2247 self.addArguments(args, self.options['global'])
2248 args.append('--remove')
2249 args.append('--recursive')
2250 if isinstance(names, list):
2251 dname, fnames = self.splitPathList(names)
2252 self.addArguments(args, fnames)
2253 else:
2254 dname, fname = self.splitPath(names)
2255 args.append(fname)
2256
2257 dia = SvnDialog(self.tr('Remove from changelist'))
2258 res = dia.startProcess(args, dname)
2259 if res:
2260 dia.exec()
2261
2262 def svnAddToChangelist(self, names):
2263 """
2264 Public method to add a file or directory to a changelist.
2265
2266 Note: Directories will be added recursively.
2267
2268 @param names name or list of names of file or directory to add
2269 (string)
2270 """
2271 clname, ok = QInputDialog.getItem(
2272 None,
2273 self.tr("Add to changelist"),
2274 self.tr("Enter name of the changelist:"),
2275 sorted(self.svnGetChangelists()),
2276 0, True)
2277 if not ok or not clname:
2278 return
2279
2280 args = []
2281 args.append('changelist')
2282 self.addArguments(args, self.options['global'])
2283 args.append('--recursive')
2284 args.append(clname)
2285 if isinstance(names, list):
2286 dname, fnames = self.splitPathList(names)
2287 self.addArguments(args, fnames)
2288 else:
2289 dname, fname = self.splitPath(names)
2290 args.append(fname)
2291
2292 dia = SvnDialog(self.tr('Remove from changelist'))
2293 res = dia.startProcess(args, dname)
2294 if res:
2295 dia.exec()
2296
2297 def svnShowChangelists(self, path):
2298 """
2299 Public method used to inspect the change lists defined for the project.
2300
2301 @param path directory name to show change lists for (string)
2302 """
2303 from .SvnChangeListsDialog import SvnChangeListsDialog
2304 self.changeLists = SvnChangeListsDialog(self)
2305 self.changeLists.show()
2306 QApplication.processEvents()
2307 self.changeLists.start(path)
2308
2309 def svnGetChangelists(self):
2310 """
2311 Public method to get a list of all defined change lists.
2312
2313 @return list of defined change list names (list of strings)
2314 """
2315 changelists = []
2316 rx_changelist = re.compile('--- \\S+ .([\\w\\s]+).:\\s*')
2317 # three dashes, Changelist (translated), quote,
2318 # changelist name, quote, :
2319
2320 args = []
2321 args.append("status")
2322 args.append("--non-interactive")
2323 args.append(".")
2324
2325 ppath = ericApp().getObject("Project").getProjectPath()
2326 process = QProcess()
2327 process.setWorkingDirectory(ppath)
2328 process.start('svn', args)
2329 procStarted = process.waitForStarted(5000)
2330 if procStarted:
2331 finished = process.waitForFinished(30000)
2332 if finished and process.exitCode() == 0:
2333 output = str(process.readAllStandardOutput(),
2334 Preferences.getSystem("IOEncoding"),
2335 'replace')
2336 if output:
2337 for line in output.splitlines():
2338 match = rx_changelist.fullmatch(line)
2339 if match is not None:
2340 changelist = match.group(1)
2341 if changelist not in changelists:
2342 changelists.append(changelist)
2343
2344 return changelists
2345
2346 def svnUpgrade(self, path):
2347 """
2348 Public method to upgrade the working copy format.
2349
2350 @param path directory name to show change lists for (string)
2351 """
2352 args = []
2353 args.append("upgrade")
2354 args.append(".")
2355
2356 dia = SvnDialog(self.tr('Upgrade'))
2357 res = dia.startProcess(args, path)
2358 if res:
2359 dia.exec()
2360
2361 ###########################################################################
2362 ## Private Subversion specific methods are below.
2363 ###########################################################################
2364
2365 def __svnURL(self, url):
2366 """
2367 Private method to format a url for subversion.
2368
2369 @param url unformatted url string (string)
2370 @return properly formated url for subversion (string)
2371 """
2372 url = self.svnNormalizeURL(url)
2373 url = url.split(':', 2)
2374 if len(url) == 3:
2375 scheme = url[0]
2376 host = url[1]
2377 port, path = url[2].split("/", 1)
2378 return "{0}:{1}:{2}/{3}".format(scheme, host, port, quote(path))
2379 else:
2380 scheme = url[0]
2381 if scheme == "file":
2382 return "{0}:{1}".format(scheme, quote(url[1]))
2383 else:
2384 try:
2385 host, path = url[1][2:].split("/", 1)
2386 except ValueError:
2387 host = url[1][2:]
2388 path = ""
2389 return "{0}://{1}/{2}".format(scheme, host, quote(path))
2390
2391 def svnNormalizeURL(self, url):
2392 """
2393 Public method to normalize a url for subversion.
2394
2395 @param url url string (string)
2396 @return properly normalized url for subversion (string)
2397 """
2398 protocol, url = url.split("://", 1)
2399 if url.startswith("\\\\"):
2400 url = url[2:]
2401 if protocol == "file":
2402 url = os.path.normcase(url)
2403 url = url.replace('\\', '/')
2404 if url.endswith('/'):
2405 url = url[:-1]
2406 if not url.startswith("/") and url[1] in [":", "|"]:
2407 url = "/{0}".format(url)
2408 return "{0}://{1}".format(protocol, url)
2409
2410 ###########################################################################
2411 ## Methods to get the helper objects are below.
2412 ###########################################################################
2413
2414 def vcsGetProjectBrowserHelper(self, browser, project,
2415 isTranslationsBrowser=False):
2416 """
2417 Public method to instanciate a helper object for the different
2418 project browsers.
2419
2420 @param browser reference to the project browser object
2421 @param project reference to the project object
2422 @param isTranslationsBrowser flag indicating, the helper is requested
2423 for the translations browser (this needs some special treatment)
2424 @return the project browser helper object
2425 """
2426 from .ProjectBrowserHelper import SvnProjectBrowserHelper
2427 return SvnProjectBrowserHelper(self, browser, project,
2428 isTranslationsBrowser)
2429
2430 def vcsGetProjectHelper(self, project):
2431 """
2432 Public method to instanciate a helper object for the project.
2433
2434 @param project reference to the project object
2435 @return the project helper object
2436 """
2437 helper = self.__plugin.getProjectHelper()
2438 helper.setObjects(self, project)
2439 self.__wcng = (
2440 os.path.exists(
2441 os.path.join(project.getProjectPath(), ".svn", "format")) or
2442 os.path.exists(
2443 os.path.join(project.getProjectPath(), "_svn", "format")) or
2444 os.path.exists(
2445 os.path.join(project.getProjectPath(), ".svn", "wc.db")) or
2446 os.path.exists(
2447 os.path.join(project.getProjectPath(), "_svn", "wc.db"))
2448 )
2449 return helper
2450
2451 ###########################################################################
2452 ## Status Monitor Thread methods
2453 ###########################################################################
2454
2455 def _createStatusMonitorThread(self, interval, project):
2456 """
2457 Protected method to create an instance of the VCS status monitor
2458 thread.
2459
2460 @param interval check interval for the monitor thread in seconds
2461 (integer)
2462 @param project reference to the project object
2463 @return reference to the monitor thread (QThread)
2464 """
2465 from .SvnStatusMonitorThread import SvnStatusMonitorThread
2466 return SvnStatusMonitorThread(interval, project, self)

eric ide

mercurial