Started implementing the Mercurial lfconvert functionality.

Thu, 27 Feb 2014 19:48:55 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 27 Feb 2014 19:48:55 +0100
changeset 3310
a2032ed66aec
parent 3309
b5f782f7d43b
child 3311
b4775920f5b8

Started implementing the Mercurial lfconvert functionality.

Plugins/VcsPlugins/vcsMercurial/HgDialog.py file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/LfConvertDataDialog.py file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/LfConvertDataDialog.ui file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/ProjectHelper.py file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/__init__.py file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/largefiles.py file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/ProjectHelper.py file | annotate | diff | comparison | revisions
Plugins/VcsPlugins/vcsMercurial/hg.py file | annotate | diff | comparison | revisions
eric5.e4p file | annotate | diff | comparison | revisions
--- a/Plugins/VcsPlugins/vcsMercurial/HgDialog.py	Thu Feb 27 18:59:52 2014 +0100
+++ b/Plugins/VcsPlugins/vcsMercurial/HgDialog.py	Thu Feb 27 19:48:55 2014 +0100
@@ -27,12 +27,14 @@
     shows the output of the process. The dialog is modal,
     which causes a synchronized execution of the process.
     """
-    def __init__(self, text, hg=None, parent=None):
+    def __init__(self, text, hg=None, useClient=True, parent=None):
         """
         Constructor
         
         @param text text to be shown by the label (string)
         @param hg reference to the Mercurial interface object (Hg)
