diff -r c822ccc4d138 -r dd9f0bca5e2f Plugins/VcsPlugins/vcsMercurial/hg.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/VcsPlugins/vcsMercurial/hg.py Mon Apr 12 18:00:42 2010 +0000 @@ -0,0 +1,1501 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the version control systems interface to Mercurial. +""" + +import os +import shutil +import urllib.request, urllib.parse, urllib.error + +from PyQt4.QtCore import QProcess, SIGNAL +from PyQt4.QtGui import QMessageBox, QApplication, QDialog, QInputDialog + +from E5Gui.E5Application import e5App + +from VCS.VersionControl import VersionControl +from VCS.RepositoryInfoDialog import VcsRepositoryInfoDialog + +from .HgDialog import HgDialog +from .HgCommitDialog import HgCommitDialog +from .HgOptionsDialog import HgOptionsDialog +from .HgNewProjectOptionsDialog import HgNewProjectOptionsDialog +from .HgCopyDialog import HgCopyDialog +from .HgLogDialog import HgLogDialog +from .HgLogBrowserDialog import HgLogBrowserDialog +from .HgDiffDialog import HgDiffDialog +from .HgRevisionsSelectionDialog import HgRevisionsSelectionDialog +from .HgRevisionSelectionDialog import HgRevisionSelectionDialog +from .HgMergeDialog import HgMergeDialog +from .HgStatusMonitorThread import HgStatusMonitorThread +from .HgStatusDialog import HgStatusDialog +from .HgAnnotateDialog import HgAnnotateDialog +from .HgTagDialog import HgTagDialog +from .HgTagBranchListDialog import HgTagBranchListDialog +from .HgCommandDialog import HgCommandDialog + +from .ProjectBrowserHelper import HgProjectBrowserHelper + +import Preferences +import Utilities + +class Hg(VersionControl): + """ + Class implementing the version control systems interface to Mercurial. + + @signal committed() emitted after the commit action has completed + """ + def __init__(self, plugin, parent=None, name=None): + """ + Constructor + + @param plugin reference to the plugin object + @param parent parent widget (QWidget) + @param name name of this object (string) + """ + VersionControl.__init__(self, parent, name) + self.defaultOptions = { + 'global' : [''], + 'commit' : [''], + 'checkout' : [''], + 'update' : [''], + 'add' : [''], + 'remove' : [''], + 'diff' : [''], + 'log' : [''], + 'history' : [''], + 'status' : [''], + 'tag' : [''], + 'export' : [''] + } + + self.__plugin = plugin + self.__ui = parent + + self.options = self.defaultOptions + self.tagsList = [] + self.branchesList = [] + self.allTagsBranchesList = [] + self.showedTags = False + self.showedBranches = False + + self.tagTypeList = [ + 'tags', + 'branches', + ] + + self.commandHistory = [] + + if "HG_ASP_DOT_NET_HACK" in os.environ: + self.adminDir = '_hg' + else: + self.adminDir = '.hg' + + self.log = None + self.diff = None + self.status = None + self.tagbranchList = None + self.annotate = None + + self.statusCache = {} + + self.__commitData = {} + self.__commitDialog = None + + def getPlugin(self): + """ + Public method to get a reference to the plugin object. + + @return reference to the plugin object (VcsMercurialPlugin) + """ + return self.__plugin + + def vcsShutdown(self): + """ + Public method used to shutdown the Mercurial interface. + """ + if self.log is not None: + self.log.close() + if self.diff is not None: + self.diff.close() + if self.status is not None: + self.status.close() + if self.tagbranchList is not None: + self.tagbranchList.close() + if self.annotate is not None: + self.annotate.close() + + def vcsExists(self): + """ + Public method used to test for the presence of the hg executable. + + @return flag indicating the existance (boolean) and an error message (string) + """ + self.versionStr = '' + errMsg = "" + ioEncoding = Preferences.getSystem("IOEncoding") + + process = QProcess() + process.start('hg', ['version']) + procStarted = process.waitForStarted() + if procStarted: + finished = process.waitForFinished(30000) + if finished and process.exitCode() == 0: + output = \ + str(process.readAllStandardOutput(), ioEncoding, 'replace') + self.versionStr = output.splitlines()[0].split()[-1][0:-1] + return True, errMsg + else: + if finished: + errMsg = \ + self.trUtf8("The hg process finished with the exit code {0}")\ + .format(process.exitCode()) + else: + errMsg = self.trUtf8("The hg process did not finish within 30s.") + else: + errMsg = self.trUtf8("Could not start the hg executable.") + + return False, errMsg + + def vcsInit(self, vcsDir, noDialog = False): + """ + Public method used to initialize the mercurial repository. + + The initialization is done, when a project is converted into a Mercurial + controlled project. Therefore we always return TRUE without doing anything. + + @param vcsDir name of the VCS directory (string) + @param noDialog flag indicating quiet operations (boolean) + @return always TRUE + """ + return True + + 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 + """ + success = self.vcsImport(vcsDataDict, project.ppath)[0] + if not success: + QMessageBox.critical(None, + self.trUtf8("Create project repository"), + self.trUtf8("""The project repository could not be created.""")) + else: + pfn = project.pfile + if not os.path.isfile(pfn): + pfn += "z" + project.closeProject() + project.openProject(pfn) + + def vcsImport(self, vcsDataDict, projectDir, noDialog = False): + """ + Public method used to import the project into the Subversion repository. + + @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) + """ + ignorePatterns = [ + "glob:.eric5project", + "glob:.ropeproject", + "glob:.directory", + "glob:*.pyc", + "glob:*.orig", + "glob:*.bak", + ] + + msg = vcsDataDict["message"] + if not msg: + msg = '***' + + args = [] + args.append('init') + args.append(projectDir) + dia = HgDialog(self.trUtf8('Creating Mercurial repository')) + res = dia.startProcess(args) + if res: + dia.exec_() + status = dia.normalExit() + + if status: + try: + # create a .hgignore file + ignore = open(os.path.join(projectDir, ".hgignore"), "w") + ignore.write("\n".join(ignorePatterns)) + ignore.close() + except IOError: + status = False + + if status: + args = [] + args.append('commit') + args.append('--addremove') + args.append('--message') + args.append(msg) + args.append(projectDir) + dia = HgDialog(self.trUtf8('Initial commit to Mercurial repository')) + res = dia.startProcess(args) + if res: + dia.exec_() + status = dia.normalExit() + + return status, False + + def vcsCheckout(self, vcsDataDict, projectDir, noDialog = False): + """ + Public method used to check the project out of a Mercurial repository (clone). + + @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) + """ + noDialog = False + try: + rev = vcsDataDict["revision"] + except KeyError: + rev = None + vcsUrl = self.hgNormalizeURL(vcsDataDict["url"]) + if vcsUrl.startswith('/'): + vcsUrl = 'file://%s' % vcsUrl + elif vcsUrl[1] in ['|', ':']: + vcsUrl = 'file:///%s' % vcsUrl + + args = [] + args.append('clone') + self.addArguments(args, self.options['global']) + self.addArguments(args, self.options['checkout']) + if rev: + args.append("--rev") + args.append(rev) + args.append(self.__hgURL(vcsUrl)) + args.append(projectDir) + + if noDialog: + return self.startSynchronizedProcess(QProcess(), 'hg', args) + else: + dia = HgDialog(self.trUtf8('Cloning project from a Mercurial repository')) + res = dia.startProcess(args) + if res: + dia.exec_() + return dia.normalExit() + + def vcsExport(self, vcsDataDict, projectDir): + """ + Public method used to export a directory from the Subversion repository. + + @param vcsDataDict dictionary of data required for the checkout + @param projectDir project directory to create (string) + @return flag indicating an execution without errors (boolean) + """ + status = self.vcsCheckout(vcsDataDict, projectDir) + shutil.rmtree(os.path.join(projectDir, self.adminDir), True) + if os.path.exists(os.path.join(projectDir, '.hgignore')): + os.remove(os.path.join(projectDir, '.hgignore')) + return status + + def vcsCommit(self, name, message, noDialog = False, closeBranch = False): + """ + Public method used to make the change of a file/directory permanent in the + Mercurial repository. + + @param name file/directory name to be committed (string or list of strings) + @param message message for this operation (string) + @param noDialog flag indicating quiet operations + @keyparam closeBranch flag indicating a close branch commit (boolean) + """ + msg = message + + if not noDialog and not msg: + # call CommitDialog and get message from there + if self.__commitDialog is None: + self.__commitDialog = HgCommitDialog(self, self.__ui) + self.connect(self.__commitDialog, SIGNAL("accepted()"), + self.__vcsCommit_Step2) + self.__commitDialog.show() + self.__commitDialog.raise_() + self.__commitDialog.activateWindow() + + self.__commitData["name"] = name + self.__commitData["msg"] = msg + self.__commitData["noDialog"] = noDialog + self.__commitData["closeBranch"] = closeBranch + + if noDialog: + self.__vcsCommit_Step2() + + def __vcsCommit_Step2(self): + """ + Private slot performing the second step of the commit action. + """ + name = self.__commitData["name"] + msg = self.__commitData["msg"] + noDialog = self.__commitData["noDialog"] + closeBranch = self.__commitData["closeBranch"] + + if self.__commitDialog is not None: + msg = self.__commitDialog.logMessage() + self.disconnect(self.__commitDialog, SIGNAL("accepted()"), + self.__vcsCommit_Step2) + self.__commitDialog = None + + if not msg: + msg = '***' + + args = [] + args.append('commit') + self.addArguments(args, self.options['global']) + self.addArguments(args, self.options['commit']) + args.append("-v") + if closeBranch: + args.append("--close-branch") + args.append("--message") + args.append(msg) + if isinstance(name, list): + dname, fnames = self.splitPathList(name) + else: + dname, fname = self.splitPath(name) + + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + if isinstance(name, list): + self.addArguments(args, fnames) + else: + if dname != repodir or fname != ".": + args.append(fname) + + if noDialog: + self.startSynchronizedProcess(QProcess(), "hg", args, dname) + else: + dia = HgDialog(self.trUtf8('Commiting changes to Mercurial repository')) + res = dia.startProcess(args, dname) + if res: + dia.exec_() + self.emit(SIGNAL("committed()")) + self.checkVCSStatus() + + def vcsUpdate(self, name, noDialog = False, revision = None): + """ + Public method used to update a file/directory with the Mercurial repository. + + @param name file/directory name to be updated (string or list of strings) + @param noDialog flag indicating quiet operations (boolean) + @keyparam revision revision to update to (string) + @return flag indicating, that the update contained an add + or delete (boolean) + """ + args = [] + args.append('update') + self.addArguments(args, self.options['global']) + self.addArguments(args, self.options['update']) + if revision is not None: + args.append("-r") + args.append(revision) + + if isinstance(name, list): + dname, fnames = self.splitPathList(name) + else: + dname, fname = self.splitPath(name) + + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return False + + if noDialog: + self.startSynchronizedProcess(QProcess(), "hg", args, repodir) + res = False + else: + dia = HgDialog(self.trUtf8('Synchronizing with the Mercurial repository')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + res = dia.hasAddOrDelete() + self.checkVCSStatus() + return res + + def vcsAdd(self, name, isDir = False, noDialog = False): + """ + Public method used to add a file/directory to the Mercurial repository. + + @param name file/directory name to be added (string) + @param isDir flag indicating name is a directory (boolean) + @param noDialog flag indicating quiet operations + """ + args = [] + args.append('add') + self.addArguments(args, self.options['global']) + self.addArguments(args, self.options['add']) + args.append("-v") + + if isinstance(name, list): + if isDir: + dname, fname = os.path.split(name[0]) + else: + dname, fnames = self.splitPathList(name) + else: + if isDir: + dname, fname = os.path.split(name) + else: + dname, fname = self.splitPath(name) + + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + if isinstance(name, list): + self.addArguments(args, name) + else: + args.append(name) + + if noDialog: + self.startSynchronizedProcess(QProcess(), "hg", args, repodir) + else: + dia = HgDialog(\ + self.trUtf8('Adding files/directories to the Mercurial repository')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + + def vcsAddBinary(self, name, isDir = False): + """ + Public method used to add a file/directory in binary mode to the + Mercurial repository. + + @param name file/directory name to be added (string) + @param isDir flag indicating name is a directory (boolean) + """ + self.vcsAdd(name, isDir) + + def vcsAddTree(self, path): + """ + Public method to add a directory tree rooted at path to the Mercurial repository. + + @param path root directory of the tree to be added (string or list of strings)) + """ + self.vcsAdd(path, isDir = False) + + def vcsRemove(self, name, project = False, noDialog = False): + """ + Public method used to remove a file/directory from the Mercurial repository. + + The default operation is to remove the local copy as well. + + @param name file/directory name to be removed (string or list of strings)) + @param project flag indicating deletion of a project tree (boolean) (not needed) + @param noDialog flag indicating quiet operations + @return flag indicating successfull operation (boolean) + """ + args = [] + args.append('remove') + self.addArguments(args, self.options['global']) + self.addArguments(args, self.options['remove']) + args.append("-v") + if noDialog and '--force' not in args: + args.append('--force') + + if isinstance(name, list): + dname, fnames = self.splitPathList(name) + self.addArguments(args, name) + else: + dname, fname = self.splitPath(name) + args.append(name) + + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return False + + if noDialog: + res = self.startSynchronizedProcess(QProcess(), "hg", args, repodir) + else: + dia = HgDialog(\ + self.trUtf8('Removing files/directories from the Mercurial repository')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + res = dia.normalExitWithoutErrors() + + return res + + 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) + """ + isDir = os.path.isdir(name) + opts = self.options['global'][:] + force = '--force' in opts + if force: + opts.remove('--force') + + res = False + if noDialog: + if target is None: + return False + force = True + accepted = True + else: + dlg = HgCopyDialog(name, None, True, force) + accepted = dlg.exec_() == QDialog.Accepted + if accepted: + target, force = dlg.getData() + + if accepted: + args = [] + args.append('rename') + self.addArguments(args, opts) + args.append("-v") + if force: + args.append('--force') + args.append(name) + args.append(target) + + dname, fname = self.splitPath(name) + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return False + + if noDialog: + res = self.startSynchronizedProcess(QProcess(), "hg", args, repodir) + else: + dia = HgDialog(self.trUtf8('Renaming {0}').format(name)) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + res = dia.normalExit() + if res: + if target.startswith(project.getProjectPath()): + if isDir: + project.moveDirectory(name, target) + else: + project.renameFileInPdata(name, target) + else: + if isDir: + project.removeDirectory(name) + else: + project.removeFile(name) + return res + + def vcsLog(self, name): + """ + Public method used to view the log of a file/directory from the + Mercurial repository. + + @param name file/directory name to show the log of (string) + """ + self.log = HgLogDialog(self) + self.log.show() + self.log.start(name) + + def vcsDiff(self, name): + """ + Public method used to view the difference of a file/directory to the + Mercurial repository. + + If name is a directory and is the project directory, all project files + are saved first. If name is a file (or list of files), which is/are being edited + and has unsaved modification, they can be saved or the operation may be aborted. + + @param name file/directory name to be diffed (string) + """ + if isinstance(name, list): + names = name[:] + else: + names = [name] + for nam in names: + if os.path.isfile(nam): + editor = e5App().getObject("ViewManager").getOpenEditor(nam) + if editor and not editor.checkDirty() : + return + else: + project = e5App().getObject("Project") + if nam == project.ppath and not project.saveAllScripts(): + return + self.diff = HgDiffDialog(self) + self.diff.show() + QApplication.processEvents() + self.diff.start(name) + + def vcsStatus(self, name): + """ + Public method used to view the status of files/directories in the + Mercurial repository. + + @param name file/directory name(s) to show the status of + (string or list of strings) + """ + self.status = HgStatusDialog(self) + self.status.show() + self.status.start(name) + + def vcsTag(self, name): + """ + Public method used to set the tag in the Mercurial repository. + + @param name file/directory name to be tagged (string) + """ + dname, fname = self.splitPath(name) + + # find the root of the repo + repodir = str(dname) + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + dlg = HgTagDialog(self.tagsList) + if dlg.exec_() == QDialog.Accepted: + tag, tagOp = dlg.getParameters() + if tag in self.tagsList: + self.tagsList.remove(tag) + self.tagsList.insert(0, tag) + else: + return + + args = [] + args.append('tag') + if tagOp == HgTagDialog.CreateLocalTag: + args.append('--local') + elif tagOp == HgTagDialog.DeleteTag: + args.append('--remove') + args.append('--message') + if tagOp != HgTagDialog.DeleteTag: + args.append("Created tag <{0}>.".format(tag)) + else: + args.append("Removed tag <{0}>.".format(tag)) + args.append(tag) + + dia = HgDialog(self.trUtf8('Taging in the Mercurial repository')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + + def vcsRevert(self, name): + """ + Public method used to revert changes made to a file/directory. + + @param name file/directory name to be reverted (string) + """ + args = [] + args.append('revert') + self.addArguments(args, self.options['global']) + args.append("--no-backup") + args.append("-v") + if isinstance(name, list): + dname, fnames = self.splitPathList(name) + self.addArguments(args, name) + else: + dname, fname = self.splitPath(name) + args.append(name) + + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + dia = HgDialog(self.trUtf8('Reverting changes')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + self.checkVCSStatus() + + def vcsMerge(self, name): + """ + Public method used to merge a URL/revision into the local project. + + @param name file/directory name to be merged (string) + """ + dname, fname = self.splitPath(name) + + opts = self.options['global'][:] + force = '--force' in opts + if force: + del opts[opts.index('--force')] + + dlg = HgMergeDialog(force, self.tagsList, self.branchesList) + if dlg.exec_() == QDialog.Accepted: + rev, force = dlg.getParameters() + else: + return + + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + args = [] + args.append('merge') + self.addArguments(args, opts) + if force: + args.append("--force") + if rev: + args.append("--rev") + args.append(rev) + + dia = HgDialog(self.trUtf8('Merging').format(name)) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + self.checkVCSStatus() + + def vcsSwitch(self, name): + """ + Public method used to switch a working directory to a different revision. + + @param name directory name to be switched (string) + """ + dlg = HgRevisionSelectionDialog(self.tagsList, self.branchesList) + if dlg.exec_() == QDialog.Accepted: + rev = dlg.getRevision() + self.vcsUpdate(name, revision = rev) + + 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 + """ + if name.endswith(os.sep): + name = name[:-1] + dname, fname = self.splitPath(name) + + if fname == '.' and os.path.isdir(os.path.join(dname, self.adminDir)): + return self.canBeCommitted + + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return 0 + + ioEncoding = Preferences.getSystem("IOEncoding") + process = QProcess() + args = [] + args.append('status') + args.append('--all') + args.append('--noninteractive') + process.setWorkingDirectory(repodir) + process.start('hg', args) + procStarted = process.waitForStarted() + if procStarted: + finished = process.waitForFinished(30000) + if finished and process.exitCode() == 0: + output = \ + str(process.readAllStandardOutput(), ioEncoding, 'replace') + for line in output.splitlines(): + flag, path = line.split(" ", 1) + absname = os.path.join(repodir, os.path.normcase(path)) + if flag not in "?I": + if fname == '.': + if absname.startswith(dname): + return self.canBeCommitted + else: + if absname == name: + return self.canBeCommitted + + return self.canBeAdded + + def vcsAllRegisteredStates(self, names, dname, shortcut = True): + """ + Public method used to get the registered states of a number of files in the vcs. + + <b>Note:</b> If a shortcut is to be taken, the code will only check, if the named + directory has been scanned already. If so, it is assumed, that the states for + all files have been populated by the previous run. + + @param names dictionary with all filenames to be checked as keys + @param dname directory to check in (string) + @param shortcut flag indicating a shortcut should be taken (boolean) + @return the received dictionary completed with a combination of + canBeCommited and canBeAdded or None in order to signal an error + """ + if dname.endswith(os.sep): + dname = dname[:-1] + + found = False + for name in list(self.statusCache.keys()): + if os.path.dirname(name) == dname: + if shortcut: + found = True + break + if name in names: + found = True + names[name] = self.statusCache[name] + + if not found: + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return names + + ioEncoding = Preferences.getSystem("IOEncoding") + process = QProcess() + args = [] + args.append('status') + args.append('--all') + args.append('--noninteractive') + process.setWorkingDirectory(dname) + process.start('hg', args) + procStarted = process.waitForStarted() + if procStarted: + finished = process.waitForFinished(30000) + if finished and process.exitCode() == 0: + dirs = [x for x in names.keys() if os.path.isdir(x)] + output = \ + str(process.readAllStandardOutput(), ioEncoding, 'replace') + for line in output.splitlines(): + flag, path = line.split(" ", 1) + name = os.path.join(repodir, os.path.normcase(path)) + if name.startswith(dname): + if flag not in "?I": + if name in names: + names[name] = self.canBeCommitted + dirName = os.path.dirname(name) + if dirName in names: + names[dirName] = self.canBeCommitted + self.statusCache[name] = self.canBeCommitted + self.statusCache[dirName] = self.canBeCommitted + if dirs: + for d in dirs: + if name.startswith(d): + names[d] = self.canBeCommitted + dirs.remove(d) + break + else: + self.statusCache[name] = self.canBeAdded + dirName = os.path.dirname(name) + if dirName not in self.statusCache: + self.statusCache[dirName] = self.canBeAdded + + return names + + def clearStatusCache(self): + """ + Public method to clear the status cache. + """ + self.statusCache = {} + + def vcsName(self): + """ + Public method returning the name of the vcs. + + @return always 'Mercurial' (string) + """ + return "Mercurial" + + def vcsCleanup(self, name): + """ + Public method used to cleanup the working directory. + + @param name directory name to be cleaned up (string) + """ + patterns = ['*.orig', '*.rej'] + + entries = [] + for pat in patterns: + entries.extend(Utilities.direntries(name, True, pat)) + + for entry in entries: + try: + os.remove(entry) + except OSError: + pass + + def vcsCommandLine(self, name): + """ + Public method used to execute arbitrary mercurial commands. + + @param name directory name of the working directory (string) + """ + dlg = HgCommandDialog(self.commandHistory, name) + if dlg.exec_() == QDialog.Accepted: + command = dlg.getData() + commandList = Utilities.parseOptionString(command) + + # This moves any previous occurrence of these arguments to the head + # of the list. + if command in self.commandHistory: + self.commandHistory.remove(command) + self.commandHistory.insert(0, command) + + args = [] + self.addArguments(args, commandList) + + # find the root of the repo + repodir = name + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + dia = HgDialog(self.trUtf8('Mercurial command')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + + 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) + """ + return HgOptionsDialog(self, project, parent) + + 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) + """ + return HgNewProjectOptionsDialog(self, parent) + + 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) + """ + info = [] + + process = QProcess() + args = [] + args.append('parents') + args.append('--template') + args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@' + '{date|isodate}@@@{branches}\n') + process.setWorkingDirectory(ppath) + process.start('hg', args) + procStarted = process.waitForStarted() + if procStarted: + finished = process.waitForFinished(30000) + if finished and process.exitCode() == 0: + output = str(process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), 'replace') + index = 0 + for line in output.splitlines(): + index += 1 + changeset, tags, author, date, branches = line.split("@@@") + cdate, ctime = date.split()[:2] + info.append("""<p><table>""") + info.append(QApplication.translate("mercurial", + """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n""" + """<tr><td><b>Changeset</b></td><td>{1}</td></tr>""")\ + .format(index, changeset)) + if tags: + info.append(QApplication.translate("mercurial", + """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")\ + .format('<br/>'.join(tags.split()))) + if branches: + info.append(QApplication.translate("mercurial", + """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")\ + .format('<br/>'.join(branches.split()))) + info.append(QApplication.translate("mercurial", + """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n""" + """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n""" + """<tr><td><b>Committed time</b></td><td>{2}</td></tr>""")\ + .format(author, cdate, ctime)) + info.append("""</table></p>""") + + url = "" + args = [] + args.append('showconfig') + args.append('paths.default') + process.setWorkingDirectory(ppath) + process.start('hg', args) + procStarted = process.waitForStarted() + if procStarted: + finished = process.waitForFinished(30000) + if finished and process.exitCode() == 0: + output = str(process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), 'replace') + url = output.splitlines()[0].strip() + + return QApplication.translate('mercurial', + """<h3>Repository information</h3>\n""" + """<p><table>\n""" + """<tr><td><b>Mercurial V.</b></td><td>{0}</td></tr>\n""" + """<tr></tr>\n""" + """<tr><td><b>URL</b></td><td>{1}</td></tr>\n""" + """</table></p>\n""" + """{2}""" + ).format(self.versionStr, url, "\n".join(info)) + + ############################################################################ + ## Private Subversion specific methods are below. + ############################################################################ + + def __hgURL(self, url): + """ + Private method to format a url for Mercurial. + + @param url unformatted url string (string) + @return properly formated url for subversion (string) + """ + url = self.hgNormalizeURL(url) + url = url.split(':', 2) + if len(url) == 4: + scheme = url[0] + user = url[1] + host = url[2] + port, path = url[3].split("/",1) + return "%s:%s:%s:%s/%s" % (scheme, user, host, port, urllib.parse.quote(path)) + elif len(url) == 3: + scheme = url[0] + host = url[1] + port, path = url[2].split("/",1) + return "%s:%s:%s/%s" % (scheme, host, port, urllib.parse.quote(path)) + else: + scheme = url[0] + if scheme == "file": + return "%s:%s" % (scheme, urllib.parse.quote(url[1])) + else: + host, path = url[1][2:].split("/",1) + return "%s://%s/%s" % (scheme, host, urllib.parse.quote(path)) + + def hgNormalizeURL(self, url): + """ + Public method to normalize a url for Mercurial. + + @param url url string (string) + @return properly normalized url for subversion (string) + """ + url = url.replace('\\', '/') + if url.endswith('/'): + url = url[:-1] + urll = url.split('//') + return "%s//%s" % (urll[0], '/'.join(urll[1:])) + + def hgCopy(self, name, project): + """ + Public method used to copy a file/directory. + + @param name file/directory name to be copied (string) + @param project reference to the project object + @return flag indicating successfull operation (boolean) + """ + dlg = HgCopyDialog(name) + res = False + if dlg.exec_() == QDialog.Accepted: + target, force = dlg.getData() + + args = [] + args.append('copy') + self.addArguments(args, self.options['global']) + args.append("-v") + args.append(name) + args.append(target) + + dname, fname = self.splitPath(name) + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return False + + dia = HgDialog(self.trUtf8('Copying {0}') + .format(name)) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + res = dia.normalExit() + if res and \ + target.startswith(project.getProjectPath()): + if os.path.isdir(name): + project.copyDirectory(name, target) + else: + project.appendFile(target) + return res + + def hgListTagBranch(self, path, tags = True): + """ + Public method used to list the available tags or branches. + + @param path directory name of the project (string) + @param tags flag indicating listing of branches or tags + (False = branches, True = tags) + """ + self.tagbranchList = HgTagBranchListDialog(self) + self.tagbranchList.show() + if tags: + if not self.showedTags: + self.showedTags = True + allTagsBranchesList = self.allTagsBranchesList + else: + self.tagsList = [] + allTagsBranchesList = None + self.tagbranchList.start(path, tags, + self.tagsList, allTagsBranchesList) + else: + if not self.showedBranches: + self.showedBranches = True + allTagsBranchesList = self.allTagsBranchesList + else: + self.branchesList = [] + allTagsBranchesList = None + self.tagbranchList.start(path, tags, + self.branchesList, self.allTagsBranchesList) + + def hgAnnotate(self, name): + """ + Public method to show the output of the hg annotate command. + + @param name file name to show the annotations for (string) + """ + self.annotate = HgAnnotateDialog(self) + self.annotate.show() + self.annotate.start(name) + + def hgExtendedDiff(self, name): + """ + Public method used to view the difference of a file/directory to the + Mercurial repository. + + If name is a directory and is the project directory, all project files + are saved first. If name is a file (or list of files), which is/are being edited + and has unsaved modification, they can be saved or the operation may be aborted. + + This method gives the chance to enter the revisions to be compared. + + @param name file/directory name to be diffed (string) + """ + if isinstance(name, list): + names = name[:] + else: + names = [name] + for nam in names: + if os.path.isfile(nam): + editor = e5App().getObject("ViewManager").getOpenEditor(nam) + if editor and not editor.checkDirty() : + return + else: + project = e5App().getObject("Project") + if nam == project.ppath and not project.saveAllScripts(): + return + dlg = HgRevisionsSelectionDialog(self.tagsList, self.branchesList) + if dlg.exec_() == QDialog.Accepted: + revisions = dlg.getRevisions() + self.diff = HgDiffDialog(self) + self.diff.show() + self.diff.start(name, revisions) + + def hgLogLimited(self, name): + """ + Public method used to view the (limited) log of a file/directory from the + Mercurial repository. + + @param name file/directory name to show the log of (string) + """ + noEntries, ok = QInputDialog.getInteger(\ + None, + self.trUtf8("Mercurial Log"), + self.trUtf8("Select number of entries to show."), + self.getPlugin().getPreferences("LogLimit"), 1, 999999, 1) + if ok: + self.log = HgLogDialog(self) + self.log.show() + self.log.start(name, noEntries) + + def hgLogBrowser(self, path): + """ + Public method used to browse the log of a file/directory from the + Mercurial repository. + + @param path file/directory name to show the log of (string) + """ + self.logBrowser = HgLogBrowserDialog(self) + self.logBrowser.show() + self.logBrowser.start(path) + + def hgIncoming(self, name): + """ + Public method used to view the log of incoming changes from the + Mercurial repository. + + @param name file/directory name to show the log of (string) + """ + self.log = HgLogDialog(self, mode = "incoming") + self.log.show() + self.log.start(name) + + def hgOutgoing(self, name): + """ + Public method used to view the log of outgoing changes from the + Mercurial repository. + + @param name file/directory name to show the log of (string) + """ + self.log = HgLogDialog(self, mode = "outgoing") + self.log.show() + self.log.start(name) + + def hgPull(self, name): + """ + Public method used to pull changes from a remote Mercurial repository. + + @param name directory name of the project to be pulled to (string) + """ + args = [] + args.append('pull') + self.addArguments(args, self.options['global']) + args.append('-v') + + # find the root of the repo + repodir = self.splitPath(name)[0] + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + dia = HgDialog(self.trUtf8('Pulling from a remote Mercurial repository')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + res = dia.hasAddOrDelete() + self.checkVCSStatus() + + def hgPush(self, name): + """ + Public method used to push changes to a remote Mercurial repository. + + @param name directory name of the project to be pushed from (string) + """ + args = [] + args.append('push') + self.addArguments(args, self.options['global']) + args.append('-v') + + # find the root of the repo + repodir = self.splitPath(name)[0] + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + dia = HgDialog(self.trUtf8('Pushing to a remote Mercurial repository')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + res = dia.hasAddOrDelete() + self.checkVCSStatus() + + def hgInfo(self, ppath, mode = "heads"): + """ + Public method to show information about the heads of the repository. + + @param ppath local path to get the repository infos (string) + @keyparam mode mode of the operation (string, one of heads, parents, tip) + """ + if mode not in ("heads", "parents", "tip"): + mode = "heads" + + info = [] + + # find the root of the repo + repodir = self.splitPath(ppath)[0] + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + process = QProcess() + args = [] + args.append(mode) + args.append('--template') + args.append('{rev}:{node|short}@@@{tags}@@@{author|xmlescape}@@@' + '{date|isodate}@@@{branches}@@@{parents}\n') + + process.setWorkingDirectory(repodir) + process.start('hg', args) + procStarted = process.waitForStarted() + if procStarted: + finished = process.waitForFinished(30000) + if finished and process.exitCode() == 0: + output = str(process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), 'replace') + index = 0 + for line in output.splitlines(): + index += 1 + changeset, tags, author, date, branches, parents = line.split("@@@") + cdate, ctime = date.split()[:2] + info.append("""<p><table>""") + if mode == "heads": + info.append(QApplication.translate("mercurial", + """<tr><td><b>Head #{0}</b></td><td></td></tr>\n""" + .format(index, changeset))) + elif mode == "parents": + info.append(QApplication.translate("mercurial", + """<tr><td><b>Parent #{0}</b></td><td></td></tr>\n""" + .format(index, changeset))) + elif mode == "tip": + info.append(QApplication.translate("mercurial", + """<tr><td><b>Tip</b></td><td></td></tr>\n""")) + info.append(QApplication.translate("mercurial", + """<tr><td><b>Changeset</b></td><td>{0}</td></tr>""")\ + .format(changeset)) + if tags: + info.append(QApplication.translate("mercurial", + """<tr><td><b>Tags</b></td><td>{0}</td></tr>""")\ + .format('<br/>'.join(tags.split()))) + if branches: + info.append(QApplication.translate("mercurial", + """<tr><td><b>Branches</b></td><td>{0}</td></tr>""")\ + .format('<br/>'.join(branches.split()))) + if parents: + info.append(QApplication.translate("mercurial", + """<tr><td><b>Parents</b></td><td>{0}</td></tr>""")\ + .format('<br/>'.join(parents.split()))) + info.append(QApplication.translate("mercurial", + """<tr><td><b>Last author</b></td><td>{0}</td></tr>\n""" + """<tr><td><b>Committed date</b></td><td>{1}</td></tr>\n""" + """<tr><td><b>Committed time</b></td><td>{2}</td></tr>\n""" + """</table></p>""")\ + .format(author, cdate, ctime)) + + dlg = VcsRepositoryInfoDialog(None, "\n".join(info)) + dlg.exec_() + + + def hgResolve(self, name): + """ + Public method used to resolve conflicts of a file/directory. + + @param name file/directory name to be resolved (string) + """ + args = [] + args.append('resolve') + self.addArguments(args, self.options['global']) + args.append("--mark") + + if isinstance(name, list): + dname, fnames = self.splitPathList(name) + self.addArguments(args, name) + else: + dname, fname = self.splitPath(name) + args.append(name) + + # find the root of the repo + repodir = dname + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return False + + dia = HgDialog( + self.trUtf8('Resolving files/directories')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + self.checkVCSStatus() + + def hgBranch(self, name): + """ + Public method used to set the tag in the Mercurial repository. + + @param name file/directory name to be tagged (string) + """ + dname, fname = self.splitPath(name) + + # find the root of the repo + repodir = str(dname) + while not os.path.isdir(os.path.join(repodir, self.adminDir)): + repodir = os.path.dirname(repodir) + if repodir == os.sep: + return + + name, ok = QInputDialog.getItem( + None, + self.trUtf8("Create Branch"), + self.trUtf8("Enter branch name"), + self.branchesList, + 0, True) + if ok and name: + args = [] + args.append('branch') + args.append(name) + + dia = HgDialog(self.trUtf8('Creating branch in the Mercurial repository')) + res = dia.startProcess(args, repodir) + if res: + dia.exec_() + + ############################################################################ + ## Methods to get the helper objects are below. + ############################################################################ + + 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 + """ + return HgProjectBrowserHelper(self, browser, project, isTranslationsBrowser) + + 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 + """ + helper = self.__plugin.getProjectHelper() + helper.setObjects(self, project) + return helper + + ############################################################################ + ## Status Monitor Thread methods + ############################################################################ + + def _createStatusMonitorThread(self, interval, project): + """ + Protected method to create an instance of the VCS status monitor thread. + + @param project reference to the project object + @param interval check interval for the monitor thread in seconds (integer) + @return reference to the monitor thread (QThread) + """ + return HgStatusMonitorThread(interval, project.ppath, self)