eric6/Plugins/VcsPlugins/vcsSubversion/subversion.py

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

eric ide

mercurial