Merge pull request #109 from MightyCreak/modularize-vcs
Modularize VCSs
This commit is contained in:
commit
85975f40a8
|
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
message
|
||||
|
||||
### Changed
|
||||
- Modularized the VCSs (reducing main.py by around 1300 lines)
|
||||
|
||||
### Fixed
|
||||
- Fixed 'APP_NAME' error when opening non existing file
|
||||
|
|
|
@ -31,5 +31,4 @@ gettext.install('diffuse', localedir)
|
|||
|
||||
if __name__ == '__main__':
|
||||
from diffuse import main
|
||||
|
||||
sys.exit(main.main())
|
||||
|
|
1319
src/main.py
1319
src/main.py
File diff suppressed because it is too large
Load Diff
|
@ -37,3 +37,4 @@ diffuse_sources = [
|
|||
]
|
||||
|
||||
install_data(diffuse_sources, install_dir: moduledir)
|
||||
install_subdir('vcs', install_dir: moduledir, strip_directory: false)
|
||||
|
|
41
src/utils.py
41
src/utils.py
|
@ -79,6 +79,42 @@ def make_subdirs(p, ss):
|
|||
def useFlatpak():
|
||||
return constants.use_flatpak
|
||||
|
||||
# constructs a relative path from 'a' to 'b', both should be absolute paths
|
||||
def relpath(a, b):
|
||||
if isWindows():
|
||||
if drive_from_path(a) != drive_from_path(b):
|
||||
return b
|
||||
c1 = [ c for c in a.split(os.sep) if c != '' ]
|
||||
c2 = [ c for c in b.split(os.sep) if c != '' ]
|
||||
i, n = 0, len(c1)
|
||||
while i < n and i < len(c2) and c1[i] == c2[i]:
|
||||
i += 1
|
||||
r = (n - i) * [ os.pardir ]
|
||||
r.extend(c2[i:])
|
||||
return os.sep.join(r)
|
||||
|
||||
# helper function prevent files from being confused with command line options
|
||||
# by prepending './' to the basename
|
||||
def safeRelativePath(abspath1, name, prefs, cygwin_pref):
|
||||
s = os.path.join(os.curdir, utils.relpath(abspath1, os.path.abspath(name)))
|
||||
if utils.isWindows():
|
||||
if prefs.getBool(cygwin_pref):
|
||||
s = s.replace('\\', '/')
|
||||
else:
|
||||
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:
|
||||
|
@ -141,6 +177,11 @@ def popenXArgsReadLines(dn, cmd, args, prefs, bash_pref):
|
|||
s, a = 0, []
|
||||
return ss
|
||||
|
||||
# escape special glob characters
|
||||
def globEscape(s):
|
||||
m = dict([ (c, f'[{c}]') for c in '[]?*' ])
|
||||
return ''.join([ m.get(c, c) for c in s ])
|
||||
|
||||
# use the program's location as a starting place to search for supporting files
|
||||
# such as icon and help documentation
|
||||
if hasattr(sys, 'frozen'):
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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.vcs_interface import VcsInterface
|
||||
|
||||
# Bazaar support
|
||||
class Bzr(VcsInterface):
|
||||
def getFileTemplate(self, prefs, name):
|
||||
# merge conflict
|
||||
left = name + '.OTHER'
|
||||
right = name + '.THIS'
|
||||
if os.path.isfile(left) and os.path.isfile(right):
|
||||
return [ (left, None), (name, None), (right, None) ]
|
||||
# default case
|
||||
return [ (name, '-1'), (name, None) ]
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
# build command
|
||||
args = [ prefs.getString('bzr_bin'), 'log', '-v', '-r', rev ]
|
||||
# build list of interesting files
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
args.append(utils.safeRelativePath(self.root, name, prefs, 'bzr_cygwin'))
|
||||
# run command
|
||||
ss = utils.popenReadLines(self.root, args, prefs, 'bzr_bash')
|
||||
# parse response
|
||||
prev = 'before:' + rev
|
||||
fs = FolderSet(names)
|
||||
added, modified, removed, renamed = {}, {}, {}, {}
|
||||
i, n = 0, len(ss)
|
||||
while i < n:
|
||||
s = ss[i]
|
||||
i += 1
|
||||
if s.startswith('added:'):
|
||||
# added files
|
||||
while i < n and ss[i].startswith(' '):
|
||||
k = prefs.convertToNativePath(ss[i][2:])
|
||||
i += 1
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
added[k] = [ (None, None), (k, rev) ]
|
||||
elif s.startswith('modified:'):
|
||||
# modified files
|
||||
while i < n and ss[i].startswith(' '):
|
||||
k = prefs.convertToNativePath(ss[i][2:])
|
||||
i += 1
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
modified[k] = [ (k, prev), (k, rev) ]
|
||||
elif s.startswith('removed:'):
|
||||
# removed files
|
||||
while i < n and ss[i].startswith(' '):
|
||||
k = prefs.convertToNativePath(ss[i][2:])
|
||||
i += 1
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
removed[k] = [ (k, prev), (None, None) ]
|
||||
elif s.startswith('renamed:'):
|
||||
# renamed files
|
||||
while i < n and ss[i].startswith(' '):
|
||||
k = ss[i][2:].split(' => ')
|
||||
i += 1
|
||||
if len(k) == 2:
|
||||
k0 = prefs.convertToNativePath(k[0])
|
||||
k1 = prefs.convertToNativePath(k[1])
|
||||
if not k0.endswith(os.sep) and not k1.endswith(os.sep):
|
||||
k0 = os.path.join(self.root, k0)
|
||||
k1 = os.path.join(self.root, k1)
|
||||
if fs.contains(k0) or fs.contains(k1):
|
||||
if not isabs:
|
||||
k0 = utils.relpath(pwd, k0)
|
||||
k1 = utils.relpath(pwd, k1)
|
||||
renamed[k1] = [ (k0, prev), (k1, rev) ]
|
||||
# sort the results
|
||||
result, r = [], set()
|
||||
for m in removed, added, modified, renamed:
|
||||
r.update(m.keys())
|
||||
for k in sorted(r):
|
||||
for m in removed, added, modified, renamed:
|
||||
if k in m:
|
||||
result.append(m[k])
|
||||
return result
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
# build command
|
||||
args = [ prefs.getString('bzr_bin'), 'status', '-SV' ]
|
||||
# build list of interesting files
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
args.append(utils.safeRelativePath(self.root, name, prefs, 'bzr_cygwin'))
|
||||
# run command
|
||||
prev = '-1'
|
||||
fs = FolderSet(names)
|
||||
added, modified, removed, renamed = {}, {}, {}, {}
|
||||
for s in utils.popenReadLines(self.root, args, prefs, 'bzr_bash'):
|
||||
# parse response
|
||||
if len(s) < 5:
|
||||
continue
|
||||
y, k = s[1], s[4:]
|
||||
if y == 'D':
|
||||
# removed
|
||||
k = prefs.convertToNativePath(k)
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
removed[k] = [ (k, prev), (None, None) ]
|
||||
elif y == 'N':
|
||||
# added
|
||||
k = prefs.convertToNativePath(k)
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
added[k] = [ (None, None), (k, None) ]
|
||||
elif y == 'M':
|
||||
# modified or merge conflict
|
||||
k = prefs.convertToNativePath(k)
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
modified[k] = self.getFileTemplate(prefs, k)
|
||||
elif s[0] == 'R':
|
||||
# renamed
|
||||
k = k.split(' => ')
|
||||
if len(k) == 2:
|
||||
k0 = prefs.convertToNativePath(k[0])
|
||||
k1 = prefs.convertToNativePath(k[1])
|
||||
if not k0.endswith(os.sep) and not k1.endswith(os.sep):
|
||||
k0 = os.path.join(self.root, k0)
|
||||
k1 = os.path.join(self.root, k1)
|
||||
if fs.contains(k0) or fs.contains(k1):
|
||||
if not isabs:
|
||||
k0 = utils.relpath(pwd, k0)
|
||||
k1 = utils.relpath(pwd, k1)
|
||||
renamed[k1] = [ (k0, prev), (k1, None) ]
|
||||
# sort the results
|
||||
result, r = [], set()
|
||||
for m in removed, added, modified, renamed:
|
||||
r.update(m.keys())
|
||||
for k in sorted(r):
|
||||
for m in removed, added, modified, renamed:
|
||||
if k in m:
|
||||
result.append(m[k])
|
||||
return result
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
prefs.getString('bzr_bin'),
|
||||
'cat',
|
||||
'--name-from-revision',
|
||||
'-r',
|
||||
rev,
|
||||
utils.safeRelativePath(self.root, name, prefs, 'bzr_cygwin')
|
||||
],
|
||||
prefs,
|
||||
'bzr_bash')
|
|
@ -0,0 +1,112 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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.vcs_interface import VcsInterface
|
||||
|
||||
# CVS support
|
||||
class Cvs(VcsInterface):
|
||||
def getFileTemplate(self, prefs, name):
|
||||
return [ (name, 'BASE'), (name, None) ]
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
result = []
|
||||
try:
|
||||
r, prev = rev.split('.'), None
|
||||
if len(r) > 1:
|
||||
m = int(r.pop())
|
||||
if m > 1:
|
||||
r.append(str(m - 1))
|
||||
else:
|
||||
m = int(r.pop())
|
||||
if len(r):
|
||||
prev = '.'.join(r)
|
||||
for k in sorted(names):
|
||||
if prev is None:
|
||||
k0 = None
|
||||
else:
|
||||
k0 = k
|
||||
result.append([ (k0, prev), (k, rev) ])
|
||||
except ValueError:
|
||||
utils.logError(_('Error parsing revision %s.') % (rev, ))
|
||||
return result
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
# build command
|
||||
args = [ prefs.getString('cvs_bin'), '-nq', 'update', '-R' ]
|
||||
# build list of interesting files
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
args.append(utils.safeRelativePath(self.root, name, prefs, 'cvs_cygwin'))
|
||||
# run command
|
||||
prev = 'BASE'
|
||||
fs = FolderSet(names)
|
||||
modified = {}
|
||||
for s in utils.popenReadLines(self.root, args, prefs, 'cvs_bash'):
|
||||
# parse response
|
||||
if len(s) < 3 or s[0] not in 'ACMR':
|
||||
continue
|
||||
k = os.path.join(self.root, prefs.convertToNativePath(s[2:]))
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
if s[0] == 'R':
|
||||
# removed
|
||||
modified[k] = [ (k, prev), (None, None) ]
|
||||
pass
|
||||
elif s[0] == 'A':
|
||||
# added
|
||||
modified[k] = [ (None, None), (k, None) ]
|
||||
else:
|
||||
# modified
|
||||
modified[k] = [ (k, prev), (k, None) ]
|
||||
# sort the results
|
||||
return [ modified[k] for k in sorted(modified.keys()) ]
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
if rev == 'BASE' and not os.path.exists(name):
|
||||
# find revision for removed files
|
||||
for s in utils.popenReadLines(
|
||||
self.root,
|
||||
[
|
||||
prefs.getString('cvs_bin'),
|
||||
'status',
|
||||
utils.safeRelativePath(self.root, name, prefs, 'cvs_cygwin')
|
||||
],
|
||||
prefs,
|
||||
'cvs_bash'):
|
||||
if s.startswith(' Working revision:\t-'):
|
||||
rev = s.split('\t')[1][1:]
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
prefs.getString('cvs_bin'),
|
||||
'-Q',
|
||||
'update',
|
||||
'-p',
|
||||
'-r',
|
||||
rev,
|
||||
utils.safeRelativePath(self.root, name, prefs, 'cvs_cygwin')
|
||||
],
|
||||
prefs,
|
||||
'cvs_bash')
|
|
@ -0,0 +1,144 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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.vcs_interface import VcsInterface
|
||||
|
||||
# Darcs support
|
||||
class Darcs(VcsInterface):
|
||||
def getFileTemplate(self, prefs, name):
|
||||
return [ (name, ''), (name, None) ]
|
||||
|
||||
def _getCommitTemplate(self, prefs, names, rev):
|
||||
mods = (rev is None)
|
||||
# build command
|
||||
args = [ prefs.getString('darcs_bin') ]
|
||||
if mods:
|
||||
args.extend(['whatsnew', '-s'])
|
||||
else:
|
||||
args.extend(['log', '--number', '-s'])
|
||||
try:
|
||||
args.extend(['-n', str(int(rev))])
|
||||
except ValueError:
|
||||
args.extend(['-h', rev])
|
||||
# build list of interesting files
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
if mods:
|
||||
args.append(utils.safeRelativePath(self.root, name, prefs, 'darcs_cygwin'))
|
||||
# run command
|
||||
# 'darcs whatsnew' will return 1 if there are no changes
|
||||
ss = utils.popenReadLines(self.root, args, prefs, 'darcs_bash', [0, 1])
|
||||
# parse response
|
||||
i, n = 0, len(ss)
|
||||
if mods:
|
||||
prev = ''
|
||||
rev = None
|
||||
else:
|
||||
try:
|
||||
rev = ss[0].split(':')[0]
|
||||
prev = str(int(rev) + 1)
|
||||
# skip to the beginning of the summary
|
||||
while i < n and len(ss[i]):
|
||||
i += 1
|
||||
except (ValueError, IndexError):
|
||||
i = n
|
||||
fs = FolderSet(names)
|
||||
added, modified, removed, renamed = {}, {}, {}, {}
|
||||
while i < n:
|
||||
s = ss[i]
|
||||
i += 1
|
||||
if not mods:
|
||||
if s.startswith(' '):
|
||||
s = s[4:]
|
||||
else:
|
||||
continue
|
||||
if len(s) < 2:
|
||||
continue
|
||||
x = s[0]
|
||||
if x == 'R':
|
||||
# removed
|
||||
k = prefs.convertToNativePath(s[2:])
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
removed[k] = [ (k, prev), (None, None) ]
|
||||
elif x == 'A':
|
||||
# added
|
||||
k = prefs.convertToNativePath(s[2:])
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
added[k] = [ (None, None), (k, rev) ]
|
||||
elif x == 'M':
|
||||
# modified
|
||||
k = prefs.convertToNativePath(s[2:].split(' ')[0])
|
||||
if not k.endswith(os.sep):
|
||||
k = os.path.join(self.root, k)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
if k not in renamed:
|
||||
modified[k] = [ (k, prev), (k, rev) ]
|
||||
elif x == ' ':
|
||||
# renamed
|
||||
k = s[1:].split(' -> ')
|
||||
if len(k) == 2:
|
||||
k0 = prefs.convertToNativePath(k[0])
|
||||
k1 = prefs.convertToNativePath(k[1])
|
||||
if not k0.endswith(os.sep):
|
||||
k0 = os.path.join(self.root, k0)
|
||||
k1 = os.path.join(self.root, k1)
|
||||
if fs.contains(k0) or fs.contains(k1):
|
||||
if not isabs:
|
||||
k0 = utils.relpath(pwd, k0)
|
||||
k1 = utils.relpath(pwd, k1)
|
||||
renamed[k1] = [ (k0, prev), (k1, rev) ]
|
||||
# sort the results
|
||||
result, r = [], set()
|
||||
for m in added, modified, removed, renamed:
|
||||
r.update(m.keys())
|
||||
for k in sorted(r):
|
||||
for m in removed, added, modified, renamed:
|
||||
if k in m:
|
||||
result.append(m[k])
|
||||
return result
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
return self._getCommitTemplate(prefs, names, rev)
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
return self._getCommitTemplate(prefs, names, None)
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
args = [ prefs.getString('darcs_bin'), 'show', 'contents' ]
|
||||
try:
|
||||
args.extend([ '-n', str(int(rev)) ])
|
||||
except ValueError:
|
||||
args.extend([ '-h', rev ])
|
||||
args.append(utils.safeRelativePath(self.root, name, prefs, 'darcs_cygwin'))
|
||||
return utils.popenRead(self.root, args, prefs, 'darcs_bash')
|
|
@ -0,0 +1,45 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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
|
||||
|
||||
class FolderSet:
|
||||
'''Utility class to help support Git and Monotone.
|
||||
Represents a set of files and folders of interest for "git status" or
|
||||
"mtn automate inventory."'''
|
||||
|
||||
def __init__(self, names):
|
||||
self.folders = f = []
|
||||
for name in names:
|
||||
name = os.path.abspath(name)
|
||||
# ensure all names end with os.sep
|
||||
if not name.endswith(os.sep):
|
||||
name += os.sep
|
||||
f.append(name)
|
||||
|
||||
# returns True if the given abspath is a file that should be included in
|
||||
# the interesting file subset
|
||||
def contains(self, abspath):
|
||||
if not abspath.endswith(os.sep):
|
||||
abspath += os.sep
|
||||
for f in self.folders:
|
||||
if abspath.startswith(f):
|
||||
return True
|
||||
return False
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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.vcs_interface import VcsInterface
|
||||
|
||||
# Git support
|
||||
class Git(VcsInterface):
|
||||
def getFileTemplate(self, prefs, name):
|
||||
return [ (name, 'HEAD'), (name, None) ]
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
# build command
|
||||
args = [ prefs.getString('git_bin'), 'show', '--pretty=format:', '--name-status', rev ]
|
||||
# build list of interesting files
|
||||
pwd = os.path.abspath(os.curdir)
|
||||
isabs = False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
# run command
|
||||
prev = rev + '^'
|
||||
fs = FolderSet(names)
|
||||
modified = {}
|
||||
for s in utils.popenReadLines(self.root, args, prefs, 'git_bash'):
|
||||
# parse response
|
||||
if len(s) < 2 or s[0] not in 'ADM':
|
||||
continue
|
||||
k = self._extractPath(s[2:], prefs)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
if s[0] == 'D':
|
||||
# removed
|
||||
modified[k] = [ (k, prev), (None, None) ]
|
||||
elif s[0] == 'A':
|
||||
# added
|
||||
modified[k] = [ (None, None), (k, rev) ]
|
||||
else:
|
||||
# modified
|
||||
modified[k] = [ (k, prev), (k, rev) ]
|
||||
# sort the results
|
||||
return [ modified[k] for k in sorted(modified.keys()) ]
|
||||
|
||||
def _extractPath(self, s, prefs):
|
||||
return os.path.join(self.root, prefs.convertToNativePath(s.strip()))
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
# build command
|
||||
args = [ prefs.getString('git_bin'), 'status', '--porcelain', '-s', '--untracked-files=no', '--ignore-submodules=all' ]
|
||||
# build list of interesting files
|
||||
pwd = os.path.abspath(os.curdir)
|
||||
isabs = False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
# run command
|
||||
prev = 'HEAD'
|
||||
fs = FolderSet(names)
|
||||
modified, renamed = {}, {}
|
||||
# 'git status' will return 1 when a commit would fail
|
||||
for s in utils.popenReadLines(self.root, args, prefs, 'git_bash', [0, 1]):
|
||||
# parse response
|
||||
if len(s) < 3:
|
||||
continue
|
||||
x, y, k = s[0], s[1], s[2:]
|
||||
if x == 'R':
|
||||
# renamed
|
||||
k = k.split(' -> ')
|
||||
if len(k) == 2:
|
||||
k0 = self._extractPath(k[0], prefs)
|
||||
k1 = self._extractPath(k[1], prefs)
|
||||
if fs.contains(k0) or fs.contains(k1):
|
||||
if not isabs:
|
||||
k0 = utils.relpath(pwd, k0)
|
||||
k1 = utils.relpath(pwd, k1)
|
||||
renamed[k1] = [ (k0, prev), (k1, None) ]
|
||||
elif x == 'U' or y == 'U' or (x == 'D' and y == 'D'):
|
||||
# merge conflict
|
||||
k = self._extractPath(k, prefs)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
if x == 'D':
|
||||
panes = [ (None, None) ]
|
||||
else:
|
||||
panes = [ (k, ':2') ]
|
||||
panes.append((k, None))
|
||||
if y == 'D':
|
||||
panes.append((None, None))
|
||||
else:
|
||||
panes.append((k, ':3'))
|
||||
if x != 'A' and y != 'A':
|
||||
panes.append((k, ':1'))
|
||||
modified[k] = panes
|
||||
else:
|
||||
k = self._extractPath(k, prefs)
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
if x == 'A':
|
||||
# added
|
||||
panes = [ (None, None) ]
|
||||
else:
|
||||
panes = [ (k, prev) ]
|
||||
# staged changes
|
||||
if x == 'D':
|
||||
panes.append((None, None))
|
||||
elif x != ' ':
|
||||
panes.append((k, ':0'))
|
||||
# working copy changes
|
||||
if y == 'D':
|
||||
panes.append((None, None))
|
||||
elif y != ' ':
|
||||
panes.append((k, None))
|
||||
modified[k] = panes
|
||||
# sort the results
|
||||
result, r = [], set()
|
||||
for m in modified, renamed:
|
||||
r.update(m.keys())
|
||||
for k in sorted(r):
|
||||
for m in modified, renamed:
|
||||
if k in m:
|
||||
result.append(m[k])
|
||||
return result
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
prefs.getString('git_bin'),
|
||||
'show',
|
||||
'{}:{}'.format(
|
||||
rev,
|
||||
utils.relpath(self.root, os.path.abspath(name)).replace(os.sep, '/'))
|
||||
],
|
||||
prefs,
|
||||
'git_bash')
|
|
@ -0,0 +1,100 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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.vcs_interface import VcsInterface
|
||||
|
||||
# Mercurial support
|
||||
class Hg(VcsInterface):
|
||||
def __init__(self, root):
|
||||
VcsInterface.__init__(self, root)
|
||||
self.working_rev = None
|
||||
|
||||
def _getPreviousRevision(self, prefs, rev):
|
||||
if rev is None:
|
||||
if self.working_rev is None:
|
||||
ss = utils.popenReadLines(self.root, [ prefs.getString('hg_bin'), 'id', '-i', '-t' ], prefs, 'hg_bash')
|
||||
if len(ss) != 1:
|
||||
raise IOError('Unknown working revision')
|
||||
ss = ss[0].split(' ')
|
||||
prev = ss[-1]
|
||||
if len(ss) == 1 and prev.endswith('+'):
|
||||
# remove local modifications indicator
|
||||
prev = prev[:-1]
|
||||
self.working_rev = prev
|
||||
return self.working_rev
|
||||
return f'p1({rev})'
|
||||
|
||||
def getFileTemplate(self, prefs, name):
|
||||
return [ (name, self._getPreviousRevision(prefs, None)), (name, None) ]
|
||||
|
||||
def _getCommitTemplate(self, prefs, names, cmd, rev):
|
||||
# build command
|
||||
args = [ prefs.getString('hg_bin') ]
|
||||
args.extend(cmd)
|
||||
# build list of interesting files
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
args.append(utils.safeRelativePath(self.root, name, prefs, 'hg_cygwin'))
|
||||
# run command
|
||||
prev = self._getPreviousRevision(prefs, rev)
|
||||
fs = FolderSet(names)
|
||||
modified = {}
|
||||
for s in utils.popenReadLines(self.root, args, prefs, 'hg_bash'):
|
||||
# parse response
|
||||
if len(s) < 3 or s[0] not in 'AMR':
|
||||
continue
|
||||
k = os.path.join(self.root, prefs.convertToNativePath(s[2:]))
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
if s[0] == 'R':
|
||||
# removed
|
||||
modified[k] = [ (k, prev), (None, None) ]
|
||||
elif s[0] == 'A':
|
||||
# added
|
||||
modified[k] = [ (None, None), (k, rev) ]
|
||||
else:
|
||||
# modified or merge conflict
|
||||
modified[k] = [ (k, prev), (k, rev) ]
|
||||
# sort the results
|
||||
return [ modified[k] for k in sorted(modified.keys()) ]
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
return self._getCommitTemplate(prefs, names, [ 'log', '--template', 'A\t{file_adds}\nM\t{file_mods}\nR\t{file_dels}\n', '-r', rev ], rev)
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
return self._getCommitTemplate(prefs, names, [ 'status', '-q' ], None)
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
prefs.getString('hg_bin'),
|
||||
'cat',
|
||||
'-r',
|
||||
rev,
|
||||
utils.safeRelativePath(self.root, name, prefs, 'hg_cygwin')
|
||||
],
|
||||
prefs,
|
||||
'hg_bash')
|
|
@ -0,0 +1,211 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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
|
||||
import shlex
|
||||
|
||||
from diffuse import utils
|
||||
from diffuse.vcs.folder_set import FolderSet
|
||||
from diffuse.vcs.vcs_interface import VcsInterface
|
||||
|
||||
# Monotone support
|
||||
class Mtn(VcsInterface):
|
||||
def getFileTemplate(self, prefs, name):
|
||||
# FIXME: merge conflicts?
|
||||
return [ (name, 'h:'), (name, None) ]
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
# build command
|
||||
vcs_bin = prefs.getString('mtn_bin')
|
||||
ss = utils.popenReadLines(self.root, [ vcs_bin, 'automate', 'select', '-q', rev ], prefs, 'mtn_bash')
|
||||
if len(ss) != 1:
|
||||
raise IOError('Ambiguous revision specifier')
|
||||
args = [ vcs_bin, 'automate', 'get_revision', ss[0] ]
|
||||
# build list of interesting files
|
||||
fs = FolderSet(names)
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
# run command
|
||||
prev = None
|
||||
removed, added, modified, renamed = {}, {}, {}, {}
|
||||
ss = utils.popenReadLines(self.root, args, prefs, 'mtn_bash')
|
||||
i = 0
|
||||
while i < len(ss):
|
||||
# process results
|
||||
s = shlex.split(ss[i])
|
||||
i += 1
|
||||
if len(s) < 2:
|
||||
continue
|
||||
arg, arg1 = s[0], s[1]
|
||||
if arg == 'old_revision' and len(arg1) > 2:
|
||||
if prev is not None:
|
||||
break
|
||||
prev = arg1[1:-1]
|
||||
continue
|
||||
elif prev is None:
|
||||
continue
|
||||
if arg == 'delete':
|
||||
# deleted file
|
||||
k = os.path.join(self.root, prefs.convertToNativePath(arg1))
|
||||
if fs.contains(k):
|
||||
removed[arg1] = k
|
||||
elif arg == 'add_file':
|
||||
# new file
|
||||
k = os.path.join(self.root, prefs.convertToNativePath(arg1))
|
||||
if fs.contains(k):
|
||||
added[arg1] = k
|
||||
elif arg == 'patch':
|
||||
# modified file
|
||||
k = os.path.join(self.root, prefs.convertToNativePath(arg1))
|
||||
if fs.contains(k):
|
||||
modified[arg1] = k
|
||||
elif arg == 'rename':
|
||||
s = shlex.split(ss[i])
|
||||
i += 1
|
||||
if len(s) > 1 and s[0] == 'to':
|
||||
# renamed file
|
||||
k0 = os.path.join(self.root, prefs.convertToNativePath(arg1))
|
||||
k1 = os.path.join(self.root, prefs.convertToNativePath(s[1]))
|
||||
if fs.contains(k0) or fs.contains(k1):
|
||||
renamed[s[1]] = (arg1, k0, k1)
|
||||
if removed or renamed:
|
||||
# remove directories
|
||||
removed_dirs = set()
|
||||
for s in utils.popenReadLines(self.root, [ vcs_bin, 'automate', 'get_manifest_of', prev ], prefs, 'mtn_bash'):
|
||||
s = shlex.split(s)
|
||||
if len(s) > 1 and s[0] == 'dir':
|
||||
removed_dirs.add(s[1])
|
||||
for k in removed_dirs:
|
||||
for m in removed, modified:
|
||||
if k in m:
|
||||
del m[k]
|
||||
for k, v in renamed.items():
|
||||
arg1, k0, k1 = v
|
||||
if arg1 in removed_dirs:
|
||||
del renamed[k]
|
||||
# sort results
|
||||
result, r = [], set()
|
||||
for m in removed, added, modified, renamed:
|
||||
r.update(m)
|
||||
for k in sorted(r):
|
||||
if k in removed:
|
||||
k = removed[k]
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
result.append([ (k, prev), (None, None) ])
|
||||
elif k in added:
|
||||
k = added[k]
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
result.append([ (None, None), (k, rev) ])
|
||||
else:
|
||||
if k in renamed:
|
||||
arg1, k0, k1 = renamed[k]
|
||||
else:
|
||||
k0 = k1 = modified[k]
|
||||
if not isabs:
|
||||
k0 = utils.relpath(pwd, k0)
|
||||
k1 = utils.relpath(pwd, k1)
|
||||
result.append([ (k0, prev), (k1, rev) ])
|
||||
return result
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
fs = FolderSet(names)
|
||||
result = []
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
args = [ prefs.getString('mtn_bin'), 'automate', 'inventory', '--no-ignored', '--no-unchanged', '--no-unknown' ]
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
# build list of interesting files
|
||||
prev = 'h:'
|
||||
ss = utils.popenReadLines(self.root, args, prefs, 'mtn_bash')
|
||||
removed, added, modified, renamed = {}, {}, {}, {}
|
||||
i = 0
|
||||
while i < len(ss):
|
||||
# parse properties
|
||||
m = {}
|
||||
while i < len(ss):
|
||||
s = ss[i]
|
||||
i += 1
|
||||
# properties are terminated by a blank line
|
||||
s = shlex.split(s)
|
||||
if len(s) == 0:
|
||||
break
|
||||
m[s[0]] = s[1:]
|
||||
# scan the list of properties for files that interest us
|
||||
if len(m.get('path', [])) > 0:
|
||||
p, s, processed = m['path'][0], m.get('status', []), False
|
||||
if 'dropped' in s and 'file' in m.get('old_type', []):
|
||||
# deleted file
|
||||
k = os.path.join(self.root, prefs.convertToNativePath(p))
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
removed[k] = [ (k, prev), (None, None) ]
|
||||
processed = True
|
||||
if 'added' in s and 'file' in m.get('new_type', []):
|
||||
# new file
|
||||
k = os.path.join(self.root, prefs.convertToNativePath(p))
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
added[k] = [ (None, None), (k, None) ]
|
||||
processed = True
|
||||
if 'rename_target' in s and 'file' in m.get('new_type', []) and len(m.get('old_path', [])) > 0:
|
||||
# renamed file
|
||||
k0 = os.path.join(self.root, prefs.convertToNativePath(m['old_path'][0]))
|
||||
k1 = os.path.join(self.root, prefs.convertToNativePath(p))
|
||||
if fs.contains(k0) or fs.contains(k1):
|
||||
if not isabs:
|
||||
k0 = utils.relpath(pwd, k0)
|
||||
k1 = utils.relpath(pwd, k1)
|
||||
renamed[k1] = [ (k0, prev), (k1, None) ]
|
||||
processed = True
|
||||
if not processed and 'file' in m.get('fs_type', []):
|
||||
# modified file or merge conflict
|
||||
k = os.path.join(self.root, prefs.convertToNativePath(p))
|
||||
if fs.contains(k):
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
modified[k] = [ (k, prev), (k, None) ]
|
||||
# sort the results
|
||||
r = set()
|
||||
for m in removed, added, modified, renamed:
|
||||
r.update(m.keys())
|
||||
for k in sorted(r):
|
||||
for m in removed, added, modified, renamed:
|
||||
if k in m:
|
||||
result.append(m[k])
|
||||
return result
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
prefs.getString('mtn_bin'),
|
||||
'automate',
|
||||
'get_file_of',
|
||||
'-q',
|
||||
'-r',
|
||||
rev,
|
||||
utils.safeRelativePath(self.root, name, prefs, 'mtn_cygwin')
|
||||
],
|
||||
prefs,
|
||||
'mtn_bash')
|
|
@ -0,0 +1,129 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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.vcs_interface import VcsInterface
|
||||
|
||||
# RCS support
|
||||
class Rcs(VcsInterface):
|
||||
def getFileTemplate(self, prefs, name):
|
||||
args = [ prefs.getString('rcs_bin_rlog'), '-L', '-h', utils.safeRelativePath(self.root, name, prefs, 'rcs_cygwin') ]
|
||||
rev = ''
|
||||
for line in utils.popenReadLines(self.root, args, prefs, 'rcs_bash'):
|
||||
if line.startswith('head: '):
|
||||
rev = line[6:]
|
||||
return [ (name, rev), (name, None) ]
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
result = []
|
||||
try:
|
||||
r, prev = rev.split('.'), None
|
||||
if len(r) > 1:
|
||||
m = int(r.pop())
|
||||
if m > 1:
|
||||
r.append(str(m - 1))
|
||||
else:
|
||||
m = int(r.pop())
|
||||
if len(r):
|
||||
prev = '.'.join(r)
|
||||
for k in sorted(names):
|
||||
if prev is None:
|
||||
k0 = None
|
||||
else:
|
||||
k0 = k
|
||||
result.append([ (k0, prev), (k, rev) ])
|
||||
except ValueError:
|
||||
utils.logError(_('Error parsing revision %s.') % (rev, ))
|
||||
return result
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
# build command
|
||||
cmd = [ prefs.getString('rcs_bin_rlog'), '-L', '-h' ]
|
||||
# build list of interesting files
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
r = []
|
||||
for k in names:
|
||||
if os.path.isdir(k):
|
||||
# the user specified a folder
|
||||
n, ex = [ k ], True
|
||||
while len(n) > 0:
|
||||
s = n.pop()
|
||||
recurse = os.path.isdir(os.path.join(s, 'RCS'))
|
||||
if ex or recurse:
|
||||
ex = False
|
||||
for v in os.listdir(s):
|
||||
dn = os.path.join(s, v)
|
||||
if v.endswith(',v') and os.path.isfile(dn):
|
||||
# map to checkout name
|
||||
r.append(dn[:-2])
|
||||
elif v == 'RCS' and os.path.isdir(dn):
|
||||
for v in os.listdir(dn):
|
||||
if os.path.isfile(os.path.join(dn, v)):
|
||||
if v.endswith(',v'):
|
||||
v = v[:-2]
|
||||
r.append(os.path.join(s, v))
|
||||
elif recurse and os.path.isdir(dn) and not os.path.islink(dn):
|
||||
n.append(dn)
|
||||
else:
|
||||
# the user specified a file
|
||||
s = k + ',v'
|
||||
if os.path.isfile(s):
|
||||
r.append(k)
|
||||
continue
|
||||
s = k.split(os.sep)
|
||||
s.insert(-1, 'RCS')
|
||||
# old-style RCS repository
|
||||
if os.path.isfile(os.sep.join(s)):
|
||||
r.append(k)
|
||||
continue
|
||||
# new-style RCS repository
|
||||
s[-1] += ',v'
|
||||
if os.path.isfile(os.sep.join(s)):
|
||||
r.append(k)
|
||||
for k in r:
|
||||
isabs |= os.path.isabs(k)
|
||||
args = [ utils.safeRelativePath(self.root, k, prefs, 'rcs_cygwin') for k in r ]
|
||||
# run command
|
||||
r, k = {}, ''
|
||||
for line in utils.popenXArgsReadLines(self.root, cmd, args, prefs, 'rcs_bash'):
|
||||
# parse response
|
||||
if line.startswith('Working file: '):
|
||||
k = prefs.convertToNativePath(line[14:])
|
||||
k = os.path.join(self.root, os.path.normpath(k))
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
elif line.startswith('head: '):
|
||||
r[k] = line[6:]
|
||||
# sort the results
|
||||
return [ [ (k, r[k]), (k, None) ] for k in sorted(r.keys()) ]
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
prefs.getString('rcs_bin_co'),
|
||||
'-p',
|
||||
'-q',
|
||||
'-r' + rev,
|
||||
utils.safeRelativePath(self.root, name, prefs, 'rcs_cygwin')
|
||||
],
|
||||
prefs,
|
||||
'rcs_bash')
|
|
@ -0,0 +1,58 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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
|
||||
import glob
|
||||
|
||||
from diffuse import utils
|
||||
from diffuse.vcs.svn import Svn
|
||||
|
||||
class Svk(Svn):
|
||||
def _getVcs(self):
|
||||
return 'svk'
|
||||
|
||||
def _getURLPrefix(self):
|
||||
return 'Depot Path: '
|
||||
|
||||
def _parseStatusLine(self, s):
|
||||
if len(s) < 4 or s[0] not in 'ACDMR':
|
||||
return
|
||||
return s[0], s[4:]
|
||||
|
||||
def _getPreviousRevision(self, rev):
|
||||
if rev is None:
|
||||
return 'HEAD'
|
||||
if rev.endswith('@'):
|
||||
return str(int(rev[:-1]) - 1) + '@'
|
||||
return str(int(rev) - 1)
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
prefs.getString('svk_bin'),
|
||||
'cat',
|
||||
'-r',
|
||||
rev,
|
||||
'{}/{}'.format(
|
||||
self._getURL(prefs),
|
||||
utils.relpath(self.root, os.path.abspath(name)).replace(os.sep, '/'))
|
||||
],
|
||||
prefs,
|
||||
'svk_bash')
|
|
@ -0,0 +1,257 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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
|
||||
import glob
|
||||
|
||||
from diffuse import utils
|
||||
from diffuse.vcs.folder_set import FolderSet
|
||||
from diffuse.vcs.vcs_interface import VcsInterface
|
||||
|
||||
# Subversion support
|
||||
# SVK support subclasses from this
|
||||
class Svn(VcsInterface):
|
||||
def __init__(self, root):
|
||||
VcsInterface.__init__(self, root)
|
||||
self.url = None
|
||||
|
||||
def _getVcs(self):
|
||||
return 'svn'
|
||||
|
||||
def _getURLPrefix(self):
|
||||
return 'URL: '
|
||||
|
||||
def _parseStatusLine(self, s):
|
||||
if len(s) < 8 or s[0] not in 'ACDMR':
|
||||
return
|
||||
# subversion 1.6 adds a new column
|
||||
k = 7
|
||||
if k < len(s) and s[k] == ' ':
|
||||
k += 1
|
||||
return s[0], s[k:]
|
||||
|
||||
def _getPreviousRevision(self, rev):
|
||||
if rev is None:
|
||||
return 'BASE'
|
||||
m = int(rev)
|
||||
if m > 1:
|
||||
return str(m - 1)
|
||||
|
||||
def _getURL(self, prefs):
|
||||
if self.url is None:
|
||||
vcs, prefix = self._getVcs(), self._getURLPrefix()
|
||||
n = len(prefix)
|
||||
args = [ prefs.getString(vcs + '_bin'), 'info' ]
|
||||
for s in utils.popenReadLines(self.root, args, prefs, vcs + '_bash'):
|
||||
if s.startswith(prefix):
|
||||
self.url = s[n:]
|
||||
break
|
||||
return self.url
|
||||
|
||||
def getFileTemplate(self, prefs, name):
|
||||
# FIXME: verify this
|
||||
# merge conflict
|
||||
escaped_name = utils.globEscape(name)
|
||||
left = glob.glob(escaped_name + '.merge-left.r*')
|
||||
right = glob.glob(escaped_name + '.merge-right.r*')
|
||||
if len(left) > 0 and len(right) > 0:
|
||||
return [ (left[-1], None), (name, None), (right[-1], None) ]
|
||||
# update conflict
|
||||
left = sorted(glob.glob(escaped_name + '.r*'))
|
||||
right = glob.glob(escaped_name + '.mine')
|
||||
right.extend(glob.glob(escaped_name + '.working'))
|
||||
if len(left) > 0 and len(right) > 0:
|
||||
return [ (left[-1], None), (name, None), (right[0], None) ]
|
||||
# default case
|
||||
return [ (name, self._getPreviousRevision(None)), (name, None) ]
|
||||
|
||||
def _getCommitTemplate(self, prefs, rev, names):
|
||||
result = []
|
||||
try:
|
||||
prev = self._getPreviousRevision(rev)
|
||||
except ValueError:
|
||||
utils.logError(_('Error parsing revision %s.') % (rev, ))
|
||||
return result
|
||||
|
||||
# build command
|
||||
vcs = self._getVcs()
|
||||
vcs_bin, vcs_bash = prefs.getString(vcs + '_bin'), vcs + '_bash'
|
||||
if rev is None:
|
||||
args = [ vcs_bin, 'status', '-q' ]
|
||||
else:
|
||||
args = [ vcs_bin, 'diff', '--summarize', '-c', rev ]
|
||||
# build list of interesting files
|
||||
pwd, isabs = os.path.abspath(os.curdir), False
|
||||
for name in names:
|
||||
isabs |= os.path.isabs(name)
|
||||
if rev is None:
|
||||
args.append(utils.safeRelativePath(self.root, name, prefs, vcs + '_cygwin'))
|
||||
# run command
|
||||
fs = FolderSet(names)
|
||||
modified, added, removed = {}, set(), set()
|
||||
for s in utils.popenReadLines(self.root, args, prefs, vcs_bash):
|
||||
status = self._parseStatusLine(s)
|
||||
if status is None:
|
||||
continue
|
||||
v, k = status
|
||||
rel = prefs.convertToNativePath(k)
|
||||
k = os.path.join(self.root, rel)
|
||||
if fs.contains(k):
|
||||
if v == 'D':
|
||||
# deleted file or directory
|
||||
# the contents of deleted folders are not reported
|
||||
# by "svn diff --summarize -c <rev>"
|
||||
removed.add(rel)
|
||||
elif v == 'A':
|
||||
# new file or directory
|
||||
added.add(rel)
|
||||
elif v == 'M':
|
||||
# modified file or merge conflict
|
||||
k = os.path.join(self.root, k)
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
modified[k] = [ (k, prev), (k, rev) ]
|
||||
elif v == 'C':
|
||||
# merge conflict
|
||||
modified[k] = self.getFileTemplate(prefs, k)
|
||||
elif v == 'R':
|
||||
# replaced file
|
||||
removed.add(rel)
|
||||
added.add(rel)
|
||||
# look for files in the added items
|
||||
if rev is None:
|
||||
m, added = added, {}
|
||||
for k in m:
|
||||
if not os.path.isdir(k):
|
||||
# confirmed as added file
|
||||
k = os.path.join(self.root, k)
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
added[k] = [ (None, None), (k, None) ]
|
||||
else:
|
||||
m = {}
|
||||
for k in added:
|
||||
d, b = os.path.dirname(k), os.path.basename(k)
|
||||
if d not in m:
|
||||
m[d] = set()
|
||||
m[d].add(b)
|
||||
# remove items we can easily determine to be directories
|
||||
for k in m.keys():
|
||||
d = os.path.dirname(k)
|
||||
if d in m:
|
||||
m[d].discard(os.path.basename(k))
|
||||
if not m[d]:
|
||||
del m[d]
|
||||
# determine which are directories
|
||||
added = {}
|
||||
for p, v in m.items():
|
||||
for s in utils.popenReadLines(self.root, [ vcs_bin, 'list', '-r', rev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
|
||||
if s in v:
|
||||
# confirmed as added file
|
||||
k = os.path.join(self.root, os.path.join(p, s))
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
added[k] = [ (None, None), (k, rev) ]
|
||||
# determine if removed items are files or directories
|
||||
if prev == 'BASE':
|
||||
m, removed = removed, {}
|
||||
for k in m:
|
||||
if not os.path.isdir(k):
|
||||
# confirmed item as file
|
||||
k = os.path.join(self.root, k)
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
removed[k] = [ (k, prev), (None, None) ]
|
||||
else:
|
||||
m = {}
|
||||
for k in removed:
|
||||
d, b = os.path.dirname(k), os.path.basename(k)
|
||||
if d not in m:
|
||||
m[d] = set()
|
||||
m[d].add(b)
|
||||
removed_dir, removed = set(), {}
|
||||
for p, v in m.items():
|
||||
for s in utils.popenReadLines(self.root, [ vcs_bin, 'list', '-r', prev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
|
||||
if s.endswith('/'):
|
||||
s = s[:-1]
|
||||
if s in v:
|
||||
# confirmed item as directory
|
||||
removed_dir.add(os.path.join(p, s))
|
||||
else:
|
||||
if s in v:
|
||||
# confirmed item as file
|
||||
k = os.path.join(self.root, os.path.join(p, s))
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
removed[k] = [ (k, prev), (None, None) ]
|
||||
# recursively find all unreported removed files
|
||||
while removed_dir:
|
||||
tmp = removed_dir
|
||||
removed_dir = set()
|
||||
for p in tmp:
|
||||
for s in utils.popenReadLines(self.root, [ vcs_bin, 'list', '-r', prev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
|
||||
if s.endswith('/'):
|
||||
# confirmed item as directory
|
||||
removed_dir.add(os.path.join(p, s[:-1]))
|
||||
else:
|
||||
# confirmed item as file
|
||||
k = os.path.join(self.root, os.path.join(p, s))
|
||||
if not isabs:
|
||||
k = utils.relpath(pwd, k)
|
||||
removed[k] = [ (k, prev), (None, None) ]
|
||||
# sort the results
|
||||
r = set()
|
||||
for m in removed, added, modified:
|
||||
r.update(m.keys())
|
||||
for k in sorted(r):
|
||||
for m in removed, added, modified:
|
||||
if k in m:
|
||||
result.append(m[k])
|
||||
return result
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
return self._getCommitTemplate(prefs, rev, names)
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
return self._getCommitTemplate(prefs, None, names)
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
vcs_bin = prefs.getString('svn_bin')
|
||||
if rev in [ 'BASE', 'COMMITTED', 'PREV' ]:
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
vcs_bin,
|
||||
'cat',
|
||||
'{}@{}'.format(utils.safeRelativePath(self.root, name, prefs, 'svn_cygwin'), rev)
|
||||
],
|
||||
prefs,
|
||||
'svn_bash')
|
||||
return utils.popenRead(
|
||||
self.root,
|
||||
[
|
||||
vcs_bin,
|
||||
'cat',
|
||||
'{}/{}@{}'.format(
|
||||
self._getURL(prefs),
|
||||
utils.relpath(self.root, os.path.abspath(name)).replace(os.sep, '/'),
|
||||
rev)
|
||||
],
|
||||
prefs,
|
||||
'svn_bash')
|
|
@ -0,0 +1,39 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
class VcsInterface:
|
||||
def __init__(self, root):
|
||||
"""The object will initialized with the repository's root folder."""
|
||||
|
||||
self.root = root
|
||||
|
||||
def getFileTemplate(self, prefs, name):
|
||||
"""Indicates which revisions to display for a file when none were explicitly requested."""
|
||||
pass
|
||||
|
||||
def getCommitTemplate(self, prefs, rev, names):
|
||||
"""Indicates which file revisions to display for a commit."""
|
||||
pass
|
||||
|
||||
def getFolderTemplate(self, prefs, names):
|
||||
"""Indicates which file revisions to display for a set of folders."""
|
||||
|
||||
def getRevision(self, prefs, name, rev):
|
||||
"""Returns the contents of the specified file revision"""
|
||||
pass
|
|
@ -0,0 +1,216 @@
|
|||
# Diffuse: a graphical tool for merging and comparing text files.
|
||||
#
|
||||
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||
#
|
||||
# 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, ))
|
Loading…
Reference in New Issue