eric7/VCS/ProjectHelper.py

branch
eric7
changeset 8312
800c432b34c8
parent 8263
af06dde4f4ef
child 8318
962bce857696
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/VCS/ProjectHelper.py	Sat May 15 18:45:04 2021 +0200
@@ -0,0 +1,668 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2005 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the base class of the VCS project helper.
+"""
+
+import os
+import shutil
+import copy
+
+from PyQt5.QtCore import pyqtSlot, QDir, QFileInfo, QObject, QCoreApplication
+from PyQt5.QtWidgets import QDialog, QInputDialog, QToolBar
+
+from E5Gui.E5Action import E5Action
+from E5Gui import E5MessageBox
+from E5Gui.E5Application import e5App
+
+import Preferences
+import UI.PixmapCache
+import UI.Config
+
+
+class VcsProjectHelper(QObject):
+    """
+    Class implementing the base class of the VCS project helper.
+    """
+    def __init__(self, vcsObject, projectObject, parent=None, name=None):
+        """
+        Constructor
+        
+        @param vcsObject reference to the vcs object
+        @param projectObject reference to the project object
+        @param parent parent widget (QWidget)
+        @param name name of this object (string)
+        """
+        super().__init__(parent)
+        if name:
+            self.setObjectName(name)
+        
+        self.vcs = vcsObject
+        self.project = projectObject
+        
+        self.actions = []
+        
+        self.vcsAddAct = None
+        
+        self.initActions()
+        
+    def setObjects(self, vcsObject, projectObject):
+        """
+        Public method to set references to the vcs and project objects.
+        
+        @param vcsObject reference to the vcs object
+        @param projectObject reference to the project object
+        """
+        self.vcs = vcsObject
+        self.project = projectObject
+    
+    def initActions(self):
+        """
+        Public method to generate the action objects.
+        """
+        self.vcsNewAct = E5Action(
+            QCoreApplication.translate(
+                "VcsProjectHelper", 'New from repository'),
+            UI.PixmapCache.getIcon("vcsCheckout"),
+            QCoreApplication.translate(
+                "VcsProjectHelper", '&New from repository...'),
+            0, 0, self, 'vcs_new')
+        self.vcsNewAct.setStatusTip(QCoreApplication.translate(
+            "VcsProjectHelper",
+            'Create a new project from the VCS repository'
+        ))
+        self.vcsNewAct.setWhatsThis(QCoreApplication.translate(
+            "VcsProjectHelper",
+            """<b>New from repository</b>"""
+            """<p>This creates a new local project from the VCS"""
+            """ repository.</p>"""
+        ))
+        self.vcsNewAct.triggered.connect(self._vcsCheckout)
+        self.actions.append(self.vcsNewAct)
+        
+        self.vcsExportAct = E5Action(
+            QCoreApplication.translate(
+                "VcsProjectHelper", 'Export from repository'),
+            UI.PixmapCache.getIcon("vcsExport"),
+            QCoreApplication.translate(
+                "VcsProjectHelper", '&Export from repository...'),
+            0, 0, self, 'vcs_export')
+        self.vcsExportAct.setStatusTip(QCoreApplication.translate(
+            "VcsProjectHelper",
+            'Export a project from the repository'
+        ))
+        self.vcsExportAct.setWhatsThis(QCoreApplication.translate(
+            "VcsProjectHelper",
+            """<b>Export from repository</b>"""
+            """<p>This exports a project from the repository.</p>"""
+        ))
+        self.vcsExportAct.triggered.connect(self._vcsExport)
+        self.actions.append(self.vcsExportAct)
+        
+        self.vcsAddAct = E5Action(
+            QCoreApplication.translate(
+                "VcsProjectHelper", 'Add to repository'),
+            UI.PixmapCache.getIcon("vcsCommit"),
+            QCoreApplication.translate(
+                "VcsProjectHelper", '&Add to repository...'),
+            0, 0, self, 'vcs_add')
+        self.vcsAddAct.setStatusTip(QCoreApplication.translate(
+            "VcsProjectHelper",
+            'Add the local project to the VCS repository'
+        ))
+        self.vcsAddAct.setWhatsThis(QCoreApplication.translate(
+            "VcsProjectHelper",
+            """<b>Add to repository</b>"""
+            """<p>This adds (imports) the local project to the VCS"""
+            """ repository.</p>"""
+        ))
+        self.vcsAddAct.triggered.connect(self._vcsImport)
+        self.actions.append(self.vcsAddAct)
+    
+    def initMenu(self, menu):
+        """
+        Public method to generate the VCS menu.
+        
+        @param menu reference to the menu to be populated (QMenu)
+        """
+        menu.clear()
+        
+        menu.addAction(self.vcsNewAct)
+        menu.addAction(self.vcsExportAct)
+        menu.addSeparator()
+        menu.addAction(self.vcsAddAct)
+        menu.addSeparator()
+
+    def initToolbar(self, ui, toolbarManager):
+        """
+        Public slot to initialize the VCS toolbar.
+        
+        @param ui reference to the main window (UserInterface)
+        @param toolbarManager reference to a toolbar manager object
+            (E5ToolBarManager)
+        @return the toolbar generated (QToolBar)
+        """
+        return None     # __IGNORE_WARNING_M831__
+    
+    def initBasicToolbar(self, ui, toolbarManager):
+        """
+        Public slot to initialize the basic VCS toolbar.
+        
+        @param ui reference to the main window (UserInterface)
+        @param toolbarManager reference to a toolbar manager object
+            (E5ToolBarManager)
+        @return the toolbar generated (QToolBar)
+        """
+        tb = QToolBar(QCoreApplication.translate("VcsProjectHelper", "VCS"),
+                      ui)
+        tb.setIconSize(UI.Config.ToolBarIconSize)
+        tb.setObjectName("VersionControlToolbar")
+        tb.setToolTip(QCoreApplication.translate("VcsProjectHelper", 'VCS'))
+        
+        tb.addAction(self.vcsNewAct)
+        tb.addAction(self.vcsExportAct)
+        tb.addSeparator()
+        tb.addAction(self.vcsAddAct)
+        
+        toolbarManager.addToolBar(tb, tb.windowTitle())
+        
+        return tb
+    
+    def showMenu(self):
+        """
+        Public slot called before the vcs menu is shown.
+        """
+        if self.vcsAddAct:
+            self.vcsAddAct.setEnabled(self.project and self.project.isOpen())
+    
+    @pyqtSlot()
+    def _vcsCheckout(self, export=False):
+        """
+        Protected slot used to create a local project from the repository.
+        
+        @param export flag indicating whether an export or a checkout
+                should be performed
+        """
+        if not self.project or not self.project.checkDirty():
+            return
+        
+        vcsSystemsDict = e5App().getObject(
+            "PluginManager").getPluginDisplayStrings("version_control")
+        if not vcsSystemsDict:
+            # no version control system found
+            return
+        
+        vcsSystemsDisplay = []
+        keys = sorted(vcsSystemsDict.keys())
+        for key in keys:
+            vcsSystemsDisplay.append(vcsSystemsDict[key])
+        vcsSelected, ok = QInputDialog.getItem(
+            None,
+            QCoreApplication.translate("VcsProjectHelper", "New Project"),
+            QCoreApplication.translate(
+                "VcsProjectHelper",
+                "Select version control system for the project"),
+            vcsSystemsDisplay,
+            0, False)
+        if not ok:
+            return
+        
+        selectedVcsSystem = None
+        for vcsSystem, vcsSystemDisplay in list(vcsSystemsDict.items()):
+            if vcsSystemDisplay == vcsSelected:
+                selectedVcsSystem = vcsSystem
+                break
+        
+        if not self.project.closeProject():
+            return
+        
+        vcs = self.project.initVCS(selectedVcsSystem)
+        if vcs is not None:
+            vcsdlg = vcs.vcsNewProjectOptionsDialog()
+            if vcsdlg.exec() == QDialog.DialogCode.Accepted:
+                projectdir, vcsDataDict = vcsdlg.getData()
+                # edit VCS command options
+                if vcs.vcsSupportCommandOptions():
+                    vcores = E5MessageBox.yesNo(
+                        self.parent(),
+                        QCoreApplication.translate(
+                            "VcsProjectHelper", "New Project"),
+                        QCoreApplication.translate(
+                            "VcsProjectHelper",
+                            """Would you like to edit the VCS command"""
+                            """ options?"""))
+                else:
+                    vcores = False
+                if vcores:
+                    from .CommandOptionsDialog import VcsCommandOptionsDialog
+                    codlg = VcsCommandOptionsDialog(vcs)
+                    if codlg.exec() == QDialog.DialogCode.Accepted:
+                        vcs.vcsSetOptions(codlg.getOptions())
+                
+                # create the project directory if it doesn't exist already
+                if not os.path.isdir(projectdir):
+                    try:
+                        os.makedirs(projectdir)
+                    except OSError:
+                        E5MessageBox.critical(
+                            self.parent(),
+                            QCoreApplication.translate(
+                                "VcsProjectHelper",
+                                "Create project directory"),
+                            QCoreApplication.translate(
+                                "VcsProjectHelper",
+                                "<p>The project directory <b>{0}</b> could not"
+                                " be created.</p>").format(projectdir))
+                        self.project.resetVCS()
+                        return
+                
+                # create the project from the VCS
+                vcs.vcsSetDataFromDict(vcsDataDict)
+                if export:
+                    ok = vcs.vcsExport(vcsDataDict, projectdir)
+                else:
+                    ok = vcs.vcsCheckout(vcsDataDict, projectdir, False)
+                if ok:
+                    projectdir = os.path.normpath(projectdir)
+                    # look for JSON style project file first
+                    filters = ["*.epj"]
+                    d = QDir(projectdir)
+                    plist = d.entryInfoList(filters)
+                    if not plist:
+                        # look for XML style project file second
+                        filters = ["*.e4p"]
+                        d = QDir(projectdir)
+                        plist = d.entryInfoList(filters)
+                    if plist:
+                        if len(plist) == 1:
+                            self.project.openProject(
+                                plist[0].absoluteFilePath())
+                        else:
+                            pfilenamelist = d.entryList(filters)
+                            pfilename, ok = QInputDialog.getItem(
+                                None,
+                                QCoreApplication.translate(
+                                    "VcsProjectHelper",
+                                    "New project from repository"),
+                                QCoreApplication.translate(
+                                    "VcsProjectHelper",
+                                    "Select a project file to open."),
+                                pfilenamelist, 0, False)
+                            if ok:
+                                self.project.openProject(
+                                    QFileInfo(d, pfilename).absoluteFilePath())
+                        if export:
+                            self.project.pdata["VCS"] = 'None'
+                            self.project.vcs = self.project.initVCS()
+                            self.project.setDirty(True)
+                            self.project.saveProject()
+                    else:
+                        res = E5MessageBox.yesNo(
+                            self.parent(),
+                            QCoreApplication.translate(
+                                "VcsProjectHelper",
+                                "New project from repository"),
+                            QCoreApplication.translate(
+                                "VcsProjectHelper",
+                                "The project retrieved from the repository"
+                                " does not contain an eric project file"
+                                " (*.epj). Create it?"),
+                            yesDefault=True)
+                        if res:
+                            self.project.ppath = projectdir
+                            self.project.opened = True
+                            
+                            from Project.PropertiesDialog import (
+                                PropertiesDialog
+                            )
+                            dlg = PropertiesDialog(self.project, False)
+                            if dlg.exec() == QDialog.DialogCode.Accepted:
+                                dlg.storeData()
+                                self.project.initFileTypes()
+                                self.project.pdata["VCS"] = selectedVcsSystem
+                                self.project.setDirty(True)
+                                if self.project.pdata["MAINSCRIPT"]:
+                                    ms = os.path.join(
+                                        self.project.ppath,
+                                        self.project.pdata["MAINSCRIPT"])
+                                    if os.path.exists(ms):
+                                        self.project.appendFile(ms)
+                                else:
+                                    ms = ""
+                                self.project.newProjectAddFiles(ms)
+                                self.project.createProjectManagementDir()
+                                self.project.saveProject()
+                                self.project.openProject(self.project.pfile)
+                                if not export:
+                                    res = E5MessageBox.yesNo(
+                                        self.parent(),
+                                        QCoreApplication.translate(
+                                            "VcsProjectHelper",
+                                            "New project from repository"),
+                                        QCoreApplication.translate(
+                                            "VcsProjectHelper",
+                                            "Shall the project file be added"
+                                            " to the repository?"),
+                                        yesDefault=True)
+                                    if res:
+                                        self.project.vcs.vcsAdd(
+                                            self.project.pfile)
+                else:
+                    E5MessageBox.critical(
+                        self.parent(),
+                        QCoreApplication.translate(
+                            "VcsProjectHelper", "New project from repository"),
+                        QCoreApplication.translate(
+                            "VcsProjectHelper",
+                            """The project could not be retrieved from"""
+                            """ the repository."""))
+                    self.project.resetVCS()
+
+    def _vcsExport(self):
+        """
+        Protected slot used to export a project from the repository.
+        """
+        self._vcsCheckout(True)
+
+    def _vcsImport(self):
+        """
+        Protected slot used to import the local project into the repository.
+        
+        <b>NOTE</b>:
+            This does not necessarily make the local project a vcs controlled
+            project. You may have to checkout the project from the repository
+            in order to accomplish that.
+        """
+        def revertChanges():
+            """
+            Local function to revert the changes made to the project object.
+            """
+            self.project.pdata["VCS"] = pdata_vcs
+            self.project.pdata["VCSOPTIONS"] = copy.deepcopy(pdata_vcsoptions)
+            self.project.pdata["VCSOTHERDATA"] = copy.deepcopy(pdata_vcsother)
+            self.project.vcs = vcs
+            self.project.vcsProjectHelper = vcsHelper
+            self.project.vcsBasicHelper = vcs is None
+            self.initMenu(self.project.vcsMenu)
+            self.project.setDirty(True)
+            self.project.saveProject()
+        
+        pdata_vcs = self.project.pdata["VCS"]
+        pdata_vcsoptions = copy.deepcopy(self.project.pdata["VCSOPTIONS"])
+        pdata_vcsother = copy.deepcopy(self.project.pdata["VCSOTHERDATA"])
+        vcs = self.project.vcs
+        vcsHelper = self.project.vcsProjectHelper
+        vcsSystemsDict = e5App().getObject(
+            "PluginManager").getPluginDisplayStrings("version_control")
+        if not vcsSystemsDict:
+            # no version control system found
+            return
+        
+        vcsSystemsDisplay = []
+        keys = sorted(list(vcsSystemsDict.keys()))
+        for key in keys:
+            vcsSystemsDisplay.append(vcsSystemsDict[key])
+        vcsSelected, ok = QInputDialog.getItem(
+            None,
+            QCoreApplication.translate("VcsProjectHelper", "Import Project"),
+            QCoreApplication.translate(
+                "VcsProjectHelper",
+                "Select version control system for the project"),
+            vcsSystemsDisplay,
+            0, False)
+        if not ok:
+            return
+        
+        selectedVcsSystem = None
+        for vcsSystem, vcsSystemDisplay in list(vcsSystemsDict.items()):
+            if vcsSystemDisplay == vcsSelected:
+                selectedVcsSystem = vcsSystem
+                break
+        
+        if selectedVcsSystem is not None:
+            self.project.pdata["VCS"] = selectedVcsSystem
+            self.project.vcs = self.project.initVCS(selectedVcsSystem)
+            if self.project.vcs is not None:
+                vcsdlg = self.project.vcs.vcsOptionsDialog(
+                    self.project, self.project.name, 1)
+                if vcsdlg.exec() == QDialog.DialogCode.Accepted:
+                    vcsDataDict = vcsdlg.getData()
+                    # edit VCS command options
+                    if self.project.vcs.vcsSupportCommandOptions():
+                        vcores = E5MessageBox.yesNo(
+                            self.parent(),
+                            QCoreApplication.translate(
+                                "VcsProjectHelper", "Import Project"),
+                            QCoreApplication.translate(
+                                "VcsProjectHelper",
+                                """Would you like to edit the VCS command"""
+                                """ options?"""))
+                    else:
+                        vcores = False
+                    if vcores:
+                        from .CommandOptionsDialog import (
+                            VcsCommandOptionsDialog
+                        )
+                        codlg = VcsCommandOptionsDialog(self.project.vcs)
+                        if codlg.exec() == QDialog.DialogCode.Accepted:
+                            self.project.vcs.vcsSetOptions(codlg.getOptions())
+                    self.project.setDirty(True)
+                    self.project.vcs.vcsSetDataFromDict(vcsDataDict)
+                    self.project.saveProject()
+                    isVcsControlled = self.project.vcs.vcsImport(
+                        vcsDataDict, self.project.ppath)[0]
+                    if isVcsControlled:
+                        # reopen the project
+                        self.project.openProject(self.project.pfile)
+                    else:
+                        # revert the changes to the local project
+                        # because the project dir is not a VCS directory
+                        revertChanges()
+                else:
+                    # revert the changes because user cancelled
+                    revertChanges()
+
+    def _vcsUpdate(self):
+        """
+        Protected slot used to update the local project from the repository.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        shouldReopen = self.vcs.vcsUpdate(self.project.ppath)
+        if shouldReopen:
+            res = E5MessageBox.yesNo(
+                self.parent(),
+                QCoreApplication.translate("VcsProjectHelper", "Update"),
+                QCoreApplication.translate(
+                    "VcsProjectHelper",
+                    """The project should be reread. Do this now?"""),
+                yesDefault=True)
+            if res:
+                self.project.reopenProject()
+        
+    def _vcsCommit(self):
+        """
+        Protected slot used to commit changes to the local project to the
+        repository.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        if Preferences.getVCS("AutoSaveProject"):
+            self.project.saveProject()
+        if Preferences.getVCS("AutoSaveFiles"):
+            self.project.saveAllScripts()
+        self.vcs.vcsCommit(self.project.ppath, '')
+        
+    def _vcsRemove(self):
+        """
+        Protected slot used to remove the local project from the repository.
+        
+        Depending on the parameters set in the vcs object the project
+        may be removed from the local disk as well.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        res = E5MessageBox.yesNo(
+            self.parent(),
+            QCoreApplication.translate(
+                "VcsProjectHelper",
+                "Remove project from repository"),
+            QCoreApplication.translate(
+                "VcsProjectHelper",
+                "Dou you really want to remove this project from"
+                " the repository (and disk)?"))
+        if res:
+            self.vcs.vcsRemove(self.project.ppath, True)
+            self._vcsCommit()
+            if not os.path.exists(self.project.pfile):
+                ppath = self.project.ppath
+                self.setDirty(False)
+                self.project.closeProject()
+                shutil.rmtree(ppath, True)
+        
+    def _vcsCommandOptions(self):
+        """
+        Protected slot to edit the VCS command options.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        if self.vcs.vcsSupportCommandOptions():
+            from .CommandOptionsDialog import VcsCommandOptionsDialog
+            codlg = VcsCommandOptionsDialog(self.vcs)
+            if codlg.exec() == QDialog.DialogCode.Accepted:
+                self.vcs.vcsSetOptions(codlg.getOptions())
+                self.project.setDirty(True)
+        
+    def _vcsLog(self):
+        """
+        Protected slot used to show the log of the local project.
+        """
+        # kept for backward compatibility for plug-ins
+        self._vcsLogBrowser()
+        
+    def _vcsLogBrowser(self):
+        """
+        Protected slot used to show the log of the local project with a
+        log browser dialog.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        self.vcs.vcsLogBrowser(self.project.ppath)
+        
+    def _vcsDiff(self):
+        """
+        Protected slot used to show the difference of the local project to
+        the repository.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        self.vcs.vcsDiff(self.project.ppath)
+        
+    def _vcsStatus(self):
+        """
+        Protected slot used to show the status of the local project.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        self.vcs.vcsStatus(self.project.ppath)
+        
+    def _vcsTag(self):
+        """
+        Protected slot used to tag the local project in the repository.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        self.vcs.vcsTag(self.project.ppath)
+        
+    def _vcsRevert(self):
+        """
+        Protected slot used to revert changes made to the local project.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        self.vcs.vcsRevert(self.project.ppath)
+        
+    def _vcsSwitch(self):
+        """
+        Protected slot used to switch the local project to another tag/branch.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        shouldReopen = self.vcs.vcsSwitch(self.project.ppath)
+        if shouldReopen:
+            res = E5MessageBox.yesNo(
+                self.parent(),
+                QCoreApplication.translate("VcsProjectHelper", "Switch"),
+                QCoreApplication.translate(
+                    "VcsProjectHelper",
+                    """The project should be reread. Do this now?"""),
+                yesDefault=True)
+            if res:
+                self.project.reopenProject()
+        
+    def _vcsMerge(self):
+        """
+        Protected slot used to merge changes of a tag/revision into the local
+        project.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        self.vcs.vcsMerge(self.project.ppath)
+        
+    def _vcsCleanup(self):
+        """
+        Protected slot used to cleanup the local project.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        self.vcs.vcsCleanup(self.project.ppath)
+        
+    def _vcsCommand(self):
+        """
+        Protected slot used to execute an arbitrary vcs command.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        self.vcs.vcsCommandLine(self.project.ppath)
+
+    def _vcsInfoDisplay(self):
+        """
+        Protected slot called to show some vcs information.
+        """
+        if self.vcs is None:
+            # just in case
+            return
+        
+        from .RepositoryInfoDialog import VcsRepositoryInfoDialog
+        info = self.vcs.vcsRepositoryInfos(self.project.ppath)
+        dlg = VcsRepositoryInfoDialog(None, info)
+        dlg.exec()

eric ide

mercurial