eric6/VCS/VersionControl.py

changeset 6942
2602857055c5
parent 6891
93f82da09f22
child 7167
b3557e77314a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing an abstract base class to be subclassed by all specific
8 VCS interfaces.
9 """
10
11 from __future__ import unicode_literals
12
13 import os
14
15 from PyQt5.QtCore import QObject, QThread, QMutex, QProcess, \
16 Qt, pyqtSignal, QCoreApplication
17 from PyQt5.QtWidgets import QApplication
18
19 from E5Gui import E5MessageBox
20
21 import Preferences
22
23
24 class VersionControl(QObject):
25 """
26 Class implementing an abstract base class to be subclassed by all specific
27 VCS interfaces.
28
29 It defines the vcs interface to be implemented by subclasses
30 and the common methods.
31
32 @signal vcsStatusMonitorData(list of str) emitted to update the VCS status
33 @signal vcsStatusMonitorStatus(str, str) emitted to signal the status of
34 the monitoring thread (ok, nok, op, off) and a status message
35 @signal vcsStatusMonitorInfo(str) emitted to signal some info of the
36 monitoring thread
37 @signal vcsStatusChanged() emitted to indicate a change of the overall
38 VCS status
39 """
40 vcsStatusMonitorData = pyqtSignal(list)
41 vcsStatusMonitorStatus = pyqtSignal(str, str)
42 vcsStatusMonitorInfo = pyqtSignal(str)
43 vcsStatusChanged = pyqtSignal()
44
45 canBeCommitted = 1 # Indicates that a file/directory is in the vcs.
46 canBeAdded = 2 # Indicates that a file/directory is not in vcs.
47
48 def __init__(self, parent=None, name=None):
49 """
50 Constructor
51
52 @param parent parent widget (QWidget)
53 @param name name of this object (string)
54 """
55 super(VersionControl, self).__init__(parent)
56 if name:
57 self.setObjectName(name)
58 self.defaultOptions = {
59 'global': [''],
60 'commit': [''],
61 'checkout': [''],
62 'update': [''],
63 'add': [''],
64 'remove': [''],
65 'diff': [''],
66 'log': [''],
67 'history': [''],
68 'status': [''],
69 'tag': [''],
70 'export': ['']
71 }
72 self.interestingDataKeys = []
73 self.options = {}
74 self.otherData = {}
75 self.canDetectBinaries = True
76
77 self.statusMonitorThread = None
78 self.vcsExecutionMutex = QMutex()
79
80 def vcsShutdown(self):
81 """
82 Public method used to shutdown the vcs interface.
83
84 @exception RuntimeError to indicate that this method must be
85 implemented by a subclass
86 """
87 raise RuntimeError('Not implemented')
88
89 def vcsExists(self):
90 """
91 Public method used to test for the presence of the vcs.
92
93 @ireturn tuple of flag indicating the existence and a string
94 giving an error message in case of failure
95 @exception RuntimeError to indicate that this method must be
96 implemented by a subclass
97 """
98 raise RuntimeError('Not implemented')
99
100 def vcsInit(self, vcsDir, noDialog=False):
101 """
102 Public method used to initialize the vcs.
103
104 @param vcsDir name of the VCS directory (string)
105 @param noDialog flag indicating quiet operations (boolean)
106 @ireturn flag indicating success (boolean)
107 @exception RuntimeError to indicate that this method must be
108 implemented by a subclass
109 """
110 raise RuntimeError('Not implemented')
111
112 def vcsConvertProject(self, vcsDataDict, project):
113 """
114 Public method to convert an uncontrolled project to a version
115 controlled project.
116
117 @param vcsDataDict dictionary of data required for the conversion
118 @param project reference to the project object
119 @exception RuntimeError to indicate that this method must be
120 implemented by a subclass
121 """
122 raise RuntimeError('Not implemented')
123
124 def vcsImport(self, vcsDataDict, projectDir, noDialog=False):
125 """
126 Public method used to import the project into the vcs.
127
128 @param vcsDataDict dictionary of data required for the import
129 @param projectDir project directory (string)
130 @param noDialog flag indicating quiet operations
131 @ireturn flag indicating an execution without errors (boolean)
132 and a flag indicating the version controll status (boolean)
133 @exception RuntimeError to indicate that this method must be
134 implemented by a subclass
135 """
136 raise RuntimeError('Not implemented')
137
138 def vcsCheckout(self, vcsDataDict, projectDir, noDialog=False):
139 """
140 Public method used to check the project out of the vcs.
141
142 @param vcsDataDict dictionary of data required for the checkout
143 @param projectDir project directory to create (string)
144 @param noDialog flag indicating quiet operations
145 @ireturn flag indicating an execution without errors (boolean)
146 @exception RuntimeError to indicate that this method must be
147 implemented by a subclass
148 """
149 raise RuntimeError('Not implemented')
150
151 def vcsExport(self, vcsDataDict, projectDir):
152 """
153 Public method used to export a directory from the vcs.
154
155 @param vcsDataDict dictionary of data required for the export
156 @param projectDir project directory to create (string)
157 @ireturn flag indicating an execution without errors (boolean)
158 @exception RuntimeError to indicate that this method must be
159 implemented by a subclass
160 """
161 raise RuntimeError('Not implemented')
162
163 def vcsCommit(self, name, message, noDialog=False):
164 """
165 Public method used to make the change of a file/directory permanent in
166 the vcs.
167
168 @param name file/directory name to be committed (string)
169 @param message message for this operation (string)
170 @param noDialog flag indicating quiet operations (boolean)
171 @ireturn flag indicating success (boolean)
172 @exception RuntimeError to indicate that this method must be
173 implemented by a subclass
174 """
175 raise RuntimeError('Not implemented')
176
177 def vcsUpdate(self, name, noDialog=False):
178 """
179 Public method used to update a file/directory in the vcs.
180
181 @param name file/directory name to be updated (string)
182 @param noDialog flag indicating quiet operations (boolean)
183 @ireturn flag indicating, that the update contained an add
184 or delete (boolean)
185 @exception RuntimeError to indicate that this method must be
186 implemented by a subclass
187 """
188 raise RuntimeError('Not implemented')
189
190 def vcsAdd(self, name, isDir=False, noDialog=False):
191 """
192 Public method used to add a file/directory in the vcs.
193
194 @param name file/directory name to be added (string)
195 @param isDir flag indicating name is a directory (boolean)
196 @param noDialog flag indicating quiet operations (boolean)
197 @exception RuntimeError to indicate that this method must be
198 implemented by a subclass
199 """
200 raise RuntimeError('Not implemented')
201
202 def vcsAddBinary(self, name, isDir=False):
203 """
204 Public method used to add a file/directory in binary mode in the vcs.
205
206 @param name file/directory name to be added (string)
207 @param isDir flag indicating name is a directory (boolean)
208 @exception RuntimeError to indicate that this method must be
209 implemented by a subclass
210 """
211 raise RuntimeError('Not implemented')
212
213 def vcsAddTree(self, path):
214 """
215 Public method to add a directory tree rooted at path in the vcs.
216
217 @param path root directory of the tree to be added (string)
218 @exception RuntimeError to indicate that this method must be
219 implemented by a subclass
220 """
221 raise RuntimeError('Not implemented')
222
223 def vcsRemove(self, name, project=False, noDialog=False):
224 """
225 Public method used to add a file/directory in the vcs.
226
227 @param name file/directory name to be removed (string)
228 @param project flag indicating deletion of a project tree (boolean)
229 @param noDialog flag indicating quiet operations
230 @ireturn flag indicating success (boolean)
231 @exception RuntimeError to indicate that this method must be
232 implemented by a subclass
233 """
234 raise RuntimeError('Not implemented')
235
236 def vcsMove(self, name, project, target=None, noDialog=False):
237 """
238 Public method used to move a file/directory.
239
240 @param name file/directory name to be moved (string)
241 @param project reference to the project object
242 @param target new name of the file/directory (string)
243 @param noDialog flag indicating quiet operations
244 @ireturn flag indicating successfull operation (boolean)
245 @exception RuntimeError to indicate that this method must be
246 implemented by a subclass
247 """
248 raise RuntimeError('Not implemented')
249
250 def vcsLogBrowser(self, name, isFile=False):
251 """
252 Public method used to view the log of a file/directory in the vcs
253 with a log browser dialog.
254
255 @param name file/directory name to show the log for (string)
256 @keyparam isFile flag indicating log for a file is to be shown
257 (boolean)
258 @exception RuntimeError to indicate that this method must be
259 implemented by a subclass
260 """
261 raise RuntimeError('Not implemented')
262
263 def vcsDiff(self, name):
264 """
265 Public method used to view the diff of a file/directory in the vcs.
266
267 @param name file/directory name to be diffed (string)
268 @exception RuntimeError to indicate that this method must be
269 implemented by a subclass
270 """
271 raise RuntimeError('Not implemented')
272
273 def vcsStatus(self, name):
274 """
275 Public method used to view the status of a file/directory in the vcs.
276
277 @param name file/directory name to show the status for (string)
278 @exception RuntimeError to indicate that this method must be
279 implemented by a subclass
280 """
281 raise RuntimeError('Not implemented')
282
283 def vcsTag(self, name):
284 """
285 Public method used to set the tag of a file/directory in the vcs.
286
287 @param name file/directory name to be tagged (string)
288 @exception RuntimeError to indicate that this method must be
289 implemented by a subclass
290 """
291 raise RuntimeError('Not implemented')
292
293 def vcsRevert(self, name):
294 """
295 Public method used to revert changes made to a file/directory.
296
297 @param name file/directory name to be reverted (string)
298 @exception RuntimeError to indicate that this method must be
299 implemented by a subclass
300 """
301 raise RuntimeError('Not implemented')
302
303 def vcsSwitch(self, name):
304 """
305 Public method used to switch a directory to a different tag/branch.
306
307 @param name directory name to be switched (string)
308 @ireturn flag indicating, that the switch contained an add
309 or delete (boolean)
310 @exception RuntimeError to indicate that this method must be
311 implemented by a subclass
312 """
313 raise RuntimeError('Not implemented')
314
315 def vcsMerge(self, name):
316 """
317 Public method used to merge a tag/branch into the local project.
318
319 @param name file/directory name to be merged (string)
320 @exception RuntimeError to indicate that this method must be
321 implemented by a subclass
322 """
323 raise RuntimeError('Not implemented')
324
325 def vcsRegisteredState(self, name):
326 """
327 Public method used to get the registered state of a file in the vcs.
328
329 @param name filename to check (string)
330 @ireturn a combination of canBeCommited and canBeAdded or
331 0 in order to signal an error
332 @exception RuntimeError to indicate that this method must be
333 implemented by a subclass
334 """
335 raise RuntimeError('Not implemented')
336
337 def vcsAllRegisteredStates(self, names, dname):
338 """
339 Public method used to get the registered states of a number of files
340 in the vcs.
341
342 @param names dictionary with all filenames to be checked as keys
343 @param dname directory to check in (string)
344 @ireturn the received dictionary completed with a combination of
345 canBeCommited and canBeAdded or None in order to signal an error
346 @exception RuntimeError to indicate that this method must be
347 implemented by a subclass
348 """
349 raise RuntimeError('Not implemented')
350
351 def vcsName(self):
352 """
353 Public method returning the name of the vcs.
354
355 @ireturn name of the vcs (string)
356 @exception RuntimeError to indicate that this method must be
357 implemented by a subclass
358 """
359 raise RuntimeError('Not implemented')
360
361 def vcsCleanup(self, name):
362 """
363 Public method used to cleanup the local copy.
364
365 @param name directory name to be cleaned up (string)
366 @exception RuntimeError to indicate that this method must be
367 implemented by a subclass
368 """
369 raise RuntimeError('Not implemented')
370
371 def vcsCommandLine(self, name):
372 """
373 Public method used to execute arbitrary vcs commands.
374
375 @param name directory name of the working directory (string)
376 @exception RuntimeError to indicate that this method must be
377 implemented by a subclass
378 """
379 raise RuntimeError('Not implemented')
380
381 def vcsOptionsDialog(self, project, archive, editable=False, parent=None):
382 """
383 Public method to get a dialog to enter repository info.
384
385 @param project reference to the project object
386 @param archive name of the project in the repository (string)
387 @param editable flag indicating that the project name is editable
388 (boolean)
389 @param parent parent widget (QWidget)
390 @exception RuntimeError to indicate that this method must be
391 implemented by a subclass
392 """
393 raise RuntimeError('Not implemented')
394
395 def vcsNewProjectOptionsDialog(self, parent=None):
396 """
397 Public method to get a dialog to enter repository info for getting a
398 new project.
399
400 @param parent parent widget (QWidget)
401 @exception RuntimeError to indicate that this method must be
402 implemented by a subclass
403 """
404 raise RuntimeError('Not implemented')
405
406 def vcsRepositoryInfos(self, ppath):
407 """
408 Public method to retrieve information about the repository.
409
410 @param ppath local path to get the repository infos (string)
411 @ireturn string with ready formated info for display (string)
412 @exception RuntimeError to indicate that this method must be
413 implemented by a subclass
414 """
415 raise RuntimeError('Not implemented')
416
417 def vcsGetProjectBrowserHelper(self, browser, project,
418 isTranslationsBrowser=False):
419 """
420 Public method to instanciate a helper object for the different
421 project browsers.
422
423 @param browser reference to the project browser object
424 @param project reference to the project object
425 @param isTranslationsBrowser flag indicating, the helper is requested
426 for the translations browser (this needs some special treatment)
427 @ireturn the project browser helper object
428 @exception RuntimeError to indicate that this method must be
429 implemented by a subclass
430 """
431 raise RuntimeError('Not implemented')
432
433 def vcsGetProjectHelper(self, project):
434 """
435 Public method to instanciate a helper object for the project.
436
437 @param project reference to the project object
438 @ireturn the project helper object
439 @exception RuntimeError to indicate that this method must be
440 implemented by a subclass
441 """
442 raise RuntimeError('Not implemented')
443
444 #####################################################################
445 ## methods above need to be implemented by a subclass
446 #####################################################################
447
448 def clearStatusCache(self):
449 """
450 Public method to clear the status cache.
451 """
452 pass
453
454 def vcsInitConfig(self, project):
455 """
456 Public method to initialize the VCS configuration.
457
458 This method could ensure, that certain files or directories are
459 exclude from being version controlled.
460
461 @param project reference to the project (Project)
462 """
463 pass
464
465 def vcsSupportCommandOptions(self):
466 """
467 Public method to signal the support of user settable command options.
468
469 @return flag indicating the support of user settable command options
470 (boolean)
471 """
472 return True
473
474 def vcsSetOptions(self, options):
475 """
476 Public method used to set the options for the vcs.
477
478 @param options a dictionary of option strings with keys as
479 defined by the default options
480 """
481 if self.vcsSupportCommandOptions():
482 for key in options:
483 try:
484 self.options[key] = options[key]
485 except KeyError:
486 pass
487
488 def vcsGetOptions(self):
489 """
490 Public method used to retrieve the options of the vcs.
491
492 @return a dictionary of option strings that can be passed to
493 vcsSetOptions.
494 """
495 if self.vcsSupportCommandOptions():
496 return self.options
497 else:
498 return self.defaultOptions
499
500 def vcsSetOtherData(self, data):
501 """
502 Public method used to set vcs specific data.
503
504 @param data a dictionary of vcs specific data
505 """
506 for key in data:
507 try:
508 self.otherData[key] = data[key]
509 except KeyError:
510 pass
511
512 def vcsGetOtherData(self):
513 """
514 Public method used to retrieve vcs specific data.
515
516 @return a dictionary of vcs specific data
517 """
518 return self.otherData
519
520 def vcsSetData(self, key, value):
521 """
522 Public method used to set an entry in the otherData dictionary.
523
524 @param key the key of the data (string)
525 @param value the value of the data
526 """
527 if key in self.interestingDataKeys:
528 self.otherData[key] = value
529
530 def vcsSetDataFromDict(self, dictionary):
531 """
532 Public method used to set entries in the otherData dictionary.
533
534 @param dictionary dictionary to pick entries from
535 """
536 for key in self.interestingDataKeys:
537 if key in dictionary:
538 self.otherData[key] = dictionary[key]
539
540 #####################################################################
541 ## below are some utility methods
542 #####################################################################
543
544 def startSynchronizedProcess(self, proc, program, arguments,
545 workingDir=None):
546 """
547 Public method to start a synchroneous process.
548
549 This method starts a process and waits
550 for its end while still serving the Qt event loop.
551
552 @param proc process to start (QProcess)
553 @param program path of the executable to start (string)
554 @param arguments list of arguments for the process (list of strings)
555 @param workingDir working directory for the process (string)
556 @return flag indicating normal exit (boolean)
557 """
558 if proc is None:
559 return False
560
561 if workingDir:
562 proc.setWorkingDirectory(workingDir)
563 proc.start(program, arguments)
564 procStarted = proc.waitForStarted(5000)
565 if not procStarted:
566 E5MessageBox.critical(
567 None,
568 QCoreApplication.translate(
569 "VersionControl", 'Process Generation Error'),
570 QCoreApplication.translate(
571 "VersionControl",
572 'The process {0} could not be started. '
573 'Ensure, that it is in the search path.'
574 ).format(program))
575 return False
576 else:
577 while proc.state() == QProcess.Running:
578 QApplication.processEvents()
579 QThread.msleep(300)
580 QApplication.processEvents()
581 return (proc.exitStatus() == QProcess.NormalExit) and \
582 (proc.exitCode() == 0)
583
584 def splitPath(self, name):
585 """
586 Public method splitting name into a directory part and a file part.
587
588 @param name path name (string)
589 @return a tuple of 2 strings (dirname, filename).
590 """
591 if os.path.isdir(name):
592 dn = os.path.abspath(name)
593 fn = "."
594 else:
595 dn, fn = os.path.split(name)
596 return (dn, fn)
597
598 def splitPathList(self, names):
599 """
600 Public method splitting the list of names into a common directory part
601 and a file list.
602
603 @param names list of paths (list of strings)
604 @return a tuple of string and list of strings (dirname, filenamelist)
605 """
606 dname = os.path.commonprefix(names)
607 if dname:
608 if not dname.endswith(os.sep):
609 dname = os.path.dirname(dname) + os.sep
610 fnames = [n.replace(dname, '') for n in names]
611 dname = os.path.dirname(dname)
612 return (dname, fnames)
613 else:
614 return ("/", names)
615
616 def addArguments(self, args, argslist):
617 """
618 Public method to add an argument list to the already present
619 arguments.
620
621 @param args current arguments list (list of strings)
622 @param argslist list of arguments (list of strings)
623 """
624 for arg in argslist:
625 if arg != '':
626 args.append(arg)
627
628 ###########################################################################
629 ## VCS status monitor thread related methods
630 ###########################################################################
631
632 def __statusMonitorStatus(self, status, statusMsg):
633 """
634 Private slot to receive the status monitor status.
635
636 It simply re-emits the received status.
637
638 @param status status of the monitoring thread
639 @type str (one of ok, nok or off)
640 @param statusMsg explanotory text for the signaled status
641 @type str
642 """
643 self.vcsStatusMonitorStatus.emit(status, statusMsg)
644 QApplication.flush()
645
646 def __statusMonitorData(self, statusList):
647 """
648 Private method to receive the status monitor status.
649
650 It simply re-emits the received status list.
651
652 @param statusList list of status records
653 @type list of str
654 """
655 self.vcsStatusMonitorData.emit(statusList)
656 QApplication.flush()
657
658 def __statusMonitorInfo(self, info):
659 """
660 Private slot to receive the status monitor info message.
661
662 It simply re-emits the received info message.
663
664 @param info received info message
665 @type str
666 """
667 self.vcsStatusMonitorInfo.emit(info)
668 QApplication.flush()
669
670 def startStatusMonitor(self, project):
671 """
672 Public method to start the VCS status monitor thread.
673
674 @param project reference to the project object
675 @return reference to the monitor thread (QThread)
676 """
677 if project.pudata["VCSSTATUSMONITORINTERVAL"]:
678 vcsStatusMonitorInterval = project.pudata[
679 "VCSSTATUSMONITORINTERVAL"]
680 else:
681 vcsStatusMonitorInterval = Preferences.getVCS(
682 "StatusMonitorInterval")
683 if vcsStatusMonitorInterval > 0:
684 self.statusMonitorThread = self._createStatusMonitorThread(
685 vcsStatusMonitorInterval, project)
686 if self.statusMonitorThread is not None:
687 self.statusMonitorThread.vcsStatusMonitorData.connect(
688 self.__statusMonitorData, Qt.QueuedConnection)
689 self.statusMonitorThread.vcsStatusMonitorStatus.connect(
690 self.__statusMonitorStatus, Qt.QueuedConnection)
691 self.statusMonitorThread.vcsStatusMonitorInfo.connect(
692 self.__statusMonitorInfo, Qt.QueuedConnection)
693 self.statusMonitorThread.setAutoUpdate(
694 Preferences.getVCS("AutoUpdate"))
695 self.statusMonitorThread.start()
696 else:
697 self.statusMonitorThread = None
698 return self.statusMonitorThread
699
700 def stopStatusMonitor(self):
701 """
702 Public method to stop the VCS status monitor thread.
703 """
704 if self.statusMonitorThread is not None:
705 self.__statusMonitorData(["--RESET--"])
706 self.statusMonitorThread.vcsStatusMonitorData.disconnect(
707 self.__statusMonitorData)
708 self.statusMonitorThread.vcsStatusMonitorStatus.disconnect(
709 self.__statusMonitorStatus)
710 self.statusMonitorThread.vcsStatusMonitorInfo.disconnect(
711 self.__statusMonitorInfo)
712 self.statusMonitorThread.stop()
713 self.statusMonitorThread.wait(10000)
714 if not self.statusMonitorThread.isFinished():
715 self.statusMonitorThread.terminate()
716 self.statusMonitorThread.wait(10000)
717 self.statusMonitorThread = None
718 self.__statusMonitorStatus(
719 "off",
720 QCoreApplication.translate(
721 "VersionControl",
722 "Repository status checking is switched off"))
723 self.__statusMonitorInfo("")
724
725 def setStatusMonitorInterval(self, interval, project):
726 """
727 Public method to change the monitor interval.
728
729 @param interval new interval in seconds (integer)
730 @param project reference to the project object
731 """
732 if self.statusMonitorThread is not None:
733 if interval == 0:
734 self.stopStatusMonitor()
735 else:
736 self.statusMonitorThread.setInterval(interval)
737 else:
738 self.startStatusMonitor(project)
739
740 def getStatusMonitorInterval(self):
741 """
742 Public method to get the monitor interval.
743
744 @return interval in seconds (integer)
745 """
746 if self.statusMonitorThread is not None:
747 return self.statusMonitorThread.getInterval()
748 else:
749 return 0
750
751 def setStatusMonitorAutoUpdate(self, auto):
752 """
753 Public method to enable the auto update function.
754
755 @param auto status of the auto update function (boolean)
756 """
757 if self.statusMonitorThread is not None:
758 self.statusMonitorThread.setAutoUpdate(auto)
759
760 def getStatusMonitorAutoUpdate(self):
761 """
762 Public method to retrieve the status of the auto update function.
763
764 @return status of the auto update function (boolean)
765 """
766 if self.statusMonitorThread is not None:
767 return self.statusMonitorThread.getAutoUpdate()
768 else:
769 return False
770
771 def checkVCSStatus(self):
772 """
773 Public method to wake up the VCS status monitor thread.
774 """
775 self.vcsStatusChanged.emit()
776
777 if self.statusMonitorThread is not None:
778 self.statusMonitorThread.checkStatus()
779
780 def clearStatusMonitorCachedState(self, name):
781 """
782 Public method to clear the cached VCS state of a file/directory.
783
784 @param name name of the entry to be cleared (string)
785 """
786 if self.statusMonitorThread is not None:
787 self.statusMonitorThread.clearCachedState(name)
788
789 def _createStatusMonitorThread(self, interval, project):
790 """
791 Protected method to create an instance of the VCS status monitor
792 thread.
793
794 Note: This method should be overwritten in subclasses in order to
795 support VCS status monitoring.
796
797 @param interval check interval for the monitor thread in seconds
798 (integer)
799 @param project reference to the project object
800 @return reference to the monitor thread (QThread)
801 """
802 return None # __IGNORE_WARNING_M831__

eric ide

mercurial