def commit_push(self, tree, message, remote_name='origin', username=None, password=None): """Commits and pushes staged changes in the tree TODO: Ensure that the config keys are correct 4/13/20 21 - seems to be working Args: tree (Oid): Oid id created from repositiory index (ex: repo.index.write_tree()) containing the tracked file changes (proably) message (string): commit message remote_name (string, optional): name of the remote to push to username (string, optional): username of user account used for pushing password (string, optional): password of user account used for pushing """ if username is None: username = Config.get('user/username') if password is None: password = Config.get('user/password') # Create commit on current branch, parent is current commit, author and commiter is the default signature self.create_commit(self.head.name, self.default_signature, self.default_signature, message, tree, [self.head.target]) self.push(remote_name, username, password)
def install(name, force=False): """Install plugin by name""" # Log installing plugin Logger.debug(f'MEG Plugins: Installing plugin <{name}>') # Get the available plugin by name available_plugin = PluginManager.get_available(name) if available_plugin is None: # Log failed to install plugin Logger.warning(f'MEG Plugins: Failed to install plugin <{name}>') return False # Log updating plugin cache information Logger.debug( f'MEG Plugins: Found plugin cache information <{available_plugin.path()}>' ) PluginManager._log_plugin(available_plugin) # Get installed plugin by name, if present plugin = PluginManager.get(name) if plugin is not None: # Check plugin is up to date or forcing installation if available_plugin.compare_versions(plugin) <= 0 and not force: return True # Get the plugins cache path plugins_path = Config.get('path/plugins') # Get the plugin installation path plugin_basename = os.path.basename(available_plugin.path()) plugin_path = os.path.join(plugins_path, plugin_basename) try: # Remove the previous plugin path, if necessary if not Config.remove_path(plugin_path): return False # Open the local plugin cache repository cache_path = os.path.join(Config.get('path/plugin_cache'), 'remote', PluginManager.DEFAULT_BARE_REPO_PATH) cache = GitManager.open(cache_path, bare=True) if cache is None: raise GitException( f'Could not open local plugin cache <{cache_path}>') # Log installing plugin Logger.debug(f'MEG Plugins: Installing plugin <{plugin_path}>') # Install plugin by checking out cache.checkout_head(directory=plugins_path, paths=[plugin_basename + '/*']) # Load (or update) plugin information plugin = PluginManager._update(plugin_path, force) if plugin is not None: # Log plugin information PluginManager._log_plugin(plugin) # Setup plugin dependencies plugin.setup().check_returncode() # Add the plugin PluginManager.__instance['plugins'][plugin.name()] = plugin except Exception as e: # Log that loading the plugin cache information failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Could not load information for plugin <{plugin_path}>' ) return False return True
def __init__(self, **kwargs): """UI manager constructor.""" # Load window resource if needed if UIManager.__widgets is None: # Load the resource setup from the package UIManager.__widgets = uic.loadUiType(pkg_resources.resource_filename(__name__, UIManager.UI_FILE)) # Initialize the super class super().__init__(**kwargs) # Setup window resource UIManager.__widgets[0]().setupUi(self) # Set the window panel stack self._panels = [] self._current_panel = None self._current_popup = None # Set handler for closing a panel self._panel = self.findChild(QtWidgets.QTabWidget, 'panelwidget') self._panel.tabCloseRequested.connect(self.remove_view_by_index) self._panel.currentChanged.connect(self._show_view_by_index) # Get status widget self._statusbar = self.findChild(QtWidgets.QStatusBar, 'statusbar') # Set handlers for main buttons # TODO: Add more handlers for these self._action_clone = self.findChild(QtWidgets.QAction, 'action_Clone') self._action_clone.triggered.connect(App.open_clone_panel) self._action_open = self.findChild(QtWidgets.QAction, 'action_Open') self._action_open.triggered.connect(App.open_repo_panel) self._action_quit = self.findChild(QtWidgets.QAction, 'action_Quit') self._action_quit.triggered.connect(App.quit) self._action_about = self.findChild(QtWidgets.QAction, 'action_About') self._action_about.triggered.connect(App.open_about) self._action_preferences = self.findChild(QtWidgets.QAction, 'action_Preferences') self._action_preferences.triggered.connect(App.open_prefs_panel) self._action_manage_plugins = self.findChild(QtWidgets.QAction, 'action_Manage_Plugins') self._action_manage_plugins.triggered.connect(App.open_plugins_panel) # Set the default title self.set_title() # Set the icon icon_path = App.get_icon() if icon_path is not None: self.setWindowIcon(QtGui.QIcon(icon_path)) # Restore the state from the configuration if needed window_state = Config.get('window/state', 'none') state = self.windowState() if window_state == 'maximized': state &= ~(QtCore.Qt.WindowMinimized | QtCore.Qt.WindowFullScreen) state |= QtCore.Qt.WindowMaximized elif window_state == 'minimized': state &= ~(QtCore.Qt.WindowMaximized | QtCore.Qt.WindowFullScreen) state |= QtCore.Qt.WindowMinimized elif window_state == 'fullscreen': state &= ~(QtCore.Qt.WindowMinimized | QtCore.Qt.WindowMaximized) state |= QtCore.Qt.WindowFullScreen self.setWindowState(state) # Restore the window geometry from the configuration if needed geometry = Config.get('window/geometry', None) if isinstance(geometry, list) and len(geometry) == 4: self.setGeometry(geometry[0], geometry[1], geometry[2], geometry[3])
def clone(repo_url, repo_path=None, checkout_branch=None, bare=False, *args, **kwargs): """Clone a remote git repository to a local repository""" # Check there is git repository manager instance if GitManager.__instance is None: GitManager() if GitManager.__instance is not None: # Log cloning repo Logger.debug( f'MEG Git: Cloning git repository <{repo_url}> to <{repo_path}>' ) try: # Get the repository path if not provided if repo_path is None: # Get the root path in the following order: # 1. The configured repositories directory path # 2. The configured user directory path # 3. The current working directory path repo_prefix = Config.get( 'path/repos', Config.get('path/user', os.curdir)) # Append the name of the repository to the path if isinstance(repo_url, str): repo_path = os.path.join(repo_prefix, pathlib.Path(repo_url).stem) elif isinstance(repo_url, pathlib.Path): repo_path = os.path.join(repo_prefix, repo_url.stem) else: raise GitException( f'No local repository path was provided and the path could not be determined from the remote <{repo_url}>' ) # Clone the repository by creating a repository instance return GitRepository(repo_path, repo_url, checkout_branch=checkout_branch, bare=bare, *args, **kwargs) except Exception as e: # Log that cloning the repo failed Logger.warning(f'MEG Git: {e}') Logger.warning( f'MEG Git: Could not clone git repository <{repo_url}> to <{repo_path}>' ) return None
def update_cache(): """Update cached plugin information""" # Check there is plugin manager instance update = False if PluginManager.__instance is None: PluginManager(False) update = True if PluginManager.__instance is None: return False # Log updating plugin information Logger.debug(f'MEG Plugins: Updating plugin cache') # Clear previous plugin cache information if 'plugin_cache' not in PluginManager.__instance or not isinstance( PluginManager.__instance['plugin_cache'], dict): PluginManager.__instance['plugin_cache'] = {} # Get the plugin cache path and create if needed cache_path = os.path.join(Config.get('path/plugin_cache'), 'remote') os.makedirs(cache_path, exist_ok=True) # Open or clone the plugins repository with the plugin cache path cache = GitManager.open_or_clone( os.path.join(cache_path, PluginManager.DEFAULT_BARE_REPO_PATH), Config.get('plugins/url', PluginManager.DEFAULT_CACHE_URL), bare=True) if cache is None: # Log that loading the plugin cache information failed Logger.warning( f'MEG Plugins: Could not update plugin cache information') return False try: # Log updating plugin cache information Logger.debug(f'MEG Plugins: Fetching plugin cache information') # Fetch and update the plugin cache cache.fetch_all() fetch_head = cache.lookup_reference('FETCH_HEAD') if fetch_head is not None: cache.head.set_target(fetch_head.target) # Checkout the plugin information files cache.checkout_head( directory=cache_path, paths=['*/' + PluginInformation.DEFAULT_PLUGIN_INFO_PATH]) except Exception as e: # Log that loading the plugin cache information failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Could not update plugin cache information') return False # Log updating plugin cache information return PluginManager._update_cache(cache_path, update)
def _install_archive_list(archive, archive_list, force): """Install plugin from archive list""" # Get the plugin cache path and create if needed plugins_path = os.path.join(Config.get('path/plugin_cache'), 'local') os.makedirs(plugins_path, exist_ok=True) # Extract found plugins from archive to install retval = True for plugin_name, plugin_list in archive_list: # Get the plugin path plugin_path = os.path.join(plugins_path, plugin_name) try: # Remove the previous plugin path, if necessary if Config.remove_path(plugin_path): # Log caching plugin Logger.debug(f'MEG Plugins: Locally caching plugin <{plugin_path}>') # Extract each plugin from archive to install archive.extractall(plugin_path, plugin_list) # Install cached plugin retval = PluginManager.install_path(plugin_path, force) and retval else: retval = False except Exception as e: # Log that caching plugin locally failed Logger.warning(f'MEG Plugins: {e}') Logger.warning(f'MEG Plugins: Failed to locally cache plugin <{plugin_path}>') retval = False return retval
def __init__(self, update=True, **kwargs): """Plugin manager constructor""" # Check if there is already a plugin manager instance if PluginManager.__instance is not None: # Except if another instance is created raise PluginException(self.__class__.__name__ + " is a singleton!") else: # Initialize super class constructor super().__init__(**kwargs) # Set this as the current plugin manager instance PluginManager.__instance = self # Set plugin and cache paths in python path for import sys.path.append(Config.get('path/plugins')) sys.path.append(Config.get('path/cache')) # Load information about plugins if update: PluginManager.update()
def enable(self): """Enable the plugin""" # Get the enabled plugins enabled_plugins = Config.get('plugins', []) # Check if this plugin is already enabled if isinstance(enabled_plugins, list) and not self.name() in enabled_plugins: # Enable this plugin enabled_plugins.append(self.name()) Config.set('plugins', enabled_plugins)
def sync(self, remote_name='origin', username=None, password=None): """Pulls and then pushes, merge conflicts resolved by pull Args: username (string, optional): username of user account used for pushing password (string, optional): password of user account used for pushing """ if username is None: username = Config.get('user/username') if password is None: password = Config.get('user/password') self.__permissions.save() self.__locking.save() self.pull(remote_name, username=username, password=password) if self.isChanged(username): self.stageChanges(username) self.create_commit('HEAD', self.default_signature, self.default_signature, "MEG SYNC", self.index.write_tree(), [self.head.target]) self.push(remote_name, username=username, password=password)
def disable(self): """Disable the plugin""" # Get the enabled plugins enabled_plugins = Config.get('plugins', []) # Check if this plugin is already enabled if isinstance(enabled_plugins, list) and self.name() in enabled_plugins: # Disable this plugin enabled_plugins.remove(self.name()) Config.set('plugins', enabled_plugins) # Unload plugin self.unload()
def on_show(self): """Showing the panel.""" # Load the repos repos = Config.get('repos') repos = [ QtWidgets.QTreeWidgetItem( [os.path.basename(repo['path']), repo['path'], repo['url']]) for repo in repos if repo['path'] ] self._tree_widget.clear() self._tree_widget.addTopLevelItems(repos)
def save_repo_entry(repo_path, repo_url=None): repos = Config.get('repos', defaultValue=[]) duplicate_found = False for index, repo in enumerate(repos): if repo['path'] == repo_path: repos[index]['url'] = repo_url duplicate_found = True break if not duplicate_found: repos.append({'path': repo_path, 'url': repo_url}) Config.set('repos', repos) Config.save()
def push(self, remote_name='origin', username=None, password=None): """Pushes current commits 4/13/20 21 - seems to be working Args: remote_name (string, optional): name of the remote to push to username (string, optional): username of user account used for pushing password (string, optional): password of user account used for pushing """ if username is None: username = Config.get('user/username') if password is None: password = Config.get('user/password') creds = pygit2.UserPass(username, password) remote = self.remotes[remote_name] remote.credentials = creds try: remote.push([self.head.name], callbacks=pygit2.RemoteCallbacks(credentials=creds)) except GitError as e: Logger.warning(e) Logger.warning("MEG Git Repository: Failed to push commit")
def install_path(path, force=False): """Install plugin from local path""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager() if PluginManager.__instance is not None: # Check if path does not exist if os.path.exists(path) and os.path.isdir(path): # Log trying to load plugin information from path Logger.debug( f'MEG Plugins: Installing plugin from path <{path}>') try: # Get the plugin information for the path if no other version is installed plugin = PluginManager._update(path, force) if plugin is not None: # Log plugin information PluginManager._log_plugin(plugin) # Log trying to load plugin information from path Logger.debug( f'MEG Plugins: Installing plugin <{plugin.name()}>' ) elif not force: # The same version exists and no force so this is installed return True # Get the installed plugin path plugin_path = os.path.join(Config.get('path/plugins'), os.path.basename(path)) # Remove the previous plugin path, if necessary if not Config.remove_path(plugin_path): return False # Copy the path to the plugins directory shutil.copytree(path, plugin_path) # Load (or update) plugin information plugin = Plugin(plugin_path) if plugin is not None: # Log plugin information PluginManager._log_plugin(plugin) # Setup plugin dependencies plugin.setup().check_returncode() # Add the plugin PluginManager.__instance['plugins'][ plugin.name()] = plugin return True except Exception as e: # Log that installing the plugin from the path failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Failed to install plugin from path <{path}>' ) return False
def setup(self): """Install the plugin dependencies""" # Setup plugin dependencies depends = self.dependencies() if isinstance(depends, list) and len(depends) > 0: # Execute PIP to install the dependencies if os.name == 'nt': cmd = ['py', '-3'] else: cmd = ['python3'] cmd.extend(['-m', 'pip', 'install', '--target', Config.get('path/cache'), '--upgrade']) cmd.extend(depends) return subprocess.run(cmd, encoding='utf-8', stdin=None, stdout=subprocess.PIPE, stderr=None, startupinfo=subprocess.STARTUPINFO(wShowWindow=False)) # No dependencies so return success status return subprocess.CompletedProcess([], 0)
def load_enabled(): """Load enabled plugins""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager() if PluginManager.__instance is None: return False # Get the enabled plugin names enabled_plugins = Config.get('plugins', []) if not isinstance(enabled_plugins, list): return False retval = True # For each enabled plugin, load the plugin for name in enabled_plugins: retval = PluginManager.load(name) and retval return retval
def isChanged(self, username): """Are there local changes from the last commit Only counts changes alowed by locking and permission module commitable files """ mask = pygit2.GIT_STATUS_WT_DELETED | pygit2.GIT_STATUS_WT_RENAMED | pygit2.GIT_STATUS_WT_MODIFIED | pygit2.GIT_STATUS_WT_NEW for path, status in self.status().items(): if status & mask == 0: continue lockEntry = self.__locking.findLock(path) if (((lockEntry is None or lockEntry["user"] == Config.get('user/username')) and self.__permissions.can_write(username, path)) or path == Locking.LOCKFILE_PATH or path == Permissions.PERMISSION_PATH): return True return False
def update(): """Update local plugin information""" # Check there is plugin manager instance if PluginManager.__instance is None: PluginManager(False) if PluginManager.__instance is None: return False # Log updating plugin information Logger.debug( f'MEG Plugins: Updating plugin information {Config.get("path/plugins")}' ) # Unload all plugins PluginManager.unload_all() # Clear previous plugin information if 'plugins' not in PluginManager.__instance or not isinstance( PluginManager.__instance['plugins'], dict): PluginManager.__instance['plugins'] = {} # Obtain the available plugins from the plugins path retval = True for (path, dirs, files) in os.walk(Config.get('path/plugins')): # For each plugin load the information for d in dirs: # Log debug information about the plugin plugin_path = os.path.join(path, d) Logger.debug( f'MEG Plugins: Found plugin information <{plugin_path}>') try: # Get the plugin path and load plugin information plugin = PluginManager._update(plugin_path) if plugin is not None: # Log plugin information PluginManager._log_plugin(plugin) # Add the plugin PluginManager.__instance['plugins'][ plugin.name()] = plugin except Exception as e: # Log that loading the plugin information failed Logger.warning(f'MEG Plugins: {e}') Logger.warning( f'MEG Plugins: Could not load information for plugin <{plugin_path}>' ) # Do not say there was an error if the file does not exists if not isinstance(e, OSError) or e.errno != errno.ENOENT: retval = False # Do not actually walk the directory tree, only get directories directly under plugins path break return retval
def clean_plugin_cache(): """Clean plugin cache""" # Remove the plugin cache directory or the block file return Config.remove_path(Config.get('path/plugin_cache'))
def clean_plugins(): """Clean plugins""" # Remove the plugins directory or the block file return Config.remove_path(Config.get('path/plugins'))
def clean_cache(): """Clean dependency cache""" # Remove the cache directory or the block file return Config.remove_path(Config.get('path/cache'))
def enabled(self): """Get if plugin is enabled""" # Check if plugin is enabled enabled_plugins = Config.get('plugins', []) return isinstance(enabled_plugins, list) and self.name() in enabled_plugins
def pull(self, remote_name='origin', fail_on_conflict=False, username=None, password=None): """Pull and merge Merge is done fully automaticly, currently uses 'ours' on conflicts Args: remote_ref_name (string): name of reference to the remote being pulled from """ if username is None: username = Config.get('user/username') if password is None: password = Config.get('user/password') self.__permissions.save() self.__locking.save() # Find state of both references have self.fetch_all() remoteId = self.lookup_reference("FETCH_HEAD").resolve().target localId = self.lookup_reference("HEAD").resolve().target ahead, behind = self.ahead_behind(localId, remoteId) # Pull only required if we are behind if behind > 0: # If changes, commit and prepare for merge if self.isChanged(username): self.stageChanges(username) self.create_commit('HEAD', self.default_signature, self.default_signature, "MEG PULL OWN", self.index.write_tree(), [self.head.target]) # Find the kind of required merge mergeState, _ = self.merge_analysis(remoteId) # Preform merge if mergeState & pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD: # Fastforward and checkout remote self.checkout_tree(self.get(remoteId)) self.head.set_target(remoteId) self.checkout_head() elif mergeState & pygit2.GIT_MERGE_ANALYSIS_NORMAL: # Preform painful merge if fail_on_conflict: self.state_cleanup() return False # Find files not to staged (because lock, permissions,...) and discard changes so merging can occur badPaths = [ entry.path for entry in self.stageChanges(username) ] self.checkout_head(strategy=pygit2.GIT_CHECKOUT_FORCE, paths=badPaths) # Merge will stage changes automaticly and find conflicts self.merge(remoteId) if self.index.conflicts is not None: self.resolveLockingPermissionsMerge() self.resolveGeneralMerge(username) else: self.__permissions.load() self.__locking.load() # Check there are no merge conflicts before committing if self.index.conflicts is None or len( self.index.conflicts) == 0: # Commit the merge self.index.add_all() self.create_commit('HEAD', self.default_signature, self.default_signature, "MEG MERGE", self.index.write_tree(), [self.head.target, remoteId]) self.push(remote_name, username, password) self.state_cleanup() return True