Commits

ZyX_I committed de7a81f

Renamed @aurum/drivers/svn to @aurum/drivers/subversion
doc: Fixed aurum://commit documentation

  • Participants
  • Parent commits 10d78e7

Comments (0)

Files changed (3)

File doc/aurum.txt

 
     Note These mappings are defined for aurumannotate filetype.
 
-aurum://commit:{repo}:{user}:{date}:{files}                   *aurum://commit*
+aurum://commit:{repo}:{user}:{date}:{closebranch}:{files}     *aurum://commit*
     Open buffer that prompts for a commit message. Writing this buffer will 
     record the commit and close the buffer. It is the only pseudo-protocol 
     type you can’t use this with |:read|.

File plugin/aurum/drivers/subversion.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',
+            \  'r2fst': 'Second revision was found before the first',
+        \}
+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': {},
+            \'tags': [],
+            \'bookmarks': [],
+        \}
+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
+    let line+=1
+    "▲2
+    return [cs, line]
+endfunction
+"▶1 getchangesets :: repo → [cs]
+function s:F.getchangesets(repo, ...)
+    let args=[]
+    let kwargs={'xml': 1}
+    if a:0==1
+        if type(a:1)==type(0)
+            let kwargs.limit=''.a:1
+        else
+            let kwargs.revision=a:1
+        endif
+    elseif a:0==2
+        let kwargs.revision=a:1.':'.a:2
+    endif
+    let log=s:F.svn(a:repo, 'log', args, kwargs, 0, 'logf')[2:-3]
+    let cslist=[]
+    let llog=len(log)
+    let line=0
+    while line<llog
+        let [cs, line]=s:F.parsecs(log, line)
+        call insert(a:repo.cslist, cs)
+        let a:repo.changesets[cs.hex]=cs
+    endwhile
+    return cslist
+endfunction
+"▶1 svn.getchangesets :: repo → [cs]
+function s:svn.getchangesets(repo)
+    if empty(a:repo.cslist)
+        let cslist=s:F.getchangesets(a:repo)
+        call map(cslist, 'extend(v:val, {"parents": '.
+                    \'(v:key ? [cslist[v:key-1]] : [])})')
+        " TODO children
+        let a:repo.cslist+=cslist
+    else
+        call a:repo.functions.updatechangesets(a:repo)
+    endif
+    return a:repo.cslist
+endfunction
+"▶1 svn.updatechangesets :: repo → _
+function s:svn.updatechangesets(repo)
+    let oldtiphex=a:repo.cslist[-1].hex
+    let tiphex=a:repo.functions.gettiphex(a:repo)
+    if tiphex<oldtiphex
+        call remove(a:repo.cslist, +tiphex, -1)
+    elseif tiphex>oldtiphex
+        let cslist=a:repo.functions.revrange(a:repo, oldtiphex, tiphex)
+        call map(cslist, 'extend(v:val, {"parents": '.
+                    \'(v:key ? [cslist[v:key-1]] : [oldtiphex])})')
+        " TODO children
+        let a:repo.cslist+=cslist
+    endif
+endfunction
+"▶1 svn.revrange :: repo, rev1, rev2 → [cs]
+function s:svn.revrange(repo, rev1, rev2)
+    if a:rev1 is 0 && a:rev2 is -1
+        return a:repo.functions.getchangesets(a:repo)
+    elseif a:rev2 is -1 && type(a:rev1)==type(0) && a:rev1<0
+        if empty(a:repo.cslist)
+            return s:F.getchangesets(a:repo, -a:rev1)
+        else
+            return a:repo.functions.getchangesets(a:repo)[(a:rev1):]
+        endif
+    elseif a:rev1 is 0
+        if type(a:rev2)==type(0)
+            return a:repo.functions.getchangesets(a:repo)[:(a:rev2)]
+        elseif empty(a:repo.cslist)
+            return s:F.getchangesets(a:repo, a:rev2)
+        else
+            call a:repo.functions.updatechangesets(a:repo)
+            let rev2=a:repo.functions.getrevhex(a:repo, a:rev2)
+            let i=len(a:repo.cslist)-1
+            while i>=0 && a:repo.cslist[i].hex isnot# rev2
+                let i-=1
+            endwhile
+            return a:repo.cslist[:(i)]
+        endif
+    elseif empty(a:repo.cslist)
+        return s:F.getchangesets(a:repo, a:rev1, a:rev2)
+    else
+        call a:repo.functions.updatechangesets(a:repo)
+        let rev1=a:repo.functions.getrevhex(a:repo, a:rev1)
+        let rev2=a:repo.functions.getrevhex(a:repo, a:rev2)
+        let i=len(a:repo.cslist)-1
+        while i>=0 && a:repo.cslist[i].hex isnot# rev1
+            if a:repo.cslist[i].hex is# rev2
+                let r2i=i
+            endif
+            let i-=1
+        endwhile
+        if !exists('r2i')
+            call s:_f.throw('r2fst', a:rev2, a:rev1)
+        endif
+        return a:repo.cslist[(i):(r2i)]
+    endif
+endfunction
+"▶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=▶,▲

