diff --git a/src/main.py b/src/main.py index fbee217..b291b19 100644 --- a/src/main.py +++ b/src/main.py @@ -53,16 +53,7 @@ from urllib.parse import urlparse from diffuse import utils from diffuse import constants -from diffuse.vcs.folder_set import FolderSet -from diffuse.vcs.bzr import Bzr -from diffuse.vcs.cvs import Cvs -from diffuse.vcs.darcs import Darcs -from diffuse.vcs.git import Git -from diffuse.vcs.hg import Hg -from diffuse.vcs.mtn import Mtn -from diffuse.vcs.rcs import Rcs -from diffuse.vcs.svk import Svk -from diffuse.vcs.svn import Svn +from diffuse.vcs.vcs_registry import VcsRegistry if not hasattr(__builtins__, 'WindowsError'): # define 'WindowsError' so 'except' statements will work on all platforms @@ -1192,191 +1183,7 @@ def convert_to_format(s, format): s += '\r' return s -# returns the Windows drive or share from a from an absolute path -def drive_from_path(s): - c = s.split(os.sep) - if len(c) > 3 and c[0] == '' and c[1] == '': - return os.path.join(c[:4]) - return c[0] - -# escape arguments for use with bash -def bashEscape(s): - return "'" + s.replace("'", "'\\''") + "'" - -# utility method to help find folders used by version control systems -def _find_parent_dir_with(path, dir_name): - while True: - name = os.path.join(path, dir_name) - if os.path.isdir(name): - return path - newpath = os.path.dirname(path) - if newpath == path: - break - path = newpath - -def _get_bzr_repo(path, prefs): - p = _find_parent_dir_with(path, '.bzr') - if p: - return Bzr(p) - -def _get_cvs_repo(path, prefs): - if os.path.isdir(os.path.join(path, 'CVS')): - return Cvs(path) - -def _get_darcs_repo(path, prefs): - p = _find_parent_dir_with(path, '_darcs') - if p: - return Darcs(p) - -def _get_git_repo(path, prefs): - if 'GIT_DIR' in os.environ: - try: - d = path - ss = utils.popenReadLines(d, [ prefs.getString('git_bin'), 'rev-parse', '--show-prefix' ], prefs, 'git_bash') - if len(ss) > 0: - # be careful to handle trailing slashes - d = d.split(os.sep) - if d[-1] != '': - d.append('') - ss = strip_eol(ss[0]).split('/') - if ss[-1] != '': - ss.append('') - n = len(ss) - if n <= len(d): - del d[-n:] - if len(d) == 0: - d = os.curdir - else: - d = os.sep.join(d) - return Git(d) - except (IOError, OSError, WindowsError): - # working tree not found - pass - # search for .git directory (project) or .git file (submodule) - while True: - name = os.path.join(path, '.git') - if os.path.isdir(name) or os.path.isfile(name): - return Git(path) - newpath = os.path.dirname(path) - if newpath == path: - break - path = newpath - -def _get_hg_repo(path, prefs): - p = _find_parent_dir_with(path, '.hg') - if p: - return Hg(p) - -def _get_mtn_repo(path, prefs): - p = _find_parent_dir_with(path, '_MTN') - if p: - return Mtn(p) - -def _get_rcs_repo(path, prefs): - if os.path.isdir(os.path.join(path, 'RCS')): - return Rcs(path) - - # [rfailliot] this code doesn't seem to work, but was in 0.4.8 too. - # I'm letting it here until further tests are done, but it is possible - # this code never actually worked. - try: - for s in os.listdir(path): - if s.endswith(',v') and os.path.isfile(os.path.join(path, s)): - return Rcs(path) - except OSError: - # the user specified an invalid folder name - pass - -def _get_svn_repo(path, prefs): - p = _find_parent_dir_with(path, '.svn') - if p: - return Svn(p) - -def _get_svk_repo(path, prefs): - name = path - # parse the ~/.svk/config file to discover which directories are part of - # SVK repositories - if utils.isWindows(): - name = name.upper() - svkroot = os.environ.get('SVKROOT', None) - if svkroot is None: - svkroot = os.path.expanduser('~/.svk') - svkconfig = os.path.join(svkroot, 'config') - if os.path.isfile(svkconfig): - try: - # find working copies by parsing the config file - f = open(svkconfig, 'r') - ss = readlines(f) - f.close() - projs, sep = [], os.sep - # find the separator character - for s in ss: - if s.startswith(' sep: ') and len(s) > 7: - sep = s[7] - # find the project directories - i = 0 - while i < len(ss): - s = ss[i] - i += 1 - if s.startswith(' hash: '): - while i < len(ss) and ss[i].startswith(' '): - s = ss[i] - i += 1 - if s.endswith(': ') and i < len(ss) and ss[i].startswith(' depotpath: '): - key = s[4:-2].replace(sep, os.sep) - # parse directory path - j, n, tt = 0, len(key), [] - while j < n: - if key[j] == '"': - # quoted string - j += 1 - while j < n: - if key[j] == '"': - j += 1 - break - elif key[j] == '\\': - # escaped character - j += 1 - if j < n: - tt.append(key[j]) - j += 1 - else: - tt.append(key[j]) - j += 1 - key = ''.join(tt).replace(sep, os.sep) - if utils.isWindows(): - key = key.upper() - projs.append(key) - break - # check if the file belongs to one of the project directories - if FolderSet(projs).contains(name): - return Svk(path) - except IOError: - utils.logError(_('Error parsing %s.') % (svkconfig, )) - -class VCSs: - def __init__(self): - # initialise the VCS objects - self._get_repo = { 'bzr': _get_bzr_repo, 'cvs': _get_cvs_repo, 'darcs': _get_darcs_repo, 'git': _get_git_repo, 'hg': _get_hg_repo, 'mtn': _get_mtn_repo, 'rcs': _get_rcs_repo, 'svk': _get_svk_repo, 'svn': _get_svn_repo } - - def setSearchOrder(self, ordering): - self._search_order = ordering - - # determines which VCS to use for files in the named folder - def findByFolder(self, path, prefs): - path = os.path.abspath(path) - for vcs in prefs.getString('vcs_search_order').split(): - if vcs in self._get_repo: - repo = self._get_repo[vcs](path, prefs) - if repo: - return repo - - # determines which VCS to use for the named file - def findByFilename(self, name, prefs): - if name is not None: - return self.findByFolder(os.path.dirname(name), prefs) - -theVCSs = VCSs() +theVCSs = VcsRegistry() # utility method to step advance an adjustment def step_adjustment(adj, delta): diff --git a/src/utils.py b/src/utils.py index 9428c07..9b8dadd 100644 --- a/src/utils.py +++ b/src/utils.py @@ -104,6 +104,17 @@ def safeRelativePath(abspath1, name, prefs, cygwin_pref): s = s.replace('/', '\\') return s +# returns the Windows drive or share from a from an absolute path +def drive_from_path(s): + c = s.split(os.sep) + if len(c) > 3 and c[0] == '' and c[1] == '': + return os.path.join(c[:4]) + return c[0] + +# escape arguments for use with bash +def bashEscape(s): + return "'" + s.replace("'", "'\\''") + "'" + # use popen to read the output of a command def popenRead(dn, cmd, prefs, bash_pref, success_results=None): if success_results is None: diff --git a/src/vcs/vcs_registry.py b/src/vcs/vcs_registry.py new file mode 100644 index 0000000..ac17175 --- /dev/null +++ b/src/vcs/vcs_registry.py @@ -0,0 +1,216 @@ +# Diffuse: a graphical tool for merging and comparing text files. +# +# Copyright (C) 2019 Derrick Moser +# Copyright (C) 2021 Romain Failliot +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os + +from diffuse import utils +from diffuse.vcs.folder_set import FolderSet +from diffuse.vcs.bzr import Bzr +from diffuse.vcs.cvs import Cvs +from diffuse.vcs.darcs import Darcs +from diffuse.vcs.git import Git +from diffuse.vcs.hg import Hg +from diffuse.vcs.mtn import Mtn +from diffuse.vcs.rcs import Rcs +from diffuse.vcs.svk import Svk +from diffuse.vcs.svn import Svn + +class VcsRegistry: + def __init__(self): + # initialise the VCS objects + self._get_repo = { + 'bzr': _get_bzr_repo, + 'cvs': _get_cvs_repo, + 'darcs': _get_darcs_repo, + 'git': _get_git_repo, + 'hg': _get_hg_repo, + 'mtn': _get_mtn_repo, + 'rcs': _get_rcs_repo, + 'svk': _get_svk_repo, + 'svn': _get_svn_repo + } + + def setSearchOrder(self, ordering): + self._search_order = ordering + + # determines which VCS to use for files in the named folder + def findByFolder(self, path, prefs): + path = os.path.abspath(path) + for vcs in prefs.getString('vcs_search_order').split(): + if vcs in self._get_repo: + repo = self._get_repo[vcs](path, prefs) + if repo: + return repo + + # determines which VCS to use for the named file + def findByFilename(self, name, prefs): + if name is not None: + return self.findByFolder(os.path.dirname(name), prefs) + + +# utility method to help find folders used by version control systems +def _find_parent_dir_with(path, dir_name): + while True: + name = os.path.join(path, dir_name) + if os.path.isdir(name): + return path + newpath = os.path.dirname(path) + if newpath == path: + break + path = newpath + +def _get_bzr_repo(path, prefs): + p = _find_parent_dir_with(path, '.bzr') + if p: + return Bzr(p) + +def _get_cvs_repo(path, prefs): + if os.path.isdir(os.path.join(path, 'CVS')): + return Cvs(path) + +def _get_darcs_repo(path, prefs): + p = _find_parent_dir_with(path, '_darcs') + if p: + return Darcs(p) + +def _get_git_repo(path, prefs): + if 'GIT_DIR' in os.environ: + try: + d = path + ss = utils.popenReadLines(d, [ prefs.getString('git_bin'), 'rev-parse', '--show-prefix' ], prefs, 'git_bash') + if len(ss) > 0: + # be careful to handle trailing slashes + d = d.split(os.sep) + if d[-1] != '': + d.append('') + ss = strip_eol(ss[0]).split('/') + if ss[-1] != '': + ss.append('') + n = len(ss) + if n <= len(d): + del d[-n:] + if len(d) == 0: + d = os.curdir + else: + d = os.sep.join(d) + return Git(d) + except (IOError, OSError, WindowsError): + # working tree not found + pass + # search for .git directory (project) or .git file (submodule) + while True: + name = os.path.join(path, '.git') + if os.path.isdir(name) or os.path.isfile(name): + return Git(path) + newpath = os.path.dirname(path) + if newpath == path: + break + path = newpath + +def _get_hg_repo(path, prefs): + p = _find_parent_dir_with(path, '.hg') + if p: + return Hg(p) + +def _get_mtn_repo(path, prefs): + p = _find_parent_dir_with(path, '_MTN') + if p: + return Mtn(p) + +def _get_rcs_repo(path, prefs): + if os.path.isdir(os.path.join(path, 'RCS')): + return Rcs(path) + + # [rfailliot] this code doesn't seem to work, but was in 0.4.8 too. + # I'm letting it here until further tests are done, but it is possible + # this code never actually worked. + try: + for s in os.listdir(path): + if s.endswith(',v') and os.path.isfile(os.path.join(path, s)): + return Rcs(path) + except OSError: + # the user specified an invalid folder name + pass + +def _get_svn_repo(path, prefs): + p = _find_parent_dir_with(path, '.svn') + if p: + return Svn(p) + +def _get_svk_repo(path, prefs): + name = path + # parse the ~/.svk/config file to discover which directories are part of + # SVK repositories + if utils.isWindows(): + name = name.upper() + svkroot = os.environ.get('SVKROOT', None) + if svkroot is None: + svkroot = os.path.expanduser('~/.svk') + svkconfig = os.path.join(svkroot, 'config') + if os.path.isfile(svkconfig): + try: + # find working copies by parsing the config file + f = open(svkconfig, 'r') + ss = readlines(f) + f.close() + projs, sep = [], os.sep + # find the separator character + for s in ss: + if s.startswith(' sep: ') and len(s) > 7: + sep = s[7] + # find the project directories + i = 0 + while i < len(ss): + s = ss[i] + i += 1 + if s.startswith(' hash: '): + while i < len(ss) and ss[i].startswith(' '): + s = ss[i] + i += 1 + if s.endswith(': ') and i < len(ss) and ss[i].startswith(' depotpath: '): + key = s[4:-2].replace(sep, os.sep) + # parse directory path + j, n, tt = 0, len(key), [] + while j < n: + if key[j] == '"': + # quoted string + j += 1 + while j < n: + if key[j] == '"': + j += 1 + break + elif key[j] == '\\': + # escaped character + j += 1 + if j < n: + tt.append(key[j]) + j += 1 + else: + tt.append(key[j]) + j += 1 + key = ''.join(tt).replace(sep, os.sep) + if utils.isWindows(): + key = key.upper() + projs.append(key) + break + # check if the file belongs to one of the project directories + if FolderSet(projs).contains(name): + return Svk(path) + except IOError: + utils.logError(_('Error parsing %s.') % (svkconfig, ))