diff -r 000000000000 -r de9c2efb9d02 VCS/VersionControl.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VCS/VersionControl.py Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,735 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2002 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an abstract base class to be subclassed by all specific +VCS interfaces. +""" + +import os + +from PyQt4.QtCore import QObject, QThread, QMutex, QProcess, \ + SIGNAL, Qt +from PyQt4.QtGui import QApplication, QMessageBox + +import Preferences + +class VersionControl(QObject): + """ + Class implementing an abstract base class to be subclassed by all specific + VCS interfaces. + + It defines the vcs interface to be implemented by subclasses + and the common methods. + + @signal vcsStatusMonitorData(QStringList) emitted to update the VCS status + @signal vcsStatusMonitorStatus(QString, QString) emitted to signal the status of the + monitoring thread (ok, nok, op, off) and a status message + """ + + canBeCommitted = 1 # Indicates that a file/directory is in the vcs. + canBeAdded = 2 # Indicates that a file/directory is not in vcs. + + def __init__(self, parent=None, name=None): + """ + Constructor + + @param parent parent widget (QWidget) + @param name name of this object (string) + """ + QObject.__init__(self, parent) + if name: + self.setObjectName(name) + self.defaultOptions = { + 'global' : [''], + 'commit' : [''], + 'checkout' : [''], + 'update' : [''], + 'add' : [''], + 'remove' : [''], + 'diff' : [''], + 'log' : [''], + 'history' : [''], + 'status' : [''], + 'tag' : [''], + 'export' : [''] + } + self.interestingDataKeys = [] + self.options = {} + self.otherData = {} + self.canDetectBinaries = True + self.autoCommit = False + + self.statusMonitorThread = None + self.vcsExecutionMutex = QMutex() + + def vcsShutdown(self): + """ + Public method used to shutdown the vcs interface. + """ + raise RuntimeError('Not implemented') + + def vcsExists(self): + """ + Public method used to test for the presence of the vcs. + + It must return a bool to indicate the existance and a QString giving + an error message in case of failure. + + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsInit(self, vcsDir, noDialog = False): + """ + Public method used to initialize the vcs. + + It must return a boolean to indicate an execution without errors. + + @param vcsDir name of the VCS directory (string) + @param noDialog flag indicating quiet operations (boolean) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsConvertProject(self, vcsDataDict, project): + """ + Public method to convert an uncontrolled project to a version controlled project. + + @param vcsDataDict dictionary of data required for the conversion + @param project reference to the project object + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsImport(self, vcsDataDict, projectDir, noDialog = False): + """ + Public method used to import the project into the vcs. + + @param vcsDataDict dictionary of data required for the import + @param projectDir project directory (string) + @param noDialog flag indicating quiet operations + @return flag indicating an execution without errors (boolean) + and a flag indicating the version controll status (boolean) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsCheckout(self, vcsDataDict, projectDir, noDialog = False): + """ + Public method used to check the project out of the vcs. + + @param vcsDataDict dictionary of data required for the checkout + @param projectDir project directory to create (string) + @param noDialog flag indicating quiet operations + @return flag indicating an execution without errors (boolean) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsExport(self, vcsDataDict, projectDir): + """ + Public method used to export a directory from the vcs. + + It must return a boolean to indicate an execution without errors. + + @param vcsDataDict dictionary of data required for the export + @param projectDir project directory to create (string) + @return flag indicating an execution without errors (boolean) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsCommit(self, name, message, noDialog = False): + """ + Public method used to make the change of a file/directory permanent in the vcs. + + It must return a boolean to indicate an execution without errors. + + @param name file/directory name to be committed (string) + @param message message for this operation (string) + @param noDialog flag indicating quiet operations + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsUpdate(self, name, noDialog = False): + """ + Public method used to update a file/directory in the vcs. + + It must not return anything. + + @param name file/directory name to be updated (string) + @param noDialog flag indicating quiet operations (boolean) + @return flag indicating, that the update contained an add + or delete (boolean) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsAdd(self, name, isDir = False, noDialog = False): + """ + Public method used to add a file/directory in the vcs. + + It must not return anything. + + @param name file/directory name to be added (string) + @param isDir flag indicating name is a directory (boolean) + @param noDialog flag indicating quiet operations (boolean) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsAddBinary(self, name, isDir = False): + """ + Public method used to add a file/directory in binary mode in the vcs. + + It must not return anything. + + @param name file/directory name to be added (string) + @param isDir flag indicating name is a directory (boolean) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsAddTree(self, path): + """ + Public method to add a directory tree rooted at path in the vcs. + + It must not return anything. + + @param path root directory of the tree to be added (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsRemove(self, name, project = False, noDialog = False): + """ + Public method used to add a file/directory in the vcs. + + It must return a flag indicating successfull operation + + @param name file/directory name to be removed (string) + @param project flag indicating deletion of a project tree (boolean) + @param noDialog flag indicating quiet operations + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsMove(self, name, project, target = None, noDialog = False): + """ + Public method used to move a file/directory. + + @param name file/directory name to be moved (string) + @param project reference to the project object + @param target new name of the file/directory (string) + @param noDialog flag indicating quiet operations + @return flag indicating successfull operation (boolean) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsLog(self, name): + """ + Public method used to view the log of a file/directory in the vcs. + + It must not return anything. + + @param name file/directory name to show the log for (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsDiff(self, name): + """ + Public method used to view the diff of a file/directory in the vcs. + + It must not return anything. + + @param name file/directory name to be diffed (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsHistory(self, name): + """ + Public method used to view the history of a file/directory in the vcs. + + It must not return anything. + + @param name file/directory name to show the history for (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsStatus(self, name): + """ + Public method used to view the status of a file/directory in the vcs. + + It must not return anything. + + @param name file/directory name to show the status for (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsTag(self, name): + """ + Public method used to set the tag of a file/directory in the vcs. + + It must not return anything. + + @param name file/directory name to be tagged (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsRevert(self, name): + """ + Public method used to revert changes made to a file/directory. + + It must not return anything. + + @param name file/directory name to be reverted (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsSwitch(self, name): + """ + Public method used to switch a directory to a different tag/branch. + + It must not return anything. + + @param name directory name to be switched (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsMerge(self, name): + """ + Public method used to merge a tag/branch into the local project. + + It must not return anything. + + @param name file/directory name to be merged (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsRegisteredState(self, name): + """ + Public method used to get the registered state of a file in the vcs. + + @param name filename to check (string) + @return a combination of canBeCommited and canBeAdded or + 0 in order to signal an error + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsAllRegisteredStates(self, names, dname): + """ + Public method used to get the registered states of a number of files in the vcs. + + @param names dictionary with all filenames to be checked as keys + @param dname directory to check in (string) + @return the received dictionary completed with a combination of + canBeCommited and canBeAdded or None in order to signal an error + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsName(self): + """ + Public method returning the name of the vcs. + + @return name of the vcs (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsCleanup(self, name): + """ + Public method used to cleanup the local copy. + + @param name directory name to be cleaned up (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsCommandLine(self, name): + """ + Public method used to execute arbitrary vcs commands. + + @param name directory name of the working directory (string) + @exception RuntimeError not implemented + """ + raise RuntimeError('Not implemented') + + def vcsOptionsDialog(self, project, archive, editable = False, parent = None): + """ + Public method to get a dialog to enter repository info. + + @param project reference to the project object + @param archive name of the project in the repository (string) + @param editable flag indicating that the project name is editable (boolean) + @param parent parent widget (QWidget) + """ + raise RuntimeError('Not implemented') + + def vcsNewProjectOptionsDialog(self, parent = None): + """ + Public method to get a dialog to enter repository info for getting a new project. + + @param parent parent widget (QWidget) + """ + raise RuntimeError('Not implemented') + + def vcsRepositoryInfos(self, ppath): + """ + Public method to retrieve information about the repository. + + @param ppath local path to get the repository infos (string) + @return string with ready formated info for display (string) + """ + raise RuntimeError('Not implemented') + + def vcsGetProjectBrowserHelper(self, browser, project, isTranslationsBrowser = False): + """ + Public method to instanciate a helper object for the different project browsers. + + @param browser reference to the project browser object + @param project reference to the project object + @param isTranslationsBrowser flag indicating, the helper is requested for the + translations browser (this needs some special treatment) + @return the project browser helper object + """ + raise RuntimeError('Not implemented') + + def vcsGetProjectHelper(self, project): + """ + Public method to instanciate a helper object for the project. + + @param project reference to the project object + @return the project helper object + """ + raise RuntimeError('Not implemented') + + ##################################################################### + ## methods above need to be implemented by a subclass + ##################################################################### + + def clearStatusCache(self): + """ + Public method to clear the status cache. + """ + pass + + def vcsDefaultOptions(self): + """ + Public method used to retrieve the default options for the vcs. + + @return a dictionary with the vcs operations as key and + the respective options as values. The key 'global' must contain + the global options. The other keys must be 'commit', 'update', + 'add', 'remove', 'diff', 'log', 'history', 'tag', 'status' and 'export'. + """ + return self.defaultOptions + + def vcsSetOptions(self, options): + """ + Public method used to set the options for the vcs. + + @param options a dictionary of option strings with keys as + defined by the default options + """ + for key in options: + try: + self.options[key] = options[key] + except KeyError: + pass + + def vcsGetOptions(self): + """ + Public method used to retrieve the options of the vcs. + + @return a dictionary of option strings that can be passed to + vcsSetOptions. + """ + return self.options + + def vcsSetOtherData(self, data): + """ + Public method used to set vcs specific data. + + @param data a dictionary of vcs specific data + """ + for key in data: + try: + self.otherData[key] = data[key] + except KeyError: + pass + + def vcsGetOtherData(self): + """ + Public method used to retrieve vcs specific data. + + @return a dictionary of vcs specific data + """ + return self.otherData + + def vcsSetData(self, key, value): + """ + Public method used to set an entry in the otherData dictionary. + + @param key the key of the data (string) + @param value the value of the data + """ + if key in self.interestingDataKeys: + self.otherData[key] = value + + def vcsSetDataFromDict(self, dict): + """ + Public method used to set entries in the otherData dictionary. + + @param dict dictionary to pick entries from + """ + for key in self.interestingDataKeys: + if key in dict: + self.otherData[key] = dict[key] + + ##################################################################### + ## below are some utility methods + ##################################################################### + + def startSynchronizedProcess(self, proc, program, arguments, workingDir = None): + """ + Public method to start a synchroneous process + + This method starts a process and waits + for its end while still serving the Qt event loop. + + @param proc process to start (QProcess) + @param program path of the executable to start (string) + @param arguments list of arguments for the process (list of strings) + @param workingDir working directory for the process (string) + @return flag indicating normal exit (boolean) + """ + if proc is None: + return + + if workingDir: + proc.setWorkingDirectory(workingDir) + proc.start(program, arguments) + procStarted = proc.waitForStarted() + if not procStarted: + QMessageBox.critical(None, + self.trUtf8('Process Generation Error'), + self.trUtf8( + 'The process {0} could not be started. ' + 'Ensure, that it is in the search path.' + ).format(program)) + return False + else: + while proc.state() == QProcess.Running: + QApplication.processEvents() + QThread.msleep(300) + QApplication.processEvents() + return (proc.exitStatus() == QProcess.NormalExit) and (proc.exitCode() == 0) + + def splitPath(self, name): + """ + Public method splitting name into a directory part and a file part. + + @param name path name (string) + @return a tuple of 2 strings (dirname, filename). + """ + if os.path.isdir(name): + dn = os.path.abspath(name) + fn = "." + else: + dn, fn = os.path.split(name) + return (dn, fn) + + def splitPathList(self, names): + """ + Public method splitting the list of names into a common directory part and + a file list. + + @param names list of paths (list of strings) + @return a tuple of string and list of strings (dirname, filenamelist) + """ + dname = os.path.commonprefix(names) + if dname: + if not dname.endswith(os.sep): + dname = os.path.dirname(dname) + os.sep + fnames = [n.replace(dname, '') for n in names] + dname = os.path.dirname(dname) + return (dname, fnames) + else: + return ("/", names) + + def addArguments(self, args, argslist): + """ + Protected method to add an argument list to the already present arguments. + + @param args current arguments list (list of strings) + @param argslist list of arguments (list of strings) + """ + for arg in argslist: + if arg != '': + args.append(arg) + + ############################################################################ + ## VCS status monitor thread related methods + ############################################################################ + + def __statusMonitorStatus(self, status, statusMsg): + """ + Private method to receive the status monitor status. + + It simply reemits the received status. + + @param status status of the monitoring thread (QString, ok, nok or off) + @param statusMsg explanotory text for the signaled status (string) + """ + self.emit(SIGNAL("vcsStatusMonitorStatus(QString, QString)"), status, statusMsg) + QApplication.flush() + + def __statusMonitorData(self, statusList): + """ + Private method to receive the status monitor status. + + It simply reemits the received status list. + + @param statusList list of status records (list of strings) + """ + self.emit(SIGNAL("vcsStatusMonitorData(QStringList)"), statusList) + QApplication.flush() + + def startStatusMonitor(self, project): + """ + Public method to start the VCS status monitor thread. + + @param project reference to the project object + @return reference to the monitor thread (QThread) + """ + if project.pudata["VCSSTATUSMONITORINTERVAL"]: + vcsStatusMonitorInterval = project.pudata["VCSSTATUSMONITORINTERVAL"][0] + else: + vcsStatusMonitorInterval = Preferences.getVCS("StatusMonitorInterval") + if vcsStatusMonitorInterval > 0: + self.statusMonitorThread = \ + self._createStatusMonitorThread(vcsStatusMonitorInterval, project) + if self.statusMonitorThread is not None: + self.connect(self.statusMonitorThread, + SIGNAL("vcsStatusMonitorData(QStringList)"), + self.__statusMonitorData, Qt.QueuedConnection) + self.connect(self.statusMonitorThread, + SIGNAL("vcsStatusMonitorStatus(QString, QString)"), + self.__statusMonitorStatus, Qt.QueuedConnection) + self.statusMonitorThread.setAutoUpdate( + Preferences.getVCS("AutoUpdate")) + self.statusMonitorThread.start() + else: + self.statusMonitorThread = None + return self.statusMonitorThread + + def stopStatusMonitor(self): + """ + Public method to stop the VCS status monitor thread. + """ + if self.statusMonitorThread is not None: + self.__statusMonitorData(["--RESET--"]) + self.disconnect(self.statusMonitorThread, + SIGNAL("vcsStatusMonitorData(QStringList)"), + self.__statusMonitorData) + self.disconnect(self.statusMonitorThread, + SIGNAL("vcsStatusMonitorStatus(QString, QString)"), + self.__statusMonitorStatus) + self.statusMonitorThread.stop() + self.statusMonitorThread.wait(10000) + if not self.statusMonitorThread.isFinished(): + self.statusMonitorThread.terminate() + self.statusMonitorThread.wait(10000) + self.statusMonitorThread = None + self.__statusMonitorStatus("off", + self.trUtf8("Repository status checking is switched off")) + + def setStatusMonitorInterval(self, interval, project): + """ + Public method to change the monitor interval. + + @param interval new interval in seconds (integer) + @param project reference to the project object + """ + if self.statusMonitorThread is not None: + if interval == 0: + self.stopStatusMonitor() + else: + self.statusMonitorThread.setInterval(interval) + else: + self.startStatusMonitor(project) + + def getStatusMonitorInterval(self): + """ + Public method to get the monitor interval. + + @return interval in seconds (integer) + """ + if self.statusMonitorThread is not None: + return self.statusMonitorThread.getInterval() + else: + return 0 + + def setStatusMonitorAutoUpdate(self, auto): + """ + Public method to enable the auto update function. + + @param auto status of the auto update function (boolean) + """ + if self.statusMonitorThread is not None: + self.statusMonitorThread.setAutoUpdate(auto) + + def getStatusMonitorAutoUpdate(self): + """ + Public method to retrieve the status of the auto update function. + + @return status of the auto update function (boolean) + """ + if self.statusMonitorThread is not None: + return self.statusMonitorThread.getAutoUpdate() + else: + return False + + def checkVCSStatus(self): + """ + Public method to wake up the VCS status monitor thread. + """ + if self.statusMonitorThread is not None: + self.statusMonitorThread.checkStatus() + + def clearStatusMonitorCachedState(self, name): + """ + Public method to clear the cached VCS state of a file/directory. + + @param name name of the entry to be cleared (string) + """ + if self.statusMonitorThread is not None: + self.statusMonitorThread.clearCachedState(name) + + def _createStatusMonitorThread(self, interval, project): + """ + Protected method to create an instance of the VCS status monitor thread. + + Note: This method should be overwritten in subclasses in order to support + VCS status monitoring. + + @param interval check interval for the monitor thread in seconds (integer) + @param project reference to the project object + @return reference to the monitor thread (QThread) + """ + return None