Plugins/VcsPlugins/vcsSubversion/subversion.py

changeset 0
de9c2efb9d02
child 6
52e8c820d0dd
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2009 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 shutil
12 import types
13 import urllib
14
15 from PyQt4.QtCore import *
16 from PyQt4.QtGui import *
17
18 from E4Gui.E4Application import e4App
19
20 from VCS.VersionControl import VersionControl
21
22 from SvnDialog import SvnDialog
23 from SvnCommitDialog import SvnCommitDialog
24 from SvnLogDialog import SvnLogDialog
25 from SvnLogBrowserDialog import SvnLogBrowserDialog
26 from SvnDiffDialog import SvnDiffDialog
27 from SvnRevisionSelectionDialog import SvnRevisionSelectionDialog
28 from SvnStatusDialog import SvnStatusDialog
29 from SvnTagDialog import SvnTagDialog
30 from SvnTagBranchListDialog import SvnTagBranchListDialog
31 from SvnCopyDialog import SvnCopyDialog
32 from SvnCommandDialog import SvnCommandDialog
33 from SvnSwitchDialog import SvnSwitchDialog
34 from SvnMergeDialog import SvnMergeDialog
35 from SvnPropListDialog import SvnPropListDialog
36 from SvnPropSetDialog import SvnPropSetDialog
37 from SvnOptionsDialog import SvnOptionsDialog
38 from SvnNewProjectOptionsDialog import SvnNewProjectOptionsDialog
39 from SvnBlameDialog import SvnBlameDialog
40 from SvnRelocateDialog import SvnRelocateDialog
41 from SvnUrlSelectionDialog import SvnUrlSelectionDialog
42 from SvnRepoBrowserDialog import SvnRepoBrowserDialog
43 from SvnStatusMonitorThread import SvnStatusMonitorThread
44
45 from ProjectBrowserHelper import SvnProjectBrowserHelper
46 from ProjectHelper import SvnProjectHelper
47
48 import Preferences
49 import Utilities
50
51 class Subversion(VersionControl):
52 """
53 Class implementing the version control systems interface to Subversion.
54
55 @signal committed() emitted after the commit action has completed
56 """
57 def __init__(self, plugin, parent=None, name=None):
58 """
59 Constructor
60
61 @param plugin reference to the plugin object
62 @param parent parent widget (QWidget)
63 @param name name of this object (string)
64 """
65 VersionControl.__init__(self, parent, name)
66 self.defaultOptions = {
67 'global' : [''],
68 'commit' : [''],
69 'checkout' : [''],
70 'update' : [''],
71 'add' : [''],
72 'remove' : [''],
73 'diff' : [''],
74 'log' : [''],
75 'history' : [''],
76 'status' : [''],
77 'tag' : [''],
78 'export' : ['']
79 }
80 self.interestingDataKeys = [
81 "standardLayout",
82 ]
83
84 self.__plugin = plugin
85 self.__ui = parent
86
87 self.options = self.defaultOptions
88 self.otherData["standardLayout"] = True
89 self.tagsList = []
90 self.branchesList = []
91 self.allTagsBranchesList = []
92 self.mergeList = [[], [], []]
93 self.showedTags = False
94 self.showedBranches = False
95
96 self.tagTypeList = [
97 'tags',
98 'branches',
99 ]
100
101 self.commandHistory = []
102 self.wdHistory = []
103
104 if os.environ.has_key("SVN_ASP_DOT_NET_HACK"):
105 self.adminDir = '_svn'
106 else:
107 self.adminDir = '.svn'
108
109 self.log = None
110 self.diff = None
111 self.status = None
112 self.propList = None
113 self.tagbranchList = None
114 self.blame = None
115 self.repoBrowser = None
116
117 # regular expression object for evaluation of the status output
118 self.rx_status1 = QRegExp('(.{8})\\s+([0-9-]+)\\s+([0-9?]+)\\s+([\\w?]+)\\s+(.+)')
119 self.rx_status2 = QRegExp('(.{8})\\s+(.+)\\s*')
120 self.statusCache = {}
121
122 self.__commitData = {}
123 self.__commitDialog = None
124
125 def getPlugin(self):
126 """
127 Public method to get a reference to the plugin object.
128
129 @return reference to the plugin object (VcsSubversionPlugin)
130 """
131 return self.__plugin
132
133 def vcsShutdown(self):
134 """
135 Public method used to shutdown the Subversion interface.
136 """
137 if self.log is not None:
138 self.log.close()
139 if self.diff is not None:
140 self.diff.close()
141 if self.status is not None:
142 self.status.close()
143 if self.propList is not None:
144 self.propList.close()
145 if self.tagbranchList is not None:
146 self.tagbranchList.close()
147 if self.blame is not None:
148 self.blame.close()
149 if self.repoBrowser is not None:
150 self.repoBrowser.close()
151
152 def vcsExists(self):
153 """
154 Public method used to test for the presence of the svn executable.
155
156 @return flag indicating the existance (boolean) and an error message (string)
157 """
158 self.versionStr = ''
159 errMsg = ""
160 ioEncoding = Preferences.getSystem("IOEncoding")
161
162 process = QProcess()
163 process.start('svn', ['--version'])
164 procStarted = process.waitForStarted()
165 if procStarted:
166 finished = process.waitForFinished(30000)
167 if finished and process.exitCode() == 0:
168 output = \
169 unicode(process.readAllStandardOutput(), ioEncoding, 'replace')
170 self.versionStr = output.split()[2]
171 return True, errMsg
172 else:
173 if finished:
174 errMsg = \
175 self.trUtf8("The svn process finished with the exit code {0}")\
176 .format(process.exitCode())
177 else:
178 errMsg = self.trUtf8("The svn process did not finish within 30s.")
179 else:
180 errMsg = self.trUtf8("Could not start the svn executable.")
181
182 return False, errMsg
183
184 def vcsInit(self, vcsDir, noDialog = False):
185 """
186 Public method used to initialize the subversion repository.
187
188 The subversion repository has to be initialized from outside eric4
189 because the respective command always works locally. Therefore we
190 always return TRUE without doing anything.
191
192 @param vcsDir name of the VCS directory (string)
193 @param noDialog flag indicating quiet operations (boolean)
194 @return always TRUE
195 """
196 return True
197
198 def vcsConvertProject(self, vcsDataDict, project):
199 """
200 Public method to convert an uncontrolled project to a version controlled project.
201
202 @param vcsDataDict dictionary of data required for the conversion
203 @param project reference to the project object
204 """
205 success = self.vcsImport(vcsDataDict, project.ppath)[0]
206 if not success:
207 QMessageBox.critical(None,
208 self.trUtf8("Create project in repository"),
209 self.trUtf8("""The project could not be created in the repository."""
210 """ Maybe the given repository doesn't exist or the"""
211 """ repository server is down."""))
212 else:
213 cwdIsPpath = False
214 if os.getcwd() == project.ppath:
215 os.chdir(os.path.dirname(project.ppath))
216 cwdIsPpath = True
217 tmpProjectDir = "%s_tmp" % project.ppath
218 shutil.rmtree(tmpProjectDir, True)
219 os.rename(project.ppath, tmpProjectDir)
220 os.makedirs(project.ppath)
221 self.vcsCheckout(vcsDataDict, project.ppath)
222 if cwdIsPpath:
223 os.chdir(project.ppath)
224 self.vcsCommit(project.ppath, vcsDataDict["message"], True)
225 pfn = project.pfile
226 if not os.path.isfile(pfn):
227 pfn += "z"
228 if not os.path.isfile(pfn):
229 QMessageBox.critical(None,
230 self.trUtf8("New project"),
231 self.trUtf8("""The project could not be checked out of the"""
232 """ repository.<br />"""
233 """Restoring the original contents."""))
234 if os.getcwd() == project.ppath:
235 os.chdir(os.path.dirname(project.ppath))
236 cwdIsPpath = True
237 else:
238 cwdIsPpath = False
239 shutil.rmtree(project.ppath, True)
240 os.rename(tmpProjectDir, project.ppath)
241 project.pdata["VCS"] = ['None']
242 project.vcs = None
243 project.setDirty(True)
244 project.saveProject()
245 project.closeProject()
246 return
247 shutil.rmtree(tmpProjectDir, True)
248 project.openProject(pfn)
249
250 def vcsImport(self, vcsDataDict, projectDir, noDialog = False):
251 """
252 Public method used to import the project into the Subversion repository.
253
254 @param vcsDataDict dictionary of data required for the import
255 @param projectDir project directory (string)
256 @param noDialog flag indicating quiet operations
257 @return flag indicating an execution without errors (boolean)
258 and a flag indicating the version controll status (boolean)
259 """
260 noDialog = False
261 msg = vcsDataDict["message"]
262 if not msg:
263 msg = '***'
264
265 vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
266 if vcsDir.startswith('/'):
267 vcsDir = 'file://%s' % vcsDir
268 elif vcsDir[1] in ['|', ':']:
269 vcsDir = 'file:///%s' % vcsDir
270
271 project = vcsDir[vcsDir.rfind('/')+1:]
272
273 # create the dir structure to be imported into the repository
274 tmpDir = '%s_tmp' % projectDir
275 try:
276 os.makedirs(tmpDir)
277 if self.otherData["standardLayout"]:
278 os.mkdir(os.path.join(tmpDir, project))
279 os.mkdir(os.path.join(tmpDir, project, 'branches'))
280 os.mkdir(os.path.join(tmpDir, project, 'tags'))
281 shutil.copytree(projectDir, os.path.join(tmpDir, project, 'trunk'))
282 else:
283 shutil.copytree(projectDir, os.path.join(tmpDir, project))
284 except OSError, e:
285 if os.path.isdir(tmpDir):
286 shutil.rmtree(tmpDir, True)
287 return False, False
288
289 args = []
290 args.append('import')
291 self.addArguments(args, self.options['global'])
292 args.append('-m')
293 args.append(msg)
294 args.append(self.__svnURL(vcsDir))
295
296 if noDialog:
297 status = self.startSynchronizedProcess(QProcess(), "svn", args,
298 os.path.join(tmpDir, project))
299 else:
300 dia = SvnDialog(self.trUtf8('Importing project into Subversion repository'))
301 res = dia.startProcess(args, os.path.join(tmpDir, project))
302 if res:
303 dia.exec_()
304 status = dia.normalExit()
305
306 shutil.rmtree(tmpDir, True)
307 return status, False
308
309 def vcsCheckout(self, vcsDataDict, projectDir, noDialog = False):
310 """
311 Public method used to check the project out of the Subversion repository.
312
313 @param vcsDataDict dictionary of data required for the checkout
314 @param projectDir project directory to create (string)
315 @param noDialog flag indicating quiet operations
316 @return flag indicating an execution without errors (boolean)
317 """
318 noDialog = False
319 try:
320 tag = vcsDataDict["tag"]
321 except KeyError:
322 tag = None
323 vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
324 if vcsDir.startswith('/'):
325 vcsDir = 'file://%s' % vcsDir
326 elif vcsDir[1] in ['|', ':']:
327 vcsDir = 'file:///%s' % vcsDir
328
329 if self.otherData["standardLayout"]:
330 if tag is None or tag == '':
331 svnUrl = '%s/trunk' % vcsDir
332 else:
333 if not tag.startswith('tags') and not tag.startswith('branches'):
334 type, ok = QInputDialog.getItem(\
335 None,
336 self.trUtf8("Subversion Checkout"),
337 self.trUtf8("The tag must be a normal tag (tags) or"
338 " a branch tag (branches)."
339 " Please select from the list."),
340 self.tagTypeList,
341 0, False)
342 if not ok:
343 return False
344 tag = '%s/%s' % (type, tag)
345 svnUrl = '%s/%s' % (vcsDir, tag)
346 else:
347 svnUrl = vcsDir
348
349 args = []
350 args.append('checkout')
351 self.addArguments(args, self.options['global'])
352 self.addArguments(args, self.options['checkout'])
353 args.append(self.__svnURL(svnUrl))
354 args.append(projectDir)
355
356 if noDialog:
357 return self.startSynchronizedProcess(QProcess(), 'svn', args)
358 else:
359 dia = SvnDialog(self.trUtf8('Checking project out of Subversion repository'))
360 res = dia.startProcess(args)
361 if res:
362 dia.exec_()
363 return dia.normalExit()
364
365 def vcsExport(self, vcsDataDict, projectDir):
366 """
367 Public method used to export a directory from the Subversion repository.
368
369 @param vcsDataDict dictionary of data required for the checkout
370 @param projectDir project directory to create (string)
371 @return flag indicating an execution without errors (boolean)
372 """
373 try:
374 tag = vcsDataDict["tag"]
375 except KeyError:
376 tag = None
377 vcsDir = self.svnNormalizeURL(vcsDataDict["url"])
378 if vcsDir.startswith('/') or vcsDir[1] == '|':
379 vcsDir = 'file://%s' % vcsDir
380
381 if self.otherData["standardLayout"]:
382 if tag is None or tag == '':
383 svnUrl = '%s/trunk' % vcsDir
384 else:
385 if not tag.startswith('tags') and not tag.startswith('branches'):
386 type, ok = QInputDialog.getItem(\
387 None,
388 self.trUtf8("Subversion Export"),
389 self.trUtf8("The tag must be a normal tag (tags) or"
390 " a branch tag (branches)."
391 " Please select from the list."),
392 self.tagTypeList,
393 0, False)
394 if not ok:
395 return False
396 tag = '%s/%s' % (unicode(type), tag)
397 svnUrl = '%s/%s' % (vcsDir, tag)
398 else:
399 svnUrl = vcsDir
400
401 args = []
402 args.append('export')
403 self.addArguments(args, self.options['global'])
404 args.append("--force")
405 args.append(self.__svnURL(svnUrl))
406 args.append(projectDir)
407
408 dia = SvnDialog(self.trUtf8('Exporting project from Subversion repository'))
409 res = dia.startProcess(args)
410 if res:
411 dia.exec_()
412 return dia.normalExit()
413
414 def vcsCommit(self, name, message, noDialog = False):
415 """
416 Public method used to make the change of a file/directory permanent in the
417 Subversion repository.
418
419 @param name file/directory name to be committed (string or list of strings)
420 @param message message for this operation (string)
421 @param noDialog flag indicating quiet operations
422 """
423 msg = message
424
425 if not noDialog and not msg:
426 # call CommitDialog and get message from there
427 if self.__commitDialog is None:
428 self.__commitDialog = SvnCommitDialog(self, self.__ui)
429 self.connect(self.__commitDialog, SIGNAL("accepted()"),
430 self.__vcsCommit_Step2)
431 self.__commitDialog.show()
432 self.__commitDialog.raise_()
433 self.__commitDialog.activateWindow()
434
435 self.__commitData["name"] = name
436 self.__commitData["msg"] = msg
437 self.__commitData["noDialog"] = noDialog
438
439 if noDialog:
440 self.__vcsCommit_Step2()
441
442 def __vcsCommit_Step2(self):
443 """
444 Private slot performing the second step of the commit action.
445 """
446 name = self.__commitData["name"]
447 msg = self.__commitData["msg"]
448 noDialog = self.__commitData["noDialog"]
449
450 if self.__commitDialog is not None:
451 msg = self.__commitDialog.logMessage()
452 if self.__commitDialog.hasChangelists():
453 changelists, keepChangelists = self.__commitDialog.changelistsData()
454 else:
455 changelists, keepChangelists = [], False
456 self.disconnect(self.__commitDialog, SIGNAL("accepted()"),
457 self.__vcsCommit_Step2)
458 self.__commitDialog = None
459 else:
460 changelists, keepChangelists = [], False
461
462 if not msg:
463 msg = '***'
464
465 args = []
466 args.append('commit')
467 self.addArguments(args, self.options['global'])
468 self.addArguments(args, self.options['commit'])
469 if keepChangelists:
470 args.append("--keep-changelists")
471 for changelist in changelists:
472 args.append("--changelist")
473 args.append(changelist)
474 args.append("-m")
475 args.append(msg)
476 if type(name) is types.ListType:
477 dname, fnames = self.splitPathList(name)
478 self.addArguments(args, fnames)
479 else:
480 dname, fname = self.splitPath(name)
481 args.append(fname)
482
483 if self.svnGetReposName(dname).startswith('http') or \
484 self.svnGetReposName(dname).startswith('svn'):
485 noDialog = False
486
487 if noDialog:
488 self.startSynchronizedProcess(QProcess(), "svn", args, dname)
489 else:
490 dia = SvnDialog(self.trUtf8('Commiting changes to Subversion repository'))
491 res = dia.startProcess(args, dname)
492 if res:
493 dia.exec_()
494 self.emit(SIGNAL("committed()"))
495 self.checkVCSStatus()
496
497 def vcsUpdate(self, name, noDialog = False):
498 """
499 Public method used to update a file/directory with the Subversion repository.
500
501 @param name file/directory name to be updated (string or list of strings)
502 @param noDialog flag indicating quiet operations (boolean)
503 @return flag indicating, that the update contained an add
504 or delete (boolean)
505 """
506 args = []
507 args.append('update')
508 self.addArguments(args, self.options['global'])
509 self.addArguments(args, self.options['update'])
510 if self.versionStr >= '1.5.0':
511 args.append('--accept')
512 args.append('postpone')
513 if type(name) is types.ListType:
514 dname, fnames = self.splitPathList(name)
515 self.addArguments(args, fnames)
516 else:
517 dname, fname = self.splitPath(name)
518 args.append(fname)
519
520 if noDialog:
521 self.startSynchronizedProcess(QProcess(), "svn", args, dname)
522 res = False
523 else:
524 dia = SvnDialog(self.trUtf8('Synchronizing with the Subversion repository'))
525 res = dia.startProcess(args, dname)
526 if res:
527 dia.exec_()
528 res = dia.hasAddOrDelete()
529 self.checkVCSStatus()
530 return res
531
532 def vcsAdd(self, name, isDir = False, noDialog = False):
533 """
534 Public method used to add a file/directory to the Subversion repository.
535
536 @param name file/directory name to be added (string)
537 @param isDir flag indicating name is a directory (boolean)
538 @param noDialog flag indicating quiet operations
539 """
540 args = []
541 args.append('add')
542 self.addArguments(args, self.options['global'])
543 self.addArguments(args, self.options['add'])
544 args.append('--non-recursive')
545 if noDialog and '--force' not in args:
546 args.append('--force')
547
548 if type(name) is types.ListType:
549 if isDir:
550 dname, fname = os.path.split(name[0])
551 else:
552 dname, fnames = self.splitPathList(name)
553 else:
554 if isDir:
555 dname, fname = os.path.split(name)
556 else:
557 dname, fname = self.splitPath(name)
558 tree = []
559 wdir = dname
560 while not os.path.exists(os.path.join(dname, self.adminDir)):
561 # add directories recursively, if they aren't in the repository already
562 tree.insert(-1, dname)
563 dname = os.path.split(dname)[0]
564 wdir = dname
565 self.addArguments(args, tree)
566
567 if type(name) is types.ListType:
568 tree2 = []
569 for n in name:
570 d = os.path.split(n)[0]
571 while not os.path.exists(os.path.join(d, self.adminDir)):
572 if d in tree2 + tree:
573 break
574 tree2.append(d)
575 d = os.path.split(d)[0]
576 tree2.reverse()
577 self.addArguments(args, tree2)
578 self.addArguments(args, name)
579 else:
580 args.append(name)
581
582 if noDialog:
583 self.startSynchronizedProcess(QProcess(), "svn", args, wdir)
584 else:
585 dia = SvnDialog(\
586 self.trUtf8('Adding files/directories to the Subversion repository'))
587 res = dia.startProcess(args, wdir)
588 if res:
589 dia.exec_()
590
591 def vcsAddBinary(self, name, isDir = False):
592 """
593 Public method used to add a file/directory in binary mode to the
594 Subversion repository.
595
596 @param name file/directory name to be added (string)
597 @param isDir flag indicating name is a directory (boolean)
598 """
599 self.vcsAdd(name, isDir)
600
601 def vcsAddTree(self, path):
602 """
603 Public method to add a directory tree rooted at path to the Subversion repository.
604
605 @param path root directory of the tree to be added (string or list of strings))
606 """
607 args = []
608 args.append('add')
609 self.addArguments(args, self.options['global'])
610 self.addArguments(args, self.options['add'])
611
612 tree = []
613 if type(path) is types.ListType:
614 dname, fnames = self.splitPathList(path)
615 for n in path:
616 d = os.path.split(n)[0]
617 while not os.path.exists(os.path.join(d, self.adminDir)):
618 # add directories recursively,
619 # if they aren't in the repository already
620 if d in tree:
621 break
622 tree.append(d)
623 d = os.path.split(d)[0]
624 tree.reverse()
625 else:
626 dname, fname = os.path.split(path)
627 while not os.path.exists(os.path.join(dname, self.adminDir)):
628 # add directories recursively,
629 # if they aren't in the repository already
630 tree.insert(-1, dname)
631 dname = os.path.split(dname)[0]
632 if tree:
633 self.vcsAdd(tree, True)
634
635 if type(path) is types.ListType:
636 self.addArguments(args, path)
637 else:
638 args.append(path)
639
640 dia = SvnDialog(\
641 self.trUtf8('Adding directory trees to the Subversion repository'))
642 res = dia.startProcess(args, dname)
643 if res:
644 dia.exec_()
645
646 def vcsRemove(self, name, project = False, noDialog = False):
647 """
648 Public method used to remove a file/directory from the Subversion repository.
649
650 The default operation is to remove the local copy as well.
651
652 @param name file/directory name to be removed (string or list of strings))
653 @param project flag indicating deletion of a project tree (boolean) (not needed)
654 @param noDialog flag indicating quiet operations
655 @return flag indicating successfull operation (boolean)
656 """
657 args = []
658 args.append('delete')
659 self.addArguments(args, self.options['global'])
660 self.addArguments(args, self.options['remove'])
661 if noDialog and '--force' not in args:
662 args.append('--force')
663
664 if type(name) is types.ListType:
665 self.addArguments(args, name)
666 else:
667 args.append(name)
668
669 if noDialog:
670 res = self.startSynchronizedProcess(QProcess(), "svn", args)
671 else:
672 dia = SvnDialog(\
673 self.trUtf8('Removing files/directories from the Subversion repository'))
674 res = dia.startProcess(args)
675 if res:
676 dia.exec_()
677 res = dia.normalExit()
678
679 return res
680
681 def vcsMove(self, name, project, target = None, noDialog = False):
682 """
683 Public method used to move a file/directory.
684
685 @param name file/directory name to be moved (string)
686 @param project reference to the project object
687 @param target new name of the file/directory (string)
688 @param noDialog flag indicating quiet operations
689 @return flag indicating successfull operation (boolean)
690 """
691 rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+')
692 opts = self.options['global'][:]
693 force = '--force' in opts
694 if force:
695 del opts[opts.index('--force')]
696
697 res = False
698 if noDialog:
699 if target is None:
700 return False
701 force = True
702 accepted = True
703 else:
704 dlg = SvnCopyDialog(name, None, True, force)
705 accepted = (dlg.exec_() == QDialog.Accepted)
706 if accepted:
707 target, force = dlg.getData()
708
709 if accepted:
710 isdir = os.path.isdir(name)
711 args = []
712 args.append('move')
713 self.addArguments(args, opts)
714 if force:
715 args.append('--force')
716 if rx_prot.exactMatch(target):
717 args.append('--message')
718 args.append('Moving {0} to {1}'.format(name, target))
719 target = self.__svnURL(target)
720 else:
721 target = unicode(target)
722 args.append(name)
723 args.append(target)
724
725 if noDialog:
726 res = self.startSynchronizedProcess(QProcess(), "svn", args)
727 else:
728 dia = SvnDialog(self.trUtf8('Moving {0}')
729 .format(name))
730 res = dia.startProcess(args)
731 if res:
732 dia.exec_()
733 res = dia.normalExit()
734 if res and not rx_prot.exactMatch(target):
735 if target.startswith(project.getProjectPath()):
736 if os.path.isdir(name):
737 project.moveDirectory(name, target)
738 else:
739 project.renameFileInPdata(name, target)
740 else:
741 if os.path.isdir(name):
742 project.removeDirectory(name)
743 else:
744 project.removeFile(name)
745 return res
746
747 def vcsLog(self, name):
748 """
749 Public method used to view the log of a file/directory from the
750 Subversion repository.
751
752 @param name file/directory name to show the log of (string)
753 """
754 self.log = SvnLogDialog(self)
755 self.log.show()
756 self.log.start(name)
757
758 def vcsDiff(self, name):
759 """
760 Public method used to view the difference of a file/directory to the
761 Subversion repository.
762
763 If name is a directory and is the project directory, all project files
764 are saved first. If name is a file (or list of files), which is/are being edited
765 and has unsaved modification, they can be saved or the operation may be aborted.
766
767 @param name file/directory name to be diffed (string)
768 """
769 if type(name) is types.ListType:
770 names = name[:]
771 else:
772 names = [name]
773 for nam in names:
774 if os.path.isfile(nam):
775 editor = e4App().getObject("ViewManager").getOpenEditor(nam)
776 if editor and not editor.checkDirty() :
777 return
778 else:
779 project = e4App().getObject("Project")
780 if nam == project.ppath and not project.saveAllScripts():
781 return
782 self.diff = SvnDiffDialog(self)
783 self.diff.show()
784 QApplication.processEvents()
785 self.diff.start(name)
786
787 def vcsStatus(self, name):
788 """
789 Public method used to view the status of files/directories in the
790 Subversion repository.
791
792 @param name file/directory name(s) to show the status of
793 (string or list of strings)
794 """
795 self.status = SvnStatusDialog(self)
796 self.status.show()
797 self.status.start(name)
798
799 def vcsTag(self, name):
800 """
801 Public method used to set the tag of a file/directory in the
802 Subversion repository.
803
804 @param name file/directory name to be tagged (string)
805 """
806 dname, fname = self.splitPath(name)
807
808 reposURL = self.svnGetReposName(dname)
809 if reposURL is None:
810 QMessageBox.critical(None,
811 self.trUtf8("Subversion Error"),
812 self.trUtf8("""The URL of the project repository could not be"""
813 """ retrieved from the working copy. The tag operation will"""
814 """ be aborted"""))
815 return
816
817 if self.otherData["standardLayout"]:
818 url = None
819 else:
820 url = self.svnNormalizeURL(reposURL)
821 dlg = SvnTagDialog(self.allTagsBranchesList, url,
822 self.otherData["standardLayout"])
823 if dlg.exec_() == QDialog.Accepted:
824 tag, tagOp = dlg.getParameters()
825 if tag in self.allTagsBranchesList:
826 self.allTagsBranchesList.remove(tag)
827 self.allTagsBranchesList.insert(0, tag)
828 else:
829 return
830
831 if self.otherData["standardLayout"]:
832 rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
833 if not rx_base.exactMatch(reposURL):
834 QMessageBox.critical(None,
835 self.trUtf8("Subversion Error"),
836 self.trUtf8("""The URL of the project repository has an"""
837 """ invalid format. The tag operation will"""
838 """ be aborted"""))
839 return
840
841 reposRoot = rx_base.cap(1)
842 if tagOp in [1, 4]:
843 url = '%s/tags/%s' % (reposRoot, urllib.quote(tag))
844 elif tagOp in [2, 8]:
845 url = '%s/branches/%s' % (reposRoot, urllib.quote(tag))
846 else:
847 url = self.__svnURL(tag)
848
849 args = []
850 if tagOp in [1, 2]:
851 args.append('copy')
852 self.addArguments(args, self.options['global'])
853 self.addArguments(args, self.options['tag'])
854 args.append('--message')
855 args.append('Created tag <%s>' % tag)
856 args.append(reposURL)
857 args.append(url)
858 else:
859 args.append('delete')
860 self.addArguments(args, self.options['global'])
861 self.addArguments(args, self.options['tag'])
862 args.append('--message')
863 args.append('Deleted tag <%s>' % tag)
864 args.append(url)
865
866 dia = SvnDialog(self.trUtf8('Tagging {0} in the Subversion repository')
867 .format(name))
868 res = dia.startProcess(args)
869 if res:
870 dia.exec_()
871
872 def vcsRevert(self, name):
873 """
874 Public method used to revert changes made to a file/directory.
875
876 @param name file/directory name to be reverted (string)
877 """
878 args = []
879 args.append('revert')
880 self.addArguments(args, self.options['global'])
881 if type(name) is types.ListType:
882 self.addArguments(args, name)
883 else:
884 if os.path.isdir(name):
885 args.append('--recursive')
886 args.append(name)
887
888 dia = SvnDialog(self.trUtf8('Reverting changes'))
889 res = dia.startProcess(args)
890 if res:
891 dia.exec_()
892 self.checkVCSStatus()
893
894 def vcsSwitch(self, name):
895 """
896 Public method used to switch a directory to a different tag/branch.
897
898 @param name directory name to be switched (string)
899 """
900 dname, fname = self.splitPath(name)
901
902 reposURL = self.svnGetReposName(dname)
903 if reposURL is None:
904 QMessageBox.critical(None,
905 self.trUtf8("Subversion Error"),
906 self.trUtf8("""The URL of the project repository could not be"""
907 """ retrieved from the working copy. The switch operation will"""
908 """ be aborted"""))
909 return
910
911 if self.otherData["standardLayout"]:
912 url = None
913 else:
914 url = self.svnNormalizeURL(reposURL)
915 dlg = SvnSwitchDialog(self.allTagsBranchesList, url,
916 self.otherData["standardLayout"])
917 if dlg.exec_() == QDialog.Accepted:
918 tag, tagType = dlg.getParameters()
919 if tag in self.allTagsBranchesList:
920 self.allTagsBranchesList.remove(tag)
921 self.allTagsBranchesList.insert(0, tag)
922 else:
923 return
924
925 if self.otherData["standardLayout"]:
926 rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
927 if not rx_base.exactMatch(reposURL):
928 QMessageBox.critical(None,
929 self.trUtf8("Subversion Error"),
930 self.trUtf8("""The URL of the project repository has an"""
931 """ invalid format. The switch operation will"""
932 """ be aborted"""))
933 return
934
935 reposRoot = rx_base.cap(1)
936 tn = tag
937 if tagType == 1:
938 url = '%s/tags/%s' % (reposRoot, urllib.quote(tag))
939 elif tagType == 2:
940 url = '%s/branches/%s' % (reposRoot, urllib.quote(tag))
941 elif tagType == 4:
942 url = '%s/trunk' % (reposRoot)
943 tn = 'HEAD'
944 else:
945 url = self.__svnURL(tag)
946 tn = url
947
948 args = []
949 args.append('switch')
950 if self.versionStr >= '1.5.0':
951 args.append('--accept')
952 args.append('postpone')
953 args.append(url)
954 args.append(name)
955
956 dia = SvnDialog(self.trUtf8('Switching to {0}')
957 .format(tn))
958 res = dia.startProcess(args)
959 if res:
960 dia.exec_()
961
962 def vcsMerge(self, name):
963 """
964 Public method used to merge a URL/revision into the local project.
965
966 @param name file/directory name to be merged (string)
967 """
968 dname, fname = self.splitPath(name)
969
970 opts = self.options['global'][:]
971 force = '--force' in opts
972 if force:
973 del opts[opts.index('--force')]
974
975 dlg = SvnMergeDialog(self.mergeList[0], self.mergeList[1], self.mergeList[2],
976 force)
977 if dlg.exec_() == QDialog.Accepted:
978 urlrev1, urlrev2, target, force = dlg.getParameters()
979 else:
980 return
981
982 # remember URL or revision
983 if urlrev1 in self.mergeList[0]:
984 self.mergeList[0].remove(urlrev1)
985 self.mergeList[0].insert(0, urlrev1)
986 if urlrev2 in self.mergeList[1]:
987 self.mergeList[1].remove(urlrev2)
988 self.mergeList[1].insert(0, urlrev2)
989
990 rx_rev = QRegExp('\\d+|HEAD')
991
992 args = []
993 args.append('merge')
994 self.addArguments(args, opts)
995 if self.versionStr >= '1.5.0':
996 args.append('--accept')
997 args.append('postpone')
998 if force:
999 args.append('--force')
1000 if rx_rev.exactMatch(urlrev1):
1001 args.append('-r')
1002 args.append('{0}:{1}'.format(urlrev1, urlrev2))
1003 if not target:
1004 args.append(name)
1005 else:
1006 args.append(target)
1007
1008 # remember target
1009 if target in self.mergeList[2]:
1010 self.mergeList[2].remove(target)
1011 self.mergeList[2].insert(0, target)
1012 else:
1013 args.append(self.__svnURL(urlrev1))
1014 args.append(self.__svnURL(urlrev2))
1015 args.append(fname)
1016
1017 dia = SvnDialog(self.trUtf8('Merging {0}').format(name))
1018 res = dia.startProcess(args, dname)
1019 if res:
1020 dia.exec_()
1021
1022 def vcsRegisteredState(self, name):
1023 """
1024 Public method used to get the registered state of a file in the vcs.
1025
1026 @param name filename to check (string)
1027 @return a combination of canBeCommited and canBeAdded
1028 """
1029 dname, fname = self.splitPath(name)
1030
1031 if fname == '.':
1032 if os.path.isdir(os.path.join(dname, self.adminDir)):
1033 return self.canBeCommitted
1034 else:
1035 return self.canBeAdded
1036
1037 name = os.path.normcase(name)
1038 states = { name : 0 }
1039 states = self.vcsAllRegisteredStates(states, dname, False)
1040 if states[name] == self.canBeCommitted:
1041 return self.canBeCommitted
1042 else:
1043 return self.canBeAdded
1044
1045 def vcsAllRegisteredStates(self, names, dname, shortcut = True):
1046 """
1047 Public method used to get the registered states of a number of files in the vcs.
1048
1049 <b>Note:</b> If a shortcut is to be taken, the code will only check, if the named
1050 directory has been scanned already. If so, it is assumed, that the states for
1051 all files has been populated by the previous run.
1052
1053 @param names dictionary with all filenames to be checked as keys
1054 @param dname directory to check in (string)
1055 @param shortcut flag indicating a shortcut should be taken (boolean)
1056 @return the received dictionary completed with a combination of
1057 canBeCommited and canBeAdded or None in order to signal an error
1058 """
1059 if not os.path.isdir(os.path.join(dname, self.adminDir)):
1060 # not under version control -> do nothing
1061 return names
1062
1063 found = False
1064 for name in self.statusCache.keys():
1065 if os.path.dirname(name) == dname:
1066 if shortcut:
1067 found = True
1068 break
1069 if name in names:
1070 found = True
1071 names[name] = self.statusCache[name]
1072
1073 if not found:
1074 ioEncoding = Preferences.getSystem("IOEncoding")
1075 process = QProcess()
1076 args = []
1077 args.append('status')
1078 args.append('--verbose')
1079 args.append('--non-interactive')
1080 args.append(dname)
1081 process.start('svn', args)
1082 procStarted = process.waitForStarted()
1083 if procStarted:
1084 finished = process.waitForFinished(30000)
1085 if finished and process.exitCode() == 0:
1086 output = \
1087 unicode(process.readAllStandardOutput(), ioEncoding, 'replace')
1088 for line in output.splitlines():
1089 if self.rx_status1.exactMatch(line):
1090 flags = str(self.rx_status1.cap(1))
1091 path = self.rx_status1.cap(5).strip()
1092 elif self.rx_status2.exactMatch(line):
1093 flags = str(self.rx_status2.cap(1))
1094 path = self.rx_status2.cap(2).strip()
1095 else:
1096 continue
1097 name = os.path.normcase(path)
1098 if flags[0] not in "?I":
1099 if name in names:
1100 names[name] = self.canBeCommitted
1101 self.statusCache[name] = self.canBeCommitted
1102 else:
1103 self.statusCache[name] = self.canBeAdded
1104
1105 return names
1106
1107 def clearStatusCache(self):
1108 """
1109 Public method to clear the status cache.
1110 """
1111 self.statusCache = {}
1112
1113 def vcsName(self):
1114 """
1115 Public method returning the name of the vcs.
1116
1117 @return always 'Subversion' (string)
1118 """
1119 return "Subversion"
1120
1121 def vcsCleanup(self, name):
1122 """
1123 Public method used to cleanup the working copy.
1124
1125 @param name directory name to be cleaned up (string)
1126 """
1127 args = []
1128 args.append('cleanup')
1129 self.addArguments(args, self.options['global'])
1130 args.append(name)
1131
1132 dia = SvnDialog(self.trUtf8('Cleaning up {0}')
1133 .format(name))
1134 res = dia.startProcess(args)
1135 if res:
1136 dia.exec_()
1137
1138 def vcsCommandLine(self, name):
1139 """
1140 Public method used to execute arbitrary subversion commands.
1141
1142 @param name directory name of the working directory (string)
1143 """
1144 dlg = SvnCommandDialog(self.commandHistory, self.wdHistory, name)
1145 if dlg.exec_() == QDialog.Accepted:
1146 command, wd = dlg.getData()
1147 commandList = Utilities.parseOptionString(command)
1148
1149 # This moves any previous occurrence of these arguments to the head
1150 # of the list.
1151 if command in self.commandHistory:
1152 self.commandHistory.remove(command)
1153 self.commandHistory.insert(0, command)
1154 if wd in self.wdHistory:
1155 self.wdHistory.remove(wd)
1156 self.wdHistory.insert(0, wd)
1157
1158 args = []
1159 self.addArguments(args, commandList)
1160
1161 dia = SvnDialog(self.trUtf8('Subversion command'))
1162 res = dia.startProcess(args, wd)
1163 if res:
1164 dia.exec_()
1165
1166 def vcsOptionsDialog(self, project, archive, editable = False, parent = None):
1167 """
1168 Public method to get a dialog to enter repository info.
1169
1170 @param project reference to the project object
1171 @param archive name of the project in the repository (string)
1172 @param editable flag indicating that the project name is editable (boolean)
1173 @param parent parent widget (QWidget)
1174 """
1175 return SvnOptionsDialog(self, project, parent)
1176
1177 def vcsNewProjectOptionsDialog(self, parent = None):
1178 """
1179 Public method to get a dialog to enter repository info for getting a new project.
1180
1181 @param parent parent widget (QWidget)
1182 """
1183 return SvnNewProjectOptionsDialog(self, parent)
1184
1185 def vcsRepositoryInfos(self, ppath):
1186 """
1187 Public method to retrieve information about the repository.
1188
1189 @param ppath local path to get the repository infos (string)
1190 @return string with ready formated info for display (string)
1191 """
1192 info = {\
1193 'committed-rev' : '',
1194 'committed-date' : '',
1195 'committed-time' : '',
1196 'url' : '',
1197 'last-author' : '',
1198 'revision' : ''
1199 }
1200
1201 ioEncoding = Preferences.getSystem("IOEncoding")
1202
1203 process = QProcess()
1204 args = []
1205 args.append('info')
1206 args.append('--non-interactive')
1207 args.append('--xml')
1208 args.append(ppath)
1209 process.start('svn', args)
1210 procStarted = process.waitForStarted()
1211 if procStarted:
1212 finished = process.waitForFinished(30000)
1213 if finished and process.exitCode() == 0:
1214 output = unicode(process.readAllStandardOutput(), ioEncoding, 'replace')
1215 entryFound = False
1216 commitFound = False
1217 for line in output.splitlines():
1218 line = line.strip()
1219 if line.startswith('<entry'):
1220 entryFound = True
1221 elif line.startswith('<commit'):
1222 commitFound = True
1223 elif line.startswith('</commit>'):
1224 commitFound = False
1225 elif line.startswith("revision="):
1226 rev = line[line.find('"')+1:line.rfind('"')]
1227 if entryFound:
1228 info['revision'] = rev
1229 entryFound = False
1230 elif commitFound:
1231 info['committed-rev'] = rev
1232 elif line.startswith('<url>'):
1233 info['url'] = \
1234 line.replace('<url>', '').replace('</url>', '')
1235 elif line.startswith('<author>'):
1236 info['last-author'] = \
1237 line.replace('<author>', '').replace('</author>', '')
1238 elif line.startswith('<date>'):
1239 value = line.replace('<date>', '').replace('</date>', '')
1240 date, time = value.split('T')
1241 info['committed-date'] = date
1242 info['committed-time'] = "%s%s" % (time.split('.')[0], time[-1])
1243
1244 return QApplication.translate('subversion',
1245 """<h3>Repository information</h3>"""
1246 """<table>"""
1247 """<tr><td><b>Subversion V.</b></td><td>{0}</td></tr>"""
1248 """<tr><td><b>URL</b></td><td>{1}</td></tr>"""
1249 """<tr><td><b>Current revision</b></td><td>{2}</td></tr>"""
1250 """<tr><td><b>Committed revision</b></td><td>{3}</td></tr>"""
1251 """<tr><td><b>Committed date</b></td><td>{4}</td></tr>"""
1252 """<tr><td><b>Comitted time</b></td><td>{5}</td></tr>"""
1253 """<tr><td><b>Last author</b></td><td>{6}</td></tr>"""
1254 """</table>"""
1255 )\
1256 .format(self.versionStr,
1257 info['url'],
1258 info['revision'],
1259 info['committed-rev'],
1260 info['committed-date'],
1261 info['committed-time'],
1262 info['last-author'])
1263
1264 ############################################################################
1265 ## Public Subversion specific methods are below.
1266 ############################################################################
1267
1268 def svnGetReposName(self, path):
1269 """
1270 Public method used to retrieve the URL of the subversion repository path.
1271
1272 @param path local path to get the svn repository path for (string)
1273 @return string with the repository path URL
1274 """
1275 ioEncoding = Preferences.getSystem("IOEncoding")
1276
1277 process = QProcess()
1278 args = []
1279 args.append('info')
1280 args.append('--xml')
1281 args.append('--non-interactive')
1282 args.append(path)
1283 process.start('svn', args)
1284 procStarted = process.waitForStarted()
1285 if procStarted:
1286 finished = process.waitForFinished(30000)
1287 if finished and process.exitCode() == 0:
1288 output = unicode(process.readAllStandardOutput(), ioEncoding, 'replace')
1289 for line in output.splitlines():
1290 line = line.strip()
1291 if line.startswith('<url>'):
1292 reposURL = line.replace('<url>', '').replace('</url>', '')
1293 return reposURL
1294
1295 return None
1296
1297 def svnResolve(self, name):
1298 """
1299 Public method used to resolve conflicts of a file/directory.
1300
1301 @param name file/directory name to be resolved (string)
1302 """
1303 args = []
1304 if self.versionStr >= '1.5.0':
1305 args.append('resolve')
1306 args.append('--accept')
1307 args.append('working')
1308 else:
1309 args.append('resolved')
1310 self.addArguments(args, self.options['global'])
1311 if type(name) is types.ListType:
1312 self.addArguments(args, name)
1313 else:
1314 if os.path.isdir(name):
1315 args.append('--recursive')
1316 args.append(name)
1317
1318 dia = SvnDialog(self.trUtf8('Resolving conficts'))
1319 res = dia.startProcess(args)
1320 if res:
1321 dia.exec_()
1322 self.checkVCSStatus()
1323
1324 def svnCopy(self, name, project):
1325 """
1326 Public method used to copy a file/directory.
1327
1328 @param name file/directory name to be copied (string)
1329 @param project reference to the project object
1330 @return flag indicating successfull operation (boolean)
1331 """
1332 rx_prot = QRegExp('(file:|svn:|svn+ssh:|http:|https:).+')
1333 dlg = SvnCopyDialog(name)
1334 res = False
1335 if dlg.exec_() == QDialog.Accepted:
1336 target, force = dlg.getData()
1337
1338 args = []
1339 args.append('copy')
1340 self.addArguments(args, self.options['global'])
1341 if rx_prot.exactMatch(target):
1342 args.append('--message')
1343 args.append('Copying {0} to {1}'.format(name, target))
1344 target = self.__svnURL(target)
1345 args.append(name)
1346 args.append(target)
1347
1348 dia = SvnDialog(self.trUtf8('Copying {0}')
1349 .format(name))
1350 res = dia.startProcess(args)
1351 if res:
1352 dia.exec_()
1353 res = dia.normalExit()
1354 if res and \
1355 not rx_prot.exactMatch(target) and \
1356 target.startswith(project.getProjectPath()):
1357 if os.path.isdir(name):
1358 project.copyDirectory(name, target)
1359 else:
1360 project.appendFile(target)
1361 return res
1362
1363 def svnListProps(self, name, recursive = False):
1364 """
1365 Public method used to list the properties of a file/directory.
1366
1367 @param name file/directory name (string or list of strings)
1368 @param recursive flag indicating a recursive list is requested
1369 """
1370 self.propList = SvnPropListDialog(self)
1371 self.propList.show()
1372 self.propList.start(name, recursive)
1373
1374 def svnSetProp(self, name, recursive = False):
1375 """
1376 Public method used to add a property to a file/directory.
1377
1378 @param name file/directory name (string or list of strings)
1379 @param recursive flag indicating a recursive list is requested
1380 """
1381 dlg = SvnPropSetDialog()
1382 if dlg.exec_() == QDialog.Accepted:
1383 propName, fileFlag, propValue = dlg.getData()
1384 if not propName:
1385 QMessageBox.critical(None,
1386 self.trUtf8("Subversion Set Property"),
1387 self.trUtf8("""You have to supply a property name. Aborting."""))
1388 return
1389
1390 args = []
1391 args.append('propset')
1392 self.addArguments(args, self.options['global'])
1393 if recursive:
1394 args.append('--recursive')
1395 args.append(propName)
1396 if fileFlag:
1397 args.append('--file')
1398 args.append(propValue)
1399 if type(name) is types.ListType:
1400 dname, fnames = self.splitPathList(name)
1401 self.addArguments(args, fnames)
1402 else:
1403 dname, fname = self.splitPath(name)
1404 args.append(fname)
1405
1406 dia = SvnDialog(self.trUtf8('Subversion Set Property'))
1407 res = dia.startProcess(args, dname)
1408 if res:
1409 dia.exec_()
1410
1411 def svnDelProp(self, name, recursive = False):
1412 """
1413 Public method used to delete a property of a file/directory.
1414
1415 @param name file/directory name (string or list of strings)
1416 @param recursive flag indicating a recursive list is requested
1417 """
1418 propName, ok = QInputDialog.getText(\
1419 None,
1420 self.trUtf8("Subversion Delete Property"),
1421 self.trUtf8("Enter property name"),
1422 QLineEdit.Normal)
1423
1424 if not ok:
1425 return
1426
1427 if not propName:
1428 QMessageBox.critical(None,
1429 self.trUtf8("Subversion Delete Property"),
1430 self.trUtf8("""You have to supply a property name. Aborting."""))
1431 return
1432
1433 args = []
1434 args.append('propdel')
1435 self.addArguments(args, self.options['global'])
1436 if recursive:
1437 args.append('--recursive')
1438 args.append(propName)
1439 if type(name) is types.ListType:
1440 dname, fnames = self.splitPathList(name)
1441 self.addArguments(args, fnames)
1442 else:
1443 dname, fname = self.splitPath(name)
1444 args.append(fname)
1445
1446 dia = SvnDialog(self.trUtf8('Subversion Delete Property'))
1447 res = dia.startProcess(args, dname)
1448 if res:
1449 dia.exec_()
1450
1451 def svnListTagBranch(self, path, tags = True):
1452 """
1453 Public method used to list the available tags or branches.
1454
1455 @param path directory name of the project (string)
1456 @param tags flag indicating listing of branches or tags
1457 (False = branches, True = tags)
1458 """
1459 self.tagbranchList = SvnTagBranchListDialog(self)
1460 self.tagbranchList.show()
1461 if tags:
1462 if not self.showedTags:
1463 self.showedTags = True
1464 allTagsBranchesList = self.allTagsBranchesList
1465 else:
1466 self.tagsList = []
1467 allTagsBranchesList = None
1468 self.tagbranchList.start(path, tags,
1469 self.tagsList, allTagsBranchesList)
1470 elif not tags:
1471 if not self.showedBranches:
1472 self.showedBranches = True
1473 allTagsBranchesList = self.allTagsBranchesList
1474 else:
1475 self.branchesList = []
1476 allTagsBranchesList = None
1477 self.tagbranchList.start(path, tags,
1478 self.branchesList, self.allTagsBranchesList)
1479
1480 def svnBlame(self, name):
1481 """
1482 Public method to show the output of the svn blame command.
1483
1484 @param name file name to show the blame for (string)
1485 """
1486 self.blame = SvnBlameDialog(self)
1487 self.blame.show()
1488 self.blame.start(name)
1489
1490 def svnExtendedDiff(self, name):
1491 """
1492 Public method used to view the difference of a file/directory to the
1493 Subversion repository.
1494
1495 If name is a directory and is the project directory, all project files
1496 are saved first. If name is a file (or list of files), which is/are being edited
1497 and has unsaved modification, they can be saved or the operation may be aborted.
1498
1499 This method gives the chance to enter the revisions to be compared.
1500
1501 @param name file/directory name to be diffed (string)
1502 """
1503 if type(name) is types.ListType:
1504 names = name[:]
1505 else:
1506 names = [name]
1507 for nam in names:
1508 if os.path.isfile(nam):
1509 editor = e4App().getObject("ViewManager").getOpenEditor(nam)
1510 if editor and not editor.checkDirty() :
1511 return
1512 else:
1513 project = e4App().getObject("Project")
1514 if nam == project.ppath and not project.saveAllScripts():
1515 return
1516 dlg = SvnRevisionSelectionDialog()
1517 if dlg.exec_() == QDialog.Accepted:
1518 revisions = dlg.getRevisions()
1519 self.diff = SvnDiffDialog(self)
1520 self.diff.show()
1521 self.diff.start(name, revisions)
1522
1523 def svnUrlDiff(self, name):
1524 """
1525 Public method used to view the difference of a file/directory of two
1526 repository URLs.
1527
1528 If name is a directory and is the project directory, all project files
1529 are saved first. If name is a file (or list of files), which is/are being edited
1530 and has unsaved modification, they can be saved or the operation may be aborted.
1531
1532 This method gives the chance to enter the revisions to be compared.
1533
1534 @param name file/directory name to be diffed (string)
1535 """
1536 if type(name) is types.ListType:
1537 names = name[:]
1538 else:
1539 names = [name]
1540 for nam in names:
1541 if os.path.isfile(nam):
1542 editor = e4App().getObject("ViewManager").getOpenEditor(nam)
1543 if editor and not editor.checkDirty() :
1544 return
1545 else:
1546 project = e4App().getObject("Project")
1547 if nam == project.ppath and not project.saveAllScripts():
1548 return
1549
1550 dname = self.splitPath(names[0])[0]
1551
1552 dlg = SvnUrlSelectionDialog(self, self.tagsList, self.branchesList, dname)
1553 if dlg.exec_() == QDialog.Accepted:
1554 urls, summary = dlg.getURLs()
1555 self.diff = SvnDiffDialog(self)
1556 self.diff.show()
1557 QApplication.processEvents()
1558 self.diff.start(name, urls = urls, summary = summary)
1559
1560 def svnLogLimited(self, name):
1561 """
1562 Public method used to view the (limited) log of a file/directory from the
1563 Subversion repository.
1564
1565 @param name file/directory name to show the log of (string)
1566 """
1567 noEntries, ok = QInputDialog.getInteger(\
1568 None,
1569 self.trUtf8("Subversion Log"),
1570 self.trUtf8("Select number of entries to show."),
1571 self.getPlugin().getPreferences("LogLimit"), 1, 999999, 1)
1572 if ok:
1573 self.log = SvnLogDialog(self)
1574 self.log.show()
1575 self.log.start(name, noEntries)
1576
1577 def svnLogBrowser(self, path):
1578 """
1579 Public method used to browse the log of a file/directory from the
1580 Subversion repository.
1581
1582 @param path file/directory name to show the log of (string)
1583 """
1584 self.logBrowser = SvnLogBrowserDialog(self)
1585 self.logBrowser.show()
1586 self.logBrowser.start(path)
1587
1588 def svnLock(self, name, stealIt=False, parent=None):
1589 """
1590 Public method used to lock a file in the Subversion repository.
1591
1592 @param name file/directory name to be locked (string or list of strings)
1593 @param stealIt flag indicating a forced operation (boolean)
1594 @param parent reference to the parent object of the subversion dialog (QWidget)
1595 """
1596 args = []
1597 args.append('lock')
1598 self.addArguments(args, self.options['global'])
1599 if stealIt:
1600 args.append('--force')
1601 if type(name) is types.ListType:
1602 dname, fnames = self.splitPathList(name)
1603 self.addArguments(args, fnames)
1604 else:
1605 dname, fname = self.splitPath(name)
1606 args.append(fname)
1607
1608 dia = SvnDialog(self.trUtf8('Locking in the Subversion repository'), parent)
1609 res = dia.startProcess(args, dname)
1610 if res:
1611 dia.exec_()
1612
1613 def svnUnlock(self, name, breakIt=False, parent=None):
1614 """
1615 Public method used to unlock a file in the Subversion repository.
1616
1617 @param name file/directory name to be unlocked (string or list of strings)
1618 @param breakIt flag indicating a forced operation (boolean)
1619 @param parent reference to the parent object of the subversion dialog (QWidget)
1620 """
1621 args = []
1622 args.append('unlock')
1623 self.addArguments(args, self.options['global'])
1624 if breakIt:
1625 args.append('--force')
1626 if type(name) is types.ListType:
1627 dname, fnames = self.splitPathList(name)
1628 self.addArguments(args, fnames)
1629 else:
1630 dname, fname = self.splitPath(name)
1631 args.append(fname)
1632
1633 dia = SvnDialog(self.trUtf8('Unlocking in the Subversion repository'), parent)
1634 res = dia.startProcess(args, dname)
1635 if res:
1636 dia.exec_()
1637
1638 def svnRelocate(self, projectPath):
1639 """
1640 Public method to relocate the working copy to a new repository URL.
1641
1642 @param projectPath path name of the project (string)
1643 """
1644 currUrl = self.svnGetReposName(projectPath)
1645 dlg = SvnRelocateDialog(currUrl)
1646 if dlg.exec_() == QDialog.Accepted:
1647 newUrl, inside = dlg.getData()
1648 args = []
1649 args.append('switch')
1650 if not inside:
1651 args.append('--relocate')
1652 args.append(currUrl)
1653 args.append(newUrl)
1654 args.append(projectPath)
1655
1656 dia = SvnDialog(self.trUtf8('Relocating'))
1657 res = dia.startProcess(args)
1658 if res:
1659 dia.exec_()
1660
1661 def svnRepoBrowser(self, projectPath = None):
1662 """
1663 Public method to open the repository browser.
1664
1665 @param projectPath path name of the project (string)
1666 """
1667 if projectPath:
1668 url = self.svnGetReposName(projectPath)
1669 else:
1670 url = None
1671
1672 if url is None:
1673 url, ok = QInputDialog.getText(\
1674 None,
1675 self.trUtf8("Repository Browser"),
1676 self.trUtf8("Enter the repository URL."),
1677 QLineEdit.Normal)
1678 if not ok or not url:
1679 return
1680
1681 self.repoBrowser = SvnRepoBrowserDialog(self)
1682 self.repoBrowser.show()
1683 self.repoBrowser.start(url)
1684
1685 def svnRemoveFromChangelist(self, names):
1686 """
1687 Public method to remove a file or directory from it's changelist.
1688
1689 Note: Directories will be removed recursively.
1690
1691 @param names name or list of names of file or directory to remove
1692 (string)
1693 """
1694 args = []
1695 args.append('changelist')
1696 self.addArguments(args, self.options['global'])
1697 args.append('--remove')
1698 args.append('--recursive')
1699 if type(names) is types.ListType:
1700 dname, fnames = self.splitPathList(names)
1701 self.addArguments(args, fnames)
1702 else:
1703 dname, fname = self.splitPath(names)
1704 args.append(fname)
1705
1706 dia = SvnDialog(self.trUtf8('Remove from changelist'))
1707 res = dia.startProcess(args, dname)
1708 if res:
1709 dia.exec_()
1710
1711 def svnAddToChangelist(self, names):
1712 """
1713 Public method to add a file or directory to a changelist.
1714
1715 Note: Directories will be added recursively.
1716
1717 @param names name or list of names of file or directory to add
1718 (string)
1719 """
1720 clname, ok = QInputDialog.getText(\
1721 None,
1722 self.trUtf8("Add to changelist"),
1723 self.trUtf8("Enter name of the changelist:"),
1724 QLineEdit.Normal)
1725 if not ok or not clname:
1726 return
1727
1728 args = []
1729 args.append('changelist')
1730 self.addArguments(args, self.options['global'])
1731 args.append('--recursive')
1732 args.append(clname)
1733 if type(names) is types.ListType:
1734 dname, fnames = self.splitPathList(names)
1735 self.addArguments(args, fnames)
1736 else:
1737 dname, fname = self.splitPath(names)
1738 args.append(fname)
1739
1740 dia = SvnDialog(self.trUtf8('Remove from changelist'))
1741 res = dia.startProcess(args, dname)
1742 if res:
1743 dia.exec_()
1744
1745 ############################################################################
1746 ## Private Subversion specific methods are below.
1747 ############################################################################
1748
1749 def __svnURL(self, url):
1750 """
1751 Private method to format a url for subversion.
1752
1753 @param url unformatted url string (string)
1754 @return properly formated url for subversion (string)
1755 """
1756 url = self.svnNormalizeURL(url)
1757 url = url.split(':', 2)
1758 if len(url) == 3:
1759 scheme = url[0]
1760 host = url[1]
1761 port, path = url[2].split("/",1)
1762 return "%s:%s:%s/%s" % (scheme, host, port, urllib.quote(path))
1763 else:
1764 scheme = url[0]
1765 if scheme == "file":
1766 return "%s:%s" % (scheme, urllib.quote(url[1]))
1767 else:
1768 host, path = url[1][2:].split("/",1)
1769 return "%s://%s/%s" % (scheme, host, urllib.quote(path))
1770
1771 def svnNormalizeURL(self, url):
1772 """
1773 Public method to normalize a url for subversion.
1774
1775 @param url url string (string)
1776 @return properly normalized url for subversion (string)
1777 """
1778 url = url.replace('\\', '/')
1779 if url.endswith('/'):
1780 url = url[:-1]
1781 urll = url.split('//')
1782 return "%s//%s" % (urll[0], '/'.join(urll[1:]))
1783
1784 ############################################################################
1785 ## Methods to get the helper objects are below.
1786 ############################################################################
1787
1788 def vcsGetProjectBrowserHelper(self, browser, project, isTranslationsBrowser = False):
1789 """
1790 Public method to instanciate a helper object for the different project browsers.
1791
1792 @param browser reference to the project browser object
1793 @param project reference to the project object
1794 @param isTranslationsBrowser flag indicating, the helper is requested for the
1795 translations browser (this needs some special treatment)
1796 @return the project browser helper object
1797 """
1798 return SvnProjectBrowserHelper(self, browser, project, isTranslationsBrowser)
1799
1800 def vcsGetProjectHelper(self, project):
1801 """
1802 Public method to instanciate a helper object for the project.
1803
1804 @param project reference to the project object
1805 @return the project helper object
1806 """
1807 helper = self.__plugin.getProjectHelper()
1808 helper.setObjects(self, project)
1809 return helper
1810
1811 ############################################################################
1812 ## Status Monitor Thread methods
1813 ############################################################################
1814
1815 def _createStatusMonitorThread(self, interval, project):
1816 """
1817 Protected method to create an instance of the VCS status monitor thread.
1818
1819 @param project reference to the project object
1820 @param interval check interval for the monitor thread in seconds (integer)
1821 @return reference to the monitor thread (QThread)
1822 """
1823 return SvnStatusMonitorThread(interval, project.ppath, self)

eric ide

mercurial