Commits

ZyX_I committed f76bbcd

@aurum/drivers: Added inital svn implementation (untested, some functions are definitely not working)

  • Participants
  • Parent commits 042a09a

Comments (0)

Files changed (7)

 syntax: glob
 python/*.pyc
 test/dummy.in
+test/*repo
+test/svntestreposerver
 
-This plugin provides a vim <--> VCS (currently mercurial and git) integration 
-for your projects. Features:
+This plugin provides a vim <--> VCS (currently mercurial, git and subversion) 
+integration for your projects. Features:
 
-  - Partially committing changes ([:AuRecord](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line350-0)).
+  - Partially committing changes ([:AuRecord](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line353-0)).
 
-  - Viewing file state at particular revision ([aurum://file](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line581-0), [:AuFile](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line155-0)).
+  - Viewing file state at particular revision ([aurum://file](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line584-0), [:AuFile](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line158-0)).
 
   - Viewing uncommited changes in a vimdiff, as well as changes between 
-    specific revisions ([:AuVimDiff](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line390-0)). It is also possible to open multiple 
+    specific revisions ([:AuVimDiff](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line393-0)). It is also possible to open multiple 
     tabs with all changes to all files viewed as side-by-side diffs.
 
-  - Viewing revisions log ([:AuLog](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line236-0)). Output is highly customizable.
+  - Viewing revisions log ([:AuLog](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line239-0)). Output is highly customizable.
 
-  - Viewing working directory status ([:AuStatus](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line354-0)).
+  - Viewing working directory status ([:AuStatus](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line357-0)).
 
-  - Commiting changes ([:AuCommit](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line96-0)), commit messages are remembered in case of 
-    rollback ([g:aurum_remembermsg](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line826-0)).
+  - Commiting changes ([:AuCommit](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line99-0)), commit messages are remembered in case of 
+    rollback ([g:aurum_remembermsg](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line829-0)).
 
   - Obtaining various URL’s out of remote repository URL (like URL of the HTML 
     version of the current file with URL fragment pointing to the current line 
-    attached: useful for sharing) ([:AuHyperlink](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line186-0)).
+    attached: useful for sharing) ([:AuHyperlink](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line189-0)).
 
-  - [aurum#changeset()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line438-0), [aurum#repository()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line434-0) and [aurum#status()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line442-0) functions 
+  - [aurum#changeset()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line441-0), [aurum#repository()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line437-0) and [aurum#status()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line445-0) functions 
     that are to be used from modeline.
 
   - Frontends for various other VCS commands.
 
-Most commands can be reached with a set of mappings (see [aurum-mappings](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line723-0)), 
+Most commands can be reached with a set of mappings (see [aurum-mappings](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line726-0)), 
 all mappings are customizable.
 
 
 *aurum.txt* Plugin for dealing with source files under various VCS control
 ==============================================================================
 CONTENTS                                                      *aurum-contents*
-    1. Intro                                             |aurum-intro|
-    2. Functionality provided                            |aurum-functionality|
-        2.1. Commands                                    |aurum-commands|
-        2.2. Functions                                   |aurum-functions|
-        2.3. aurum:// pseudo-protocol                    |aurum://|
-        2.4. Global mappings                             |aurum-mappings|
-    4. Options                                           |aurum-options|
-    5. Globs                                             |aurum-globs|
-    6. Log templates                                     |aurum-logtemplates|
-        6.1. Built-in template styles                    |aurum-logstyles|
-    7. Record mode                                       |aurum-record|
-    8. Internal objects                                  |aurum-objects|
-        8.1. Repository                                  |aurum-repository|
-        8.2. Changeset                                   |aurum-changeset|
-    9. Creating your own driver                          |aurum-new-driver|
-    10. Notes about various driver implementations       |aurum-driver-notes|
-    11. Changelog                                        |aurum-changelog|
+    1. Intro                                         |aurum-intro|
+    2. Functionality provided                        |aurum-functionality|
+        2.1. Commands                                |aurum-commands|
+        2.2. Functions                               |aurum-functions|
+        2.3. aurum:// pseudo-protocol                |aurum://|
+        2.4. Global mappings                         |aurum-mappings|
+    4. Options                                       |aurum-options|
+    5. Globs                                         |aurum-globs|
+    6. Log templates                                 |aurum-logtemplates|
+        6.1. Built-in template styles                |aurum-logstyles|
+    7. Record mode                                   |aurum-record|
+    8. Internal objects                              |aurum-objects|
+        8.1. Repository                              |aurum-repository|
+        8.2. Changeset                               |aurum-changeset|
+    9. Creating your own driver                      |aurum-new-driver|
+    10. Notes about various driver implementations   |aurum-driver-notes|
+        10.1. Mercurial                              |aurum-driver-Mercurial|
+        10.2. Git                                    |aurum-driver-Git|
+        10.3. Subversion                             |aurum-driver-Subversion|
+    11. Changelog                                    |aurum-changelog|
 
 ==============================================================================
 1. Intro                                                         *aurum-intro*
 
-This plugin provides a vim <--> VCS (currently mercurial and git) integration 
-for your projects. Features:
+This plugin provides a vim <--> VCS (currently mercurial, git and subversion) 
+integration for your projects. Features:
   - Partially committing changes (|:AuRecord|).
   - Viewing file state at particular revision (|aurum://file|, |:AuFile|).
   - Viewing uncommited changes in a |vimdiff|, as well as changes between 
 ==============================================================================
 10. Notes about various driver implementations            *aurum-driver-notes*
 
-Mercurial:
+------------------------------------------------------------------------------
+10.1. Mercurial                                       *aurum-driver-Mercurial*
 
 |aurum-rf-label|:
     Supported labels: tag (non-local/local), bookmark (non-local).
     results.
 Python driver was not tested under windows (wine) at all.
 
-Git:
+------------------------------------------------------------------------------
+10.2. Git                                                   *aurum-driver-Git*
 
 |aurum-rf-label|: Supported labels: tag, branch (both non-local).
 
 |aurum-rf-annotate| won’t output annotation of file in working directory, it 
     is always returning annotation of file at given revision.
 
+------------------------------------------------------------------------------
+10.3. Subversion                                     *aurum-driver-Subversion*
+
+|aurum-rf-label|, |aurum-rf-branch| are not supported.
+
+|aurum-cs.branch| always contains string "default".
+|aurum-cs.rev| and |aurum-cs.hex| have the same value, though second one is 
+    a string.
+|aurum-cs.renames| and |aurum-cs.copies| are always empty.
+|aurum-cs.time| is always 0 if “date” programm from coreutils is missing.
+|aurum-rf-diff|: Options "git", "iblanks", "dates", "alltext" are not 
+    supported, option "reverse" is supported only if two revisions are given.
+
 ==============================================================================
 11. Changelog                                                *aurum-changelog*
 

plugin/aurum/drivers/svn.vim

+"▶1
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.1', {   '@aurum/repo': '1.0',
+                \                          '@/os': '0.0',
+                \   '@aurum/drivers/common/utils': '0.0',
+                \'@aurum/drivers/common/hypsites': '0.0',}, 0)
+    finish
+elseif s:_pluginloaded
+    finish
+endif
+let s:_messages={
+            \  'rnimp': 'Reversing diff between working directory and '.
+            \           'any revision is not implemented',
+            \  'animp': 'Unable to specify commit author',
+            \  'dnimp': 'Unable to specify commit date',
+            \ 'cbnimp': 'Subversion driver is not able to close branch',
+            \  'difff': 'Failed to get diff between %s and %s for files %s '.
+            \           'in the repository %s: %s',
+            \    'csf': 'Failed to obtain information about revision %s '.
+            \           'in the repository %s: %s',
+            \    'cif': 'Failed to commit changes to the repository %s: %s',
+            \   'updf': 'Failed to update to revision %s '.
+            \           'in the repository %s: %s',
+            \    'mvf': 'Failed to move file %s to %s in the repository %s: %s',
+            \    'cpf': 'Failed to copy file %s to %s in the repository %s: %s',
+            \    'rmf': 'Failed to remove file %s in the repository %s: %s',
+            \    'fgf': 'Failed to forget file %s in the repository %s: %s',
+            \  'filef': 'Failed to get revision %s of the file %s '.
+            \           'from the repository %s: %s',
+            \   'annf': 'Failet to annotate revision %s of the file %s '.
+            \           'in the repository %s: %s',
+            \   'ignf': 'Failed to ignore %s in the repository %s: %s',
+            \  'irevf': 'Failed to get information about revision %s '.
+            \           'from the repository %s: %s',
+            \  'infof': 'Failed to get information about repository %s: %s',
+            \   'addf': 'Failed to add file %s to the repository %s: %s',
+            \  'listf': 'Failed to list file of revision %s '.
+            \           'in the repository %s: %s',
+            \   'logf': 'Failed to get log of the repository %s: %s',
+            \  'nocfg': 'Failed to get property %s of repository %s',
+            \'fcunsup': 'Forced copy is not supported',
+            \   'perr': 'Parser error: expected %s, but got %s',
+            \   'derr': 'Program “date” failed to parse date “%s” obtained '.
+            \           'by parsing “%s”: %s',
+            \'infoerr': 'Failed to get information for the repository %s: '.
+            \           'there are no lines starting with “URL: ” or '.
+            \           '“Repository Root: ” in the output of “svn info”',
+            \'ireverr': 'Failed to get information about revision %s '.
+            \           'from the repository %s: there are no lines starting '.
+            \           'with “Revision: ” in the output of “svn info”',
+            \  'ndate': 'You must install “date” programm in order to get '.
+            \           'time information for Subversion revisions',
+        \}
+let s:svn={}
+"▶1 s:hypsites
+let s:hypsites=s:_r.hypsites.svn
+"▶1 svncmd :: repo, cmd, args, kwargs, esc → String
+function s:F.svncmd(repo, ...)
+    return 'svn '.call(s:_r.utils.getcmd, a:000, {})
+endfunction
+"▶1 svn :: repo, cmd, args, kwargs, has0[, msgid[, marg1[, …]]] → [String] + ?
+function s:F.svn(repo, cmd, args, kwargs, hasnulls, ...)
+    let cmd=s:F.svncmd(a:repo, a:cmd, a:args, a:kwargs, a:hasnulls)
+    let r=s:_r.utils.run(cmd, a:hasnulls, a:repo.path)
+    if v:shell_error && a:0
+        call call(s:_f.throw, a:000+[a:repo.path, join(r[:-1-(a:hasnulls)],
+                    \                                  "\n")], {})
+    endif
+    return r
+endfunction
+"▶1 svnm :: {svn args} → + :echom
+function s:F.svnm(...)
+    return s:_r.utils.printm(call(s:F.svn, a:000, {}))
+endfunction
+"▶1 svn.getrevhex :: repo, rev → hex
+function s:svn.getrevhex(repo, rev)
+    let info=s:F.svn(a:repo, 'info', [], {'revision': ''.a:rev}, 0,
+                \    'irevf', a:rev)
+    for line in info
+        if line[:9] is# 'Revision: '
+            return line[10:]
+        endif
+    endfor
+    call s:_f.throw('ireverr', a:rev, a:repo.path)
+endfunction
+"▶1 decodeentities :: String → String
+let s:entities={
+            \  'lt': '<',
+            \  'gt': '>',
+            \ 'amp': '&',
+            \'quot': '"',
+        \}
+function s:F.decodeentities(s)
+    return substitute(a:s, '\v\&([lg]t|amp)\;', '\=s:entities[submatch(1)]','g')
+endfunction
+"▶1 parsecs :: csdata, lstart::UInt → (cs, line::UInt)
+let s:logseparator=repeat('-', 72)
+let s:csinit={
+            \'branch': 'default',
+            \'renames': {},
+            \'copies': {},
+        \}
+let s:hasdateexe=executable('date')
+" TODO HEAD, ... in cs.tags
+function s:F.parsecs(csdata, line)
+    let cs=deepcopy(s:csinit)
+    "▶2 Check for logentry start
+    let line=a:line
+    if a:csdata[line][:8] isnot# '<logentry'
+        call s:_f.throw('perr', '<logentry', a:csdata[line])
+    endif
+    let line+=1
+    "▶2 rev and hex
+    let rev=matchstr(a:csdata[line], '\v(\srevision\=\")@<=(\d+)\"@=')
+    if empty(rev)
+        call s:_f.throw('perr', 'revision="N"', a:csdata[line])
+    endif
+    let cs.rev=str2nr(rev)
+    let cs.hex=rev
+    let line+=1
+    "▶2 author
+    let author=matchstr(a:csdata[line], '\v(\<author\>)@<=.*(\<\/author\>)@=')
+    if empty(author)
+        call s:_f.throw('perr', '<author>name</author>', a:csdata[line])
+    endif
+    let cs.user=s:F.decodeentities(author)
+    let line+=1
+    "▶2 date
+    " date :: (year, month, day, hour, minute, second)
+    if s:hasdateexe || executable('date')
+        let s:hasdateexe=1
+        let date=matchlist(a:csdata[line], '\V<date>'.
+                    \                        '\v(\d{4,})\-(\d\d)\-(\d\d)'.
+                    \                         'T(\d\d)\:(\d\d)\:(\d\d)\.\d+Z'.
+                    \                      '\V</date>')[1:6]
+        if empty(date)
+            call s:_f.throw('perr', '<date>yyyy-mm-ddTHH:MM:SS.nnnnnnZ</date>',
+                        \   a:csdata[line])
+        endif
+        let [y, mon, d, h, min, s]=date
+        let datearg=y.'-'.mon.'-'.d.' '.h.':'.min.':'.s.' UTC'
+        let time=system('date --date='.shellescape(datearg).' +%s')
+        if v:shell_error
+            call s:_f.throw('derr', datearg, a:csdata[line], time)
+        else
+            let cs.time=str2nr(time)
+        endif
+    else
+        call s:_f.warn('ndate')
+        let cs.time=0
+    endif
+    let line+=1
+    "▶2 description
+    let idx=stridx(a:csdata[line], '<msg>')
+    if idx==-1
+        call s:_f.throw('perr', '<msg>...', a:csdata[line])
+    endif
+    let idx+=5
+    let description=a:csdata[line][(idx):]
+    let line+=1
+    let idx=stridx(description, '</msg>')
+    if idx==-1
+        let cs.description=s:F.decodeentities(description)
+        let lcsdata=len(a:csdata)
+        while line<lcsdata
+            let idx=stridx(a:csdata[line], '</msg>')
+            if idx==-1
+                let cs.description.="\n".s:F.decodeentities(a:csdata[line])
+            else
+                if idx
+                    let cs.description.="\n".a:csdata[line][:(idx-1)]
+                endif
+                break
+            endif
+            let line+=1
+        endwhile
+        let line+=1
+    else
+        let cs.description=s:F.decodeentities(description[:(idx-1)])
+    endif
+    "▶2 check for </logentry>
+    if a:csdata[line] isnot# '</logentry>'
+        call s:_f.throw('perr', '</logentry>', a:csdata[line])
+    endif
+    "▲2
+    return [cs, line]
+endfunction
+"▶1 svn.getchangesets :: repo → []
+function s:svn.getchangesets(repo, ...)
+    let args=[]
+    let kwargs={}
+    let log=s:F.svn(a:repo, 'log', args, kwargs, 0, 'logf')
+    let cslist=[]
+    let llog=len(log)
+    let line=0
+    while i<llog
+        let [cs, line]=s:F.parsecs(log, line)
+        let cslist+=[cs]
+        let a:repo.changesets[cs.hex]=cs
+    endwhile
+    if !a:0
+        let a:repo.cslist=copy(cslist)
+    endif
+    return cslist
+endfunction
+"▶1 svn.updatechangesets :: repo → _
+let s:svn.updatechangesets=s:svn.getchangesets
+"▶1 svn.revrange :: repo, rev1, rev2 → [cs]
+let s:svn.revrange=s:svn.getchangesets
+"▶1 svn.getcs :: repo, rev → cs
+function s:svn.getcs(repo, rev)
+    let cs=s:F.parsecs(s:F.svn(a:repo, 'log', [],
+                \              {'revision': a:rev, 'limit': '1', 'xml': 1},
+                \              0, 'csf', a:rev), 2)[0]
+    " XXX This construct is used to preserve information like “allfiles” etc
+    let a:repo.changesets[cs.hex]=extend(get(a:repo.changesets, cs.hex, {}), cs)
+    return a:repo.changesets[cs.hex]
+endfunction
+"▶1 svn.getwork :: repo → cs
+function s:svn.getwork(repo)
+    return a:repo.functions.getcs(a:repo, 'BASE')
+endfunction
+"▶1 svn.getworkhex :: repo → hex
+function s:svn.getworkhex(repo)
+    return a:repo.functions.getrevhex(a:repo, 'BASE')
+endfunction
+"▶1 svn.gettiphex
+function s:svn.gettiphex(repo)
+    return a:repo.functions.getrevhex(a:repo, 'HEAD')
+endfunction
+"▶1 svn.setcsprop :: repo, cs, propname → propvalue
+function s:svn.setcsprop(repo, cs, prop)
+    if a:prop is# 'allfiles'
+        let r=s:F.svn(a:repo, 'list', ['.'], {'recursive': 1,
+                    \                         'revision': cs.hex}, 0,
+                    \ 'listf', cs.hex)
+    elseif a:prop is# 'children'
+        let lastrev=+(a:repo.functions.gettiphex(a:repo))
+        return ((a:cs.rev<lastrev)?([string(a:cs.rev+1)]):([]))
+    elseif  a:prop is# 'changes' || a:prop is# 'files' || a:prop is# 'removes'
+    endif
+    let a:cs[a:prop]=r
+    return r
+endfunction
+"▶1 svn.readfile :: repo, rev, file → [String]
+function s:svn.readfile(repo, rev, file)
+    return s:F.svn(a:repo, 'cat', ['--', a:file], {'revision': a:rev}, 1,
+                \  'filef', a:rev, a:file)
+endfunction
+"▶1 svn.diff :: repo, rev, rev, files, opts → [String]
+let s:difftrans={
+            \ 'numlines': 'unified',
+            \ 'ignorews': 'ignore-all-space',
+            \'iwsamount': 'ignore-space-change',
+            \ 'showfunc': 'show-c-function',
+        \}
+function s:svn.diff(repo, rev1, rev2, files, opts)
+    let diffopts=s:_r.utils.diffopts(a:opts, a:repo.diffopts, s:difftrans)
+    if has_key(diffopts, 'unified')
+        let diffopts.unified=''.diffopts.unified
+    endif
+    if get(a:opts, 'reverse', 0)
+        let reverse=1
+    endif
+    let kwargs=copy(diffopts)
+    let args=[]
+    if empty(a:rev2)
+        if !empty(a:rev1)
+            if reverse
+                let kwargs.revision=a:rev1.':'.(a:rev1-1)
+            else
+                let kwargs.change=''.a:rev1
+            endif
+        elseif reverse
+            call s:_f.throw('rnimp')
+        endif
+    else
+        let kwargs.revision=''.a:rev2
+        if !empty(a:rev1)
+            if reverse
+                let kwargs.revision=a:rev1.':'.kwargs.revision
+            else
+                let kwargs.revision.=':'.a:rev1
+            endif
+        elseif reverse
+            call s:_f.throw('rnimp')
+        endif
+    endif
+    if !empty(a:files)
+        let args+=['--']+a:files
+    endif
+    let r=s:F.svn(a:repo, 'diff', args, kwargs, 1,
+                \ 'difff', a:rev1, a:rev2, join(a:files, ', '))
+    return r
+endfunction
+"▶1 svn.diffre :: _, opts → Regex
+function s:svn.diffre(repo, opts)
+    return '\m^Index: \v(.*)'
+endfunction
+"▶1 nullnl :: [String] → [String]
+" Convert between lines (NL separated strings with NULLs represented as NLs) and 
+" NULL separated strings with NLs represented by NLs.
+function s:F.nullnl(text)
+    let r=[]
+    for line in a:text
+        let nlsplit=split(line, "\n", 1)
+        if empty(r)
+            call extend(r, nlsplit)
+        else
+            let r[-1].="\n".nlsplit[0]
+            call extend(r, nlsplit[1:])
+        endif
+    endfor
+    return r
+endfunction
+"▶1 svn.status :: repo[, rev1[, rev2[, files]]]
+function s:svn.status(repo, ...)
+    if a:0 && (a:1 isnot 0 || (a:0>1 && a:2 isnot 0))
+    else
+    endif
+    return r
+endfunction
+"▶1 svn.annotate :: repo, rev, file → [(file, hex, linenumber)]
+function s:svn.annotate(repo, rev, file)
+    let args=['--', a:file]
+    let kwargs={'revision': ''.a:rev}
+    let lines=s:F.svn(a:repo, 'blame', args, kwargs, 1, 'annf', a:rev, a:file)
+    let r=[]
+    return r
+endfunction
+"▶1 svn.commit :: repo, message[, files[, user[, date[, _]]]]
+function s:svn.commit(repo, message, ...)
+    let kwargs={}
+    let usingfile=0
+    if a:message=~#'\v[\r\n]'
+        let tmpfile=tempname()
+        call writefile(split(a:message, "\n", 1), tmpfile, 'b')
+        let kwargs.file=tmpfile
+        let usingfile=1
+    else
+        let kwargs.message=a:message
+    endif
+    let args=[]
+    if a:0
+        if !empty(a:1)
+            let args+=['--']+a:1
+            for file in filter(copy(a:1),
+                        \      'filereadable(s:_r.os.path.join(a:repo.path, '.
+                        \                                     'v:val))')
+                call a:repo.functions.add(a:repo, file)
+            endfor
+        endif
+        if a:0>1 && !empty(a:2)
+            call s:_f.throw('animp')
+        elseif a:0>2 && !empty(a:3)
+            call s:_f.throw('dnimp')
+        elseif a:0>3 && !empty(a:4)
+            call s:_f.throw('cbnimp')
+        endif
+    endif
+    try
+        return s:F.svnm(a:repo, 'commit', args, kwargs, 0, 'cif')
+    finally
+        if usingfile && filereadable(tmpfile)
+            call delete(tmpfile)
+        endif
+    endtry
+endfunction
+"▶1 svn.update :: repo, rev, force → + FS
+function s:svn.update(repo, rev, force)
+    let kwargs={}
+    if a:force
+        let kwargs.force=1
+    endif
+    let kwargs.revision=a:rev
+    return s:F.svnm(a:repo, 'update', [], kwargs, 0, 'updf', a:rev)
+endfunction
+"▶1 svn.move :: repo, force, source, destination → + FS
+function s:svn.move(repo, force, source, destination)
+    return s:F.svnm(a:repo, 'move', ['--', a:source, a:destination],
+                \   a:force ? {'force': 1} : {}, 0, 'mvf',
+                \   a:source, a:destination)
+endfunction
+"▶1 svn.copy :: repo, force, source, destination → + FS
+function s:svn.copy(repo, force, source, destination)
+    if a:force
+        call s:_f.throw('fcunsup')
+    endif
+    return s:F.svnm(a:repo, 'copy', ['--', a:source, a:destination], {}, 0,
+                \   'cpf', a:source, a:destination)
+endfunction
+"▶1 svn.remove :: repo, file → + FS
+function s:svn.remove(repo, file)
+    return s:F.svnm(a:repo, 'delete', ['--', a:file], {}, 0, 'rmf', a:file)
+endfunction
+"▶1 svn.forget :: repo, file → + FS
+function s:svn.forget(repo, file)
+    return s:F.svnm(a:repo, 'delete', ['--',a:file], {'keep-local':1}, 0, 'fgf',
+                \   a:file)
+endfunction
+"▶1 svn.add :: repo, file → + FS
+function s:svn.add(repo, file)
+    return s:F.svnm(a:repo, 'add', ['--', a:file], {}, 0, 'addf', a:file)
+endfunction
+"▶1 svn.ignore :: repo, file → + FS
+function s:svn.ignore(repo, file)
+    return a:repo.functions.ignoreglob(a:repo, escape(a:file, '\*?[]'))
+endfunction
+"▶1 svn.ignoreglob :: repo, glob → + FS
+function s:svn.ignoreglob(repo, glob)
+    " XXX Check ignoring in subdirectories
+    return s:F.svnm(a:repo, 'propset', ['svn:ignore', a:glob, '.'], {}, 0,
+                \   'ignf', a:glob)
+endfunction
+"▶1 svn.getrepoprop :: repo, propname → a
+function s:svn.getrepoprop(repo, prop)
+    if a:prop is# 'url'
+        let info=s:F.svn(a:repo, 'info', [], {}, 0, 'infof')
+        for line in info
+            if line[:4] is# 'URL: '
+                return line[5:]
+            elseif line[:16] is# 'Repository Root: '
+                return line[17:]
+            endif
+        endfor
+        call s:_f.throw('infoerr', a:repo.path)
+    elseif a:prop is# 'tagslist'
+        return ['HEAD', 'BASE', 'COMMITTED', 'PREV']
+    elseif a:prop[-4:] is# 'list'
+        return []
+    endif
+    call s:_f.throw('nocfg', a:prop, a:repo.path)
+endfunction
+"▶1 svn.repo :: path → repo
+function s:svn.repo(path)
+    let repo={'path': a:path, 'changesets': {}, 'cslist': [],
+                \'local': (stridx(a:path, '://')==-1),
+                \'labeltypes': [], 'hasrevisions': 1,
+                \'requires_sort': 0, 'has_octopus_merges': 0,
+                \'has_merges': 0,}
+    return repo
+endfunction
+"▶1 svn.checkdir :: dir → Bool
+function s:svn.checkdir(dir)
+    return s:_r.os.path.isdir(s:_r.os.path.join(a:dir, '.svn'))
+endfunction
+"▶1 Register driver
+call s:_f.regdriver('Subversion', s:svn)
+"▶1
+call frawor#Lockvar(s:, '_pluginloaded,hasdateexe')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

test/createsvnrepo.zsh

+#!/bin/zsh
+export TZ=UTC
+svnadmin create svntestreposerver
+svn checkout file://$PWD/svntestreposerver svntestrepo
+cd svntestrepo
+
+> file-under-svn-control << EOF
+First line
+Second line
+Third line
+Fourth line
+Fifth line
+Sixth line
+EOF
+svn add file-under-svn-control
+svn commit --message 'Added file-under-svn-control'
+mkdir subdir
+svn add subdir
+svn commit --message 'Commit where only a directory added'
+echo File that looks like a “svn add” key > --force
+svn add -- --force
+svn commit --message 'Added --force'
+echo Seventh line >> file-under-svn-control
+echo Abc > def
+echo Def > ghi
+echo Ghi > mno
+mkdir subdir2
+echo Mno > subdir/pqr
+echo Pqr > subdir2/stu
+svn add -- def ghi mno subdir2 subdir/pqr
+svn commit --message "\
+Added seventh line to file-under-svn-control
+Added def, ghi, mno
+Added subdir/pqr
+Added subdir2, subdir2/stu"
+perl -p -i -e 's/Fourth/4’th/' file-under-svn-control
+svn commit -m 'Replaced “Fourth” with “4’th”'
+
+cd ..
+tar -cJvf svntestreposerver.tar.xz svntestreposerver

test/gentests-setuptestrepos.zsh

 tar xJf gittestrepo.tar.xz
 tar xJf gitteststatusrepo.tar.xz
 
+tar xJf svntestreposerver.tar.xz
+svn checkout file://$PWD/svntestreposerver svntestrepo
+
 for test in annotate-buffers commit record stat statmaps vimdiff diff update \
             prevmessage vimdiff-usewin vimdiff-full
 do
 hg clone testrepo testrepo-cloned
 for test in *.in ; do
     [[ $test == git.in ]] && continue
+    [[ $test == svn.in ]] && continue
     test=$test:r
     [[ -d ${test}repo ]] && continue
     mkdir ${test}repo

test/svntestreposerver.tar.xz

Binary file added.