File 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',
-            \  'r2fst': 'Second revision was found before the first',
-        \}
-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': {},
-            \'tags': [],
-            \'bookmarks': [],
-        \}
-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
-    let line+=1
-    "▲2
-    return [cs, line]
-endfunction
-"▶1 getchangesets :: repo → [cs]
-function s:F.getchangesets(repo, ...)
-    let args=[]
-    let kwargs={'xml': 1}
-    if a:0==1
-        if type(a:1)==type(0)
-            let kwargs.limit=''.a:1
-        else
-            let kwargs.revision=a:1
-        endif
-    elseif a:0==2
-        let kwargs.revision=a:1.':'.a:2
-    endif
-    let log=s:F.svn(a:repo, 'log', args, kwargs, 0, 'logf')[2:-3]
-    let cslist=[]
-    let llog=len(log)
-    let line=0
-    while line<llog
-        let [cs, line]=s:F.parsecs(log, line)
-        call insert(a:repo.cslist, cs)
-        let a:repo.changesets[cs.hex]=cs
-    endwhile
-    return cslist
-endfunction
-"▶1 svn.getchangesets :: repo → [cs]
-function s:svn.getchangesets(repo)
-    if empty(a:repo.cslist)
-        let cslist=s:F.getchangesets(a:repo)
-        call map(cslist, 'extend(v:val, {"parents": '.
-                    \'(v:key ? [cslist[v:key-1]] : [])})')
-        " TODO children
-        let a:repo.cslist+=cslist
-    else
-        call a:repo.functions.updatechangesets(a:repo)
-    endif
-    return a:repo.cslist
-endfunction
-"▶1 svn.updatechangesets :: repo → _
-function s:svn.updatechangesets(repo)
-    let oldtiphex=a:repo.cslist[-1].hex
-    let tiphex=a:repo.functions.gettiphex(a:repo)
-    if tiphex<oldtiphex
-        call remove(a:repo.cslist, +tiphex, -1)
-    elseif tiphex>oldtiphex
-        let cslist=a:repo.functions.revrange(a:repo, oldtiphex, tiphex)
-        call map(cslist, 'extend(v:val, {"parents": '.
-                    \'(v:key ? [cslist[v:key-1]] : [oldtiphex])})')
-        " TODO children
-        let a:repo.cslist+=cslist
-    endif
-endfunction
-"▶1 svn.revrange :: repo, rev1, rev2 → [cs]
-function s:svn.revrange(repo, rev1, rev2)
-    if a:rev1 is 0 && a:rev2 is -1
-        return a:repo.functions.getchangesets(a:repo)
-    elseif a:rev2 is -1 && type(a:rev1)==type(0) && a:rev1<0
-        if empty(a:repo.cslist)
-            return s:F.getchangesets(a:repo, -a:rev1)
-        else
-            return a:repo.functions.getchangesets(a:repo)[(a:rev1):]
-        endif
-    elseif a:rev1 is 0
-        if type(a:rev2)==type(0)
-            return a:repo.functions.getchangesets(a:repo)[:(a:rev2)]
-        elseif empty(a:repo.cslist)
-            return s:F.getchangesets(a:repo, a:rev2)
-        else
-            call a:repo.functions.updatechangesets(a:repo)
-            let rev2=a:repo.functions.getrevhex(a:repo, a:rev2)
-            let i=len(a:repo.cslist)-1
-            while i>=0 && a:repo.cslist[i].hex isnot# rev2
-                let i-=1
-            endwhile
-            return a:repo.cslist[:(i)]
-        endif
-    elseif empty(a:repo.cslist)
-        return s:F.getchangesets(a:repo, a:rev1, a:rev2)
-    else
-        call a:repo.functions.updatechangesets(a:repo)
-        let rev1=a:repo.functions.getrevhex(a:repo, a:rev1)
-        let rev2=a:repo.functions.getrevhex(a:repo, a:rev2)
-        let i=len(a:repo.cslist)-1
-        while i>=0 && a:repo.cslist[i].hex isnot# rev1
-            if a:repo.cslist[i].hex is# rev2
-                let r2i=i
-            endif
-            let i-=1
-        endwhile
-        if !exists('r2i')
-            call s:_f.throw('r2fst', a:rev2, a:rev1)
-        endif
-        return a:repo.cslist[(i):(r2i)]
-    endif
-endfunction
-"▶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=▶,▲