+        @param useClient flag indicating to use the command server client
+            if possible (boolean)
         @param parent parent widget (QWidget)
         """
         super().__init__(parent)
@@ -44,7 +46,10 @@
         self.proc = None
         self.username = ''
         self.password = ''
-        self.__hgClient = hg.getClient()
+        if useClient:
+            self.__hgClient = hg.getClient()
+        else:
+            self.__hgClient = None
         self.vcs = hg
         
         self.outputGroup.setTitle(text)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/LfConvertDataDialog.py	Thu Feb 27 19:48:55 2014 +0100
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+"""
+Module implementing a dialog to enter the data for the repo conversion.
+"""
+
+import os
+
+from PyQt4.QtCore import pyqtSlot
+from PyQt4.QtGui import QDialog, QDialogButtonBox
+
+from E5Gui import E5FileDialog
+from E5Gui.E5Completers import E5DirCompleter
+
+from .Ui_LfConvertDataDialog import Ui_LfConvertDataDialog
+
+from . import getDefaults
+
+import Utilities
+import UI.PixmapCache
+
+
+class LfConvertDataDialog(QDialog, Ui_LfConvertDataDialog):
+    """
+    Class implementing a dialog to enter the data for the repo conversion.
+    """
+    def __init__(self, currentPath, mode, parent=None):
+        """
+        Constructor
+        
+        @param currentPath directory name of the current project (string)
+        @param mode dialog mode (string, one of 'largefiles' or 'normal')
+        @param parent reference to the parent widget (QWidget)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.newProjectButton.setIcon(UI.PixmapCache.getIcon("open.png"))
+        
+        self.__newProjectCompleter = E5DirCompleter(self.newProjectEdit)
+        
+        self.__defaults = getDefaults()
+        self.__currentPath = Utilities.toNativeSeparators(currentPath)
+        
+        self.currentProjectLabel.setPath(currentPath)
+        self.newProjectEdit.setText(os.path.dirname(currentPath))
+        
+        self.lfFileSizeSpinBox.setValue(self.__defaults["minsize"])
+        self.lfFilePatternsEdit.setText(" ".join(self.__defaults["pattern"]))
+        
+        if mode == 'normal':
+            self.lfFileSizeSpinBox.setEnabled(False)
+            self.lfFilePatternsEdit.setEnabled(False)
+        
+        self.resize(self.width(), self.minimumSizeHint().height())
+    
+    @pyqtSlot(str)
+    def on_newProjectEdit_textChanged(self, txt):
+        """
+        Private slot to handle editing of the new project directory.
+        
+        @param txt new project directory name (string)
+        """
+        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(
+            txt and Utilities.toNativeSeparators(txt) != os.path.dirname(
+                self.__currentPath))
+    
+    @pyqtSlot()
+    def on_newProjectButton_clicked(self):
+        """
+        Private slot to select the new project directory name via a directory
+        selection dialog.
+        """
+        directory = Utilities.fromNativeSeparators(self.newProjectEdit.text())
+        directory = E5FileDialog.getExistingDirectory(
+            self,
+            self.tr("New Project Directory"),
+            directory,
+            E5FileDialog.Options(E5FileDialog.ShowDirsOnly))
+        if directory:
+            self.newProjectEdit.setText(
+                Utilities.toNativeSeparators(directory))
+    
+    def getData(self):
+        """
+        Public method to retrieve the entered data.
+        
+        @return tuple containing the new project directory name (string),
+            minimum file size (integer) and file patterns (list of string)
+        """
+        patterns = self.lfFilePatternsEdit.text().split()
+        if set(patterns) == set(self.__defaults["pattern"]):
+            patterns = []
+        
+        return (
+            Utilities.toNativeSeparators(self.newProjectEdit.text()),
+            self.lfFileSizeSpinBox.value(),
+            patterns,
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/LfConvertDataDialog.ui	Thu Feb 27 19:48:55 2014 +0100
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LfConvertDataDialog</class>
+ <widget class="QDialog" name="LfConvertDataDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>143</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Convert Repository Format</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0" colspan="4">
+    <widget class="E5SqueezeLabelPath" name="currentProjectLabel"/>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>New project directory:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1" colspan="2">
+    <widget class="QLineEdit" name="newProjectEdit">
+     <property name="toolTip">
+      <string>Enter the directory name of the new project directory</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="3">
+    <widget class="QToolButton" name="newProjectButton">
+     <property name="toolTip">
+      <string>Press to select the new project directory name via a directory selection dialog</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_4">
+     <property name="text">
+      <string>Minimum file size:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QSpinBox" name="lfFileSizeSpinBox">
+     <property name="toolTip">
+      <string>Enter the minimum file size in MB for files to be treated as Large Files</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+     </property>
+     <property name="suffix">
+      <string> MB</string>
+     </property>
+     <property name="minimum">
+      <number>1</number>
+     </property>
+     <property name="value">
+      <number>10</number>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="2" colspan="2">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>297</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="3" column="0">
+    <widget class="QLabel" name="label_5">
+     <property name="text">
+      <string>Patterns:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1" colspan="3">
+    <widget class="QLineEdit" name="lfFilePatternsEdit">
+     <property name="toolTip">
+      <string>Enter file patterns (space separated) for files to be treated as Large Files</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0" colspan="4">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5SqueezeLabelPath</class>
+   <extends>QLabel</extends>
+   <header>E5Gui/E5SqueezeLabels.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>newProjectEdit</tabstop>
+  <tabstop>newProjectButton</tabstop>
+  <tabstop>lfFileSizeSpinBox</tabstop>
+  <tabstop>lfFilePatternsEdit</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>LfConvertDataDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>LfConvertDataDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/ProjectHelper.py	Thu Feb 27 19:48:55 2014 +0100
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the shelve extension project helper.
+"""
+
+from PyQt4.QtGui import QMenu
+
+from E5Gui.E5Action import E5Action
+
+from ..HgExtensionProjectHelper import HgExtensionProjectHelper
+
+
+class LargefilesProjectHelper(HgExtensionProjectHelper):
+    """
+    Class implementing the queues extension project helper.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        super().__init__()
+    
+    def initActions(self):
+        """
+        Public method to generate the action objects.
+        """
+        self.hgConvertToLargefilesAct = E5Action(
+            self.tr('Convert repository to largefiles'),
+            self.tr('Convert repository to largefiles...'),
+            0, 0, self, 'mercurial_convert_to_largefiles')
+        self.hgConvertToLargefilesAct.setStatusTip(self.tr(
+            'Convert the repository of the project to a largefiles repository.'
+        ))
+        self.hgConvertToLargefilesAct.setWhatsThis(self.tr(
+            """<b>Convert repository to largefiles</b>"""
+            """<p>This converts the repository of the project to a"""
+            """ largefiles repository. A new project  is created. The"""
+            """ current one is kept as a backup.</p>"""
+        ))
+        self.hgConvertToLargefilesAct.triggered[()].connect(
+            lambda: self.__hgLfconvert("largefiles"))
+        self.actions.append(self.hgConvertToLargefilesAct)
+        
+        self.hgConvertToNormalAct = E5Action(
+            self.tr('Convert repository to normal'),
+            self.tr('Convert repository to normal...'),
+            0, 0, self, 'mercurial_convert_to_normal')
+        self.hgConvertToNormalAct.setStatusTip(self.tr(
+            'Convert the repository of the project to a normal repository.'
+        ))
+        self.hgConvertToNormalAct.setWhatsThis(self.tr(
+            """<b>Convert repository to normal</b>"""
+            """<p>This converts the repository of the project to a"""
+            """ normal repository. A new project is created. The current"""
+            """ one is kept as a backup.</p>"""
+        ))
+        self.hgConvertToNormalAct.triggered[()].connect(
+            lambda: self.__hgLfconvert("normal"))
+        self.actions.append(self.hgConvertToNormalAct)
+    
+    def initMenu(self, mainMenu):
+        """
+        Public method to generate the extension menu.
+        
+        @param mainMenu reference to the main menu (QMenu)
+        @return populated menu (QMenu)
+        """
+        menu = QMenu(self.menuTitle(), mainMenu)
+        menu.setTearOffEnabled(True)
+        
+        menu.addAction(self.hgConvertToLargefilesAct)
+        menu.addAction(self.hgConvertToNormalAct)
+        
+        return menu
+    
+    def menuTitle(self):
+        """
+        Public method to get the menu title.
+        
+        @return title of the menu (string)
+        """
+        return self.tr("Large Files")
+    
+    def __hgLfconvert(self, direction):
+        """
+        Private slot to convert the repository format of the current project.
+        
+        @param direction direction of the conversion (string, one of
+            'largefiles' or 'normal')
+        """
+        assert direction in ["largefiles", "normal"]
+        
+        self.vcs.getExtensionObject("largefiles").hgLfconvert(
+            direction, self.project.getProjectFile())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/__init__.py	Thu Feb 27 19:48:55 2014 +0100
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the largefiles extension support interface.
+"""
+
+def getDefaults():
+    """
+    Function to get the default values of the extension.
+    
+    @return dictionary with default values and parameter as key (dict)
+    """
+    return {
+        'minsize': 10,      # minimum size in MB
+        'pattern': [],      # file name patterns
+    }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/largefiles.py	Thu Feb 27 19:48:55 2014 +0100
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the largefiles extension interface.
+"""
+
+import os
+import shutil
+
+from PyQt4.QtGui import QDialog
+
+from E5Gui.E5Application import e5App
+from E5Gui import E5MessageBox
+
+from ..HgExtension import HgExtension
+from ..HgDialog import HgDialog
+
+from . import getDefaults
+
+
+class Largefiles(HgExtension):
+    """
+    Class implementing the largefiles extension interface.
+    """
+    def __init__(self, vcs):
+        """
+        Constructor
+        
+        @param vcs reference to the Mercurial vcs object
+        """
+        super().__init__(vcs)
+    
+    def hgLfconvert(self, direction, projectFile):
+        """
+        Public slot to convert the repository format of the current project.
+        
+        @param direction direction of the conversion (string, one of
+            'largefiles' or 'normal')
+        @param projectFile file name of the current project file (string)
+        """
+        assert direction in ["largefiles", "normal"]
+        
+        projectDir = os.path.dirname(projectFile)
+        
+        # find the root of the repo
+        repodir = projectDir
+        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
+            repodir = os.path.dirname(repodir)
+            if os.path.splitdrive(repodir)[1] == os.sep:
+                return False
+        
+        from .LfConvertDataDialog import LfConvertDataDialog
+        dlg = LfConvertDataDialog(projectDir, direction)
+        if dlg.exec_() == QDialog.Accepted:
+            newName, minSize, patterns = dlg.getData()
+            newProjectFile = os.path.join(
+                newName, os.path.basename(projectFile))
+            
+            # step 1: convert the current project to new project
+            args = self.vcs.initCommand("lfconvert")
+            if direction == 'normal':
+                args.append('--to-normal')
+            else:
+                args.append("--size")
+                args.append(str(minSize))
+            args.append(projectDir)
+            args.append(newName)
+            if direction == 'largefiles' and patterns:
+                args.extend(patterns)
+            
+            dia = HgDialog(self.tr('Convert Project - Converting'), self.vcs)
+            res = dia.startProcess(args, repodir)
+            if res:
+                dia.exec_()
+                res = dia.normalExit() and os.path.isdir(
+                    os.path.join(newName, self.vcs.adminDir))
+            
+            # step 2: create working directory contents
+            if res:
+                args = self.vcs.initCommand("update")
+                if "-v" not in args and "--verbose" not in args:
+                    args.append("-v")
+                dia = HgDialog(self.tr('Convert Project - Extracting'),
+                               self.vcs, useClient=False)
+                res = dia.startProcess(args, newName)
+                if res:
+                    dia.exec_()
+                    res = dia.normalExit() and os.path.isfile(newProjectFile)
+            
+            # step 3: close current project and open new one
+            if res:
+                e5App().getObject("Project").openProject(newProjectFile)
+                
+                # step 3.1: copy old hgrc file
+                hgrc = os.path.join(repodir, self.vcs.adminDir, "hgrc")
+                if os.path.exists(hgrc):
+                    ok = E5MessageBox.yesNo(
+                        None,
+                        self.tr("Convert Project"),
+                        self.tr("""Shall the Mercurial repository"""
+                                """ configuration file be copied over?"""))
+                    if ok:
+                        shutil.copy(
+                            hgrc,
+                            os.path.join(newName, self.vcs.adminDir, "hgrc"))
+                # TODO: write patterns to hgrc
--- a/Plugins/VcsPlugins/vcsMercurial/ProjectHelper.py	Thu Feb 27 18:59:52 2014 +0100
+++ b/Plugins/VcsPlugins/vcsMercurial/ProjectHelper.py	Thu Feb 27 19:48:55 2014 +0100
@@ -46,6 +46,7 @@
         from .TransplantExtension.ProjectHelper import TransplantProjectHelper
         from .RebaseExtension.ProjectHelper import RebaseProjectHelper
         from .ShelveExtension.ProjectHelper import ShelveProjectHelper
+        from .LargefilesExtension.ProjectHelper import LargefilesProjectHelper
         self.__extensions = {
             "bookmarks": BookmarksProjectHelper(),
             "mq": QueuesProjectHelper(),
@@ -55,6 +56,7 @@
             "transplant": TransplantProjectHelper(),
             "rebase": RebaseProjectHelper(),
             "shelve": ShelveProjectHelper(),
+            "largefiles": LargefilesProjectHelper(),
         }
         
         self.__extensionMenuTitles = {}
--- a/Plugins/VcsPlugins/vcsMercurial/hg.py	Thu Feb 27 18:59:52 2014 +0100
+++ b/Plugins/VcsPlugins/vcsMercurial/hg.py	Thu Feb 27 19:48:55 2014 +0100
@@ -136,6 +136,7 @@
         from .TransplantExtension.transplant import Transplant
         from .RebaseExtension.rebase import Rebase
         from .ShelveExtension.shelve import Shelve
+        from .LargefilesExtension.largefiles import Largefiles
         self.__extensions = {
             "bookmarks": Bookmarks(self),
             "mq": Queues(self),
@@ -145,6 +146,7 @@
             "transplant": Transplant(self),
             "rebase": Rebase(self),
             "shelve": Shelve(self),
+            "largefiles": Largefiles(self)
         }
     
     def getPlugin(self):
@@ -3346,6 +3348,11 @@
                 self.version < (2, 8):
             # shelve extension was added as of Mercurial 2.8.0
             isActive = False
+        if isActive and \
+            extensionName == "largefiles" and \
+                self.version < (2, 0):
+            # largefiles extension was added as of Mercurial 2.0.0
+            isActive = False
         
         return isActive
     
--- a/eric5.e4p	Thu Feb 27 18:59:52 2014 +0100
+++ b/eric5.e4p	Thu Feb 27 19:48:55 2014 +0100
@@ -1121,6 +1121,10 @@
     <Source>Plugins/VcsPlugins/vcsMercurial/ShelveExtension/HgShelvesSelectionDialog.py</Source>
     <Source>Plugins/VcsPlugins/vcsMercurial/HgExtensionProjectBrowserHelper.py</Source>
     <Source>Plugins/VcsPlugins/vcsMercurial/ShelveExtension/ProjectBrowserHelper.py</Source>
+    <Source>Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/__init__.py</Source>
+    <Source>Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/largefiles.py</Source>
+    <Source>Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/ProjectHelper.py</Source>
+    <Source>Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/LfConvertDataDialog.py</Source>
   </Sources>
   <Forms>
     <Form>PyUnit/UnittestDialog.ui</Form>
@@ -1446,6 +1450,7 @@
     <Form>Plugins/VcsPlugins/vcsMercurial/ShelveExtension/HgUnshelveDataDialog.ui</Form>
     <Form>Plugins/VcsPlugins/vcsMercurial/ShelveExtension/HgShelveBrowserDialog.ui</Form>
     <Form>Plugins/VcsPlugins/vcsMercurial/ShelveExtension/HgShelvesSelectionDialog.ui</Form>
+    <Form>Plugins/VcsPlugins/vcsMercurial/LargefilesExtension/LfConvertDataDialog.ui</Form>
   </Forms>
   <Translations>
     <Translation>i18n/eric5_cs.ts</Translation>

eric ide

mercurial