# -*- coding: utf-8 -*- # # Copyright © 2011 Pierre Raybaut # Licensed under the terms of the MIT License # (see spyderlib/__init__.py for details) """Utilities for version control systems""" from __future__ import print_function import os.path as osp import subprocess # Local imports from spyderlib.utils import programs from spyderlib.utils.misc import abspardir from spyderlib.py3compat import PY3 SUPPORTED = [ { 'name': 'Mercurial', 'rootdir': '.hg', 'actions': dict( commit=( ('thg', ['commit']), ('hgtk', ['commit']) ), browse=( ('thg', ['log']), ('hgtk', ['log']) )) }, { 'name': 'Git', 'rootdir': '.git', 'actions': dict( commit=( ('git', ['gui']), ), browse=( ('gitk', []), )) }] class ActionToolNotFound(RuntimeError): """Exception to transmit information about supported tools for failed attempt to execute given action""" def __init__(self, vcsname, action, tools): RuntimeError.__init__(self) self.vcsname = vcsname self.action = action self.tools = tools def get_vcs_info(path): """Return support status dict if path is under VCS root""" for info in SUPPORTED: vcs_path = osp.join(path, info['rootdir']) if osp.isdir(vcs_path): return info def get_vcs_root(path): """Return VCS root directory path Return None if path is not within a supported VCS repository""" previous_path = path while get_vcs_info(path) is None: path = abspardir(path) if path == previous_path: return else: previous_path = path return osp.abspath(path) def is_vcs_repository(path): """Return True if path is a supported VCS repository""" return get_vcs_root(path) is not None def run_vcs_tool(path, action): """If path is a valid VCS repository, run the corresponding VCS tool Supported VCS actions: 'commit', 'browse' Return False if the VCS tool is not installed""" info = get_vcs_info(get_vcs_root(path)) tools = info['actions'][action] for tool, args in tools: if programs.find_program(tool): programs.run_program(tool, args, cwd=path) return else: cmdnames = [name for name, args in tools] raise ActionToolNotFound(info['name'], action, cmdnames) def is_hg_installed(): """Return True if Mercurial is installed""" return programs.find_program('hg') is not None def get_hg_revision(repopath): """Return Mercurial revision for the repository located at repopath Result is a tuple (global, local, branch), with None values on error For example: >>> get_hg_revision(".") ('eba7273c69df+', '2015+', 'default') """ try: hg = programs.find_program('hg') assert hg is not None and osp.isdir(osp.join(repopath, '.hg')) output, _err = subprocess.Popen([hg, 'id', '-nib', repopath], stdout=subprocess.PIPE).communicate() # output is now: ('eba7273c69df+ 2015+ default\n', None) # Split 2 times max to allow spaces in branch names. return tuple(output.decode().strip().split(None, 2)) except (subprocess.CalledProcessError, AssertionError, AttributeError): # print("Error: Failed to get revision number from Mercurial - %s" % exc) return (None, None, None) def get_git_revision(repopath): """Return Git revision for the repository located at repopath Result is the latest commit hash, with None on error """ try: git = programs.find_program('git') assert git is not None and osp.isdir(osp.join(repopath, '.git')) commit = subprocess.Popen([git, 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE, cwd=repopath).communicate() if PY3: commit = str(commit[0][:-1]) commit = commit[2:-1] else: commit = commit[0][:-1] return commit except (subprocess.CalledProcessError, AssertionError, AttributeError): return None if __name__ == '__main__': print(get_vcs_root(osp.dirname(__file__))) print(get_vcs_root(r'D:\Python\ipython\IPython\kernel')) #run_vcs_tool(r'D:\Python\userconfig\userconfig', 'commit') print(get_git_revision(osp.dirname(__file__)+"/../..")) print(get_git_revision('/'))