Convert Svn VCS
This commit is contained in:
parent
f314f46309
commit
5d6ece84e1
242
src/main.py
242
src/main.py
|
@ -61,6 +61,7 @@ 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.svn import Svn
|
||||
|
||||
if not hasattr(__builtins__, 'WindowsError'):
|
||||
# define 'WindowsError' so 'except' statements will work on all platforms
|
||||
|
@ -70,11 +71,6 @@ if not hasattr(__builtins__, 'WindowsError'):
|
|||
# this is sorted based upon frequency to speed up code for stripping whitespace
|
||||
whitespace = ' \t\n\r\x0b\x0c'
|
||||
|
||||
# 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 ])
|
||||
|
||||
# colour resources
|
||||
class Colour:
|
||||
def __init__(self, r, g, b, a=1.0):
|
||||
|
@ -495,7 +491,7 @@ class Resources:
|
|||
if args[0] == 'import' and len(args) == 2:
|
||||
path = os.path.expanduser(args[1])
|
||||
# relative paths are relative to the parsed file
|
||||
path = os.path.join(globEscape(os.path.dirname(file_name)), path)
|
||||
path = os.path.join(utils.globEscape(os.path.dirname(file_name)), path)
|
||||
paths = glob.glob(path)
|
||||
if len(paths) == 0:
|
||||
paths = [ path ]
|
||||
|
@ -1290,242 +1286,10 @@ def _get_rcs_repo(path, prefs):
|
|||
# the user specified an invalid folder name
|
||||
pass
|
||||
|
||||
# Subversion support
|
||||
# SVK support subclasses from this
|
||||
class _Svn:
|
||||
def __init__(self, root):
|
||||
self.root = 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 = 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')
|
||||
|
||||
def _get_svn_repo(path, prefs):
|
||||
p = _find_parent_dir_with(path, '.svn')
|
||||
if p:
|
||||
return _Svn(p)
|
||||
return Svn(p)
|
||||
|
||||
class _Svk(_Svn):
|
||||
def __init__(self, root):
|
||||
|
|
|
@ -166,6 +166,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,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')
|
Loading…
Reference in New Issue