Commits

ZyX_I committed 47ba2f7 Merge

Merge: subversion driver is stable enough to be in default

Comments (0)

Files changed (22)

 
-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#line356-0)).
+  - Partially committing changes ([:AuRecord](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line357-0)).
 
-  - Viewing file state at particular revision ([aurum://file](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line598-0), [:AuFile](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line157-0)).
+  - Viewing file state at particular revision ([aurum://file](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line599-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#line399-0)). It is also possible to open multiple 
+    specific revisions ([:AuVimDiff](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line400-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#line238-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#line360-0)).
+  - Viewing working directory status ([:AuStatus](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line361-0)).
 
-  - Commiting changes ([:AuCommit](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line98-0)), commit messages are remembered in case of 
-    rollback ([g:aurum_remembermsg](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line869-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#line870-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#line188-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#line447-0), [aurum#repository()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line443-0) and [aurum#status()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line451-0) functions 
+  - [aurum#changeset()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line448-0), [aurum#repository()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line444-0) and [aurum#status()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line452-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#line753-0)), 
+Most commands can be reached with a set of mappings (see [aurum-mappings](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line754-0)), 
 all mappings are customizable.
 
 
     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 
           (revision, filename (relative to repository root)). Second is to be 
           transformed into |aurum://file| path.
                                                              *aurum-rf-status*
-  status :: [hex[, hex[, [ file ]]]] -> {status : [ files ]}
+  status :: [hex[, hex[, [ file ][, requiresclean]]]] -> {status : [ file ]}
     Returns dictionary where values are lists of files and keys are 
     "modified", "added" (new tracked files), "removed" (made untracked, not 
     necessary deleted from filesystem), "deleted" (just deleted from 
     filesystem without making files untracked), "unknown" (new untracked 
     files), "ignored" and "clean" (unmodified).
+    Specifying 0 instead of any optional argument acts like not specifying 
+    this argument.
+    Note: “clean” may be empty unless {requiresclean} argument is present and 
+          true.
   dirty :: file -> Bool                                       *aurum-rf-dirty*
     Check whether file is “dirty” (meaning that it has changes that can be 
     committed).
     anything supported by |aurum-rf-getcs|, {file} is a filename (in case this 
     function is able to track copies or renames) and {line-number} is number 
     of the corresponding line in that file as it was at that revision.
+    If {line-number} is 0, then |aurum://annotate| mappings will use line 
+    contents to search for the same line in another revision.
   diff :: hex, hex, [ file ], diffopts -> [ String ]           *aurum-rf-diff*
     Returns list of strings (similar to |readfile()| output with "b" flag set) 
     that contains changes made to given files between given revisions. If list 
 <   . Second is called once on paths that contain “://” in their string (it is 
     likely to return just 0 because normal work with remote repositories is 
     impossible in most VCS’es).
+  getroot :: path -> path|0                                 *aurum-rf-getroot*
+    Being given a path for which |aurum-rf-checkdir| returns true should 
+    return a repository root.
+    If absent acts like the below function, thus considering any directory for 
+    which |aurum-rf-checkdir| returns true being a repository root >
+        function s:driver.getroot(path)
+            return a:path
+        endfunction
 
 ------------------------------------------------------------------------------
 8.2. Changeset                                               *aurum-changeset*
   |aurum-rf-remove|        Uses |aurum-rf-forget| to untrack file and then 
                          |delete()|s it.
   |aurum-rf-checkremote|   Returns 0.
+  |aurum-rf-grep|          Does just the same what git grep does: iterates 
+                         over given revisions and searches for given regular 
+                         expression (in this case: vim |regexp|) in each file. 
+                         If third argument is empty, then searches working 
+                         directory versions of files present in current 
+                         changeset. Uses |aurum-rf-getcsprop| (with “allfiles” 
+                         argument), |aurum-rf-revrange|, |aurum-rf-getcs| and 
+                         |aurum-rf-readfile|.
+                         Slow!
   |aurum-repo.iterfuncs|   "changeset": Uses |aurum-rf-getchangesets|
                          "revrange": Uses |aurum-rf-revrange|
                          "ancestors": Iterates recursively over 
     just tags.
 |aurum-cs.rev| contains truncated hash (for use in |aurum://annotate|).
 |aurum-repo.cslist| is always empty
-|aurum-rf-status|: If two first optional arguments are given and are non-zero, 
-    then unknown and deleted files won’t be shown (if any).
+|aurum-rf-status|: if at least one of first two optional arguments is 
+    non-zero, then then unknown and deleted files won’t be shown (if any).
 |aurum-rf-gettiphex|: Same as |aurum-rf-getworkhex|.
 |aurum-rf-diff|: Options "git", "iblanks", "showfunc", "dates" are not 
     supported.
 |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.time| is always 0 if “date” programm from coreutils is missing.
+|aurum-cs.hex| contains the stringified value of |aurum-cs.rev| and thus is 
+    not fixed-length.
+|aurum-repo.cslist|: repo.cslist[cs.rev] is any revision with .rev≥cs.rev, not 
+    necessary cs itself.
+|aurum-rf-diff|: Options "git", "iblanks", "dates", "alltext" are not 
+    supported, option "reverse" is supported only if two revisions are given.
+|aurum-rf-status|: if at least one of first two optional arguments is 
+    non-zero, then then unknown and deleted files won’t be shown (if any).
+    Note: this function may output directories in a list.
+|aurum-rf-annotate| neither tracks renames/copies nor is able to output line 
+    numbers.
+|aurum-rf-grep| uses internal grep implementation based on 
+    |aurum-rf-readfile|. Thus it is slow, but supports vim |regexp|.
+    See more in |aurum-f-regdriver|.
+|aurum-rf-ignoreglob| uses “svn propset svn:ignore” and thus is limited to 
+    non-recursive globs applying to exactly one directory: ignoring 
+    “subdir*/ignored*” will ignore all files with names starting with 
+    “ignored” in a directory “subdir*”, which is probably not what you 
+    initially wanted.
+
 ==============================================================================
 11. Changelog                                                *aurum-changelog*
 
          value of |aurum-rf-getchangesets| is not stated as ignored.
     2.1: Added |aurum-repo.iterfuncs| support.
     2.2: Added |aurum-repo.initprops|.
+    2.3: Added |aurum-rf-getroot| support.
+    2.4: Added {requiresclean} argument to |aurum-rf-status|.
 @aurum:
     0.1: Added :AuBranch and :AuName.
 @aurum/edit:

ftplugin/aurumannotate.vim

     let bvar=s:_r.bufvars[buf]
     let hex=bvar.revisions[line('.')-1]
     let file=bvar.files[line('.')-1]
-    let hasannbuf = has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
+    let hasannbuf=has_key(bvar, 'annbuf')
+    if hasannbuf
+        let abwnr=bufwinnr(bvar.annbuf)
+        let hasannbuf=(abwnr!=-1)
+    endif
     "▶2 Various *diff actions
     if a:action[-4:] is# 'diff'
         if a:action[:2] is# 'rev'
         let rev2=hex
         if hasannbuf
             wincmd c
-            execute bufwinnr(bvar.annbuf).'wincmd w'
+            execute abwnr.'wincmd w'
             setlocal noscrollbind
         endif
         if a:action[-7:-5] is# 'vim'
         if a:0 && a:1
             let file=bvar.files[line('.')-1]
             let lnr=bvar.linenumbers[line('.')-1]
+            if lnr is 0
+                unlet lnr
+            endif
         else
             let file=s:F.getfile(bvar.repo,
                         \        bvar.repo.functions.getcs(bvar.repo, hex))
         endif
         if hasannbuf
             let lnr=bvar.linenumbers[line('.')-1]
+            if lnr is 0
+                let ablnr=line('.')
+                execute abwnr.'wincmd w'
+                let line=getline(ablnr)
+                unlet lnr
+                wincmd p
+            endif
             call s:_r.run('silent edit', 'annotate', bvar.repo, hex, file)
-            execute lnr
+            if exists('lnr')
+                execute lnr
+            endif
             setlocal scrollbind cursorbind
             let abuf=bufnr('%')
             let newbvar=s:_r.bufvars[abuf]
-            execute bufwinnr(bvar.annbuf).'wincmd w'
+            execute abwnr.'wincmd w'
         endif
         let existed=s:_r.run('silent edit', 'file', bvar.repo, hex, file)
         if exists('lnr')
             execute lnr
+        elseif exists('line')
+            0
+            call search('\V\^'.escape(line, '\').'\$', 'cW')
+            let lnr=line('.')
+            wincmd p
+            execute lnr
+            wincmd p
         endif
         if hasannbuf
             if exists('lnr')
         let abuf=bufnr('%')
         let newbvar=s:_r.bufvars[abuf]
         if hasannbuf
-            execute bufwinnr(bvar.annbuf).'wincmd w'
+            execute abwnr.'wincmd w'
             setlocal noscrollbind
         else
             vsplit
         call s:_r.cmdutils.checkrepo(repo)
         let file=s:F.urlescape(file)
         if rev is 0
-            if has_key(opts, 'line') &&
-                        \index(repo.functions.status(repo).clean, file)==-1
+            if has_key(opts, 'line') && repo.functions.dirty(repo, file)
                 call remove(opts, 'line')
             endif
             let cs=repo.functions.getwork(repo)

plugin/aurum/annotate.vim

     if rev is 0
         let rev=repo.functions.getworkhex(repo)
     endif
+    if hasannbuf==2
+        let hasannbuf=!repo.functions.dirty(repo, file)
+    endif
     if hasannbuf
         let annbuf=bufnr('%')
     else

plugin/aurum/cmdutils.vim

             let file=repo.functions.reltorepo(repo, expand('%'))
         endif
         let  rev=0
-        let hasbuf=1
+        let hasbuf=2
     "▲2
     elseif a:failmsg isnot 0
         call s:_f.throw(a:failmsg)

plugin/aurum/drivers/common/utils.vim

File contents unchanged.

plugin/aurum/drivers/git.vim

     endfor
     return r
 endfunction
-"▶1 git.status :: repo[, rev1[, rev2[, files]]]
+"▶1 git.status :: repo[, rev1[, rev2[, files[, clean]]]]
 let s:statchars={
             \'A': 'added',
             \'M': 'modified',
         \}
 function s:git.status(repo, ...)
     let r=deepcopy(s:initstatdct)
+    let requiresclean=(a:0>3 && a:4)
     if a:0 && (a:1 isnot 0 || (a:0>1 && a:2 isnot 0))
         let args=((a:0>2 && !empty(a:3))?(['--']+a:3):([]))
         let rspec=[]
             let [r.deleted, r.unknown]=[r.unknown, r.deleted]
             let [r.added,   r.removed]=[r.removed, r.added  ]
         endif
-        let allfiles=a:repo.functions.getcsprop(a:repo, rspec[0], 'allfiles')
-        if a:0>2 && !empty(a:3)
-            let allfiles=filter(copy(allfiles), 'index(a:3, v:val)!=-1')
+        if requiresclean
+            let allfiles=a:repo.functions.getcsprop(a:repo,rspec[0],'allfiles')
         endif
-        let r.clean=filter(copy(allfiles), 'index(files, v:val)==-1')
     else
         let args=((a:0>2 && !empty(a:3))?(['--']+a:3):([]))
         let kwargs={'porcelain': 1, 'z': 1}
-        let s=s:F.nullnl(s:F.git(a:repo, 'status', args, kwargs, 1, 'statusf'))
-        let files=[]
+        let s=s:F.nullnl(s:F.git(a:repo,'status',args,kwargs,1,'statusf'))[:-2]
+        let files={}
         while !empty(s)
             let line=remove(s, 0)
             let status=line[:1]
             let file=line[3:]
-            call add(files, file)
+            let files[file]=1
             if status[0] is# 'R'
                 let r.added+=[file]
                 let r.removed+=[remove(s, 0)]
                 let r.unknown+=[file]
             endif
         endwhile
-        let allfiles=a:repo.functions.getcsprop(a:repo, 'HEAD', 'allfiles')
+        if requiresclean
+            let allfiles=a:repo.functions.getcsprop(a:repo, 'HEAD', 'allfiles')
+        endif
+    endif
+    if exists('allfiles')
         if a:0>2 && !empty(a:3)
             let allfiles=filter(copy(allfiles), 'index(a:3, v:val)!=-1')
         endif
-        let r.clean=filter(copy(allfiles), 'index(files, v:val)==-1')
+        let r.clean=filter(copy(allfiles), '!has_key(files, v:val)')
     endif
     return r
 endfunction

plugin/aurum/drivers/mercurial.vim

     endtry
 endfunction
 endif
-"▶1 hg.status :: repo[, rev1[, rev2[, files]]] → {type : [file]}
+"▶1 hg.status :: repo[, rev1[, rev2[, files[, clean]]]] → {type : [file]}
 " type :: "modified" | "added" | "removed" | "deleted" | "unknown" | "ignored"
 "       | "clean"
 if s:usepythondriver "▶2
                 \'removed': 1,
                 \'deleted': 1,
                 \'unknown': 1,
-                \'ignored': 1,
-                \  'clean': 1}
+                \'ignored': 1}
+    if (a:0>3 && a:4)
+        let kwargs.clean=1
+    endif
     let reverse=0
     if a:0
         if a:1 is 0

plugin/aurum/drivers/subversion.vim

+"▶1
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.1', {   '@aurum/repo': '2.3',
+                \                          '@/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 revisions %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',
+            \  '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',
+            \  'statf': 'Failed to get status 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 “%s: ” '.
+            \           'in the output of “svn info”',
+            \  'ndate': 'You must install “date” programm in order to get '.
+            \           'time information for Subversion revisions',
+            \  'r2fst': 'Second revision (%s) was found before the first (%s)',
+            \     'u3': 'Subversion supports only three lines '.
+            \           'of unified context, you gave %u',
+            \ 'iediff': 'Diff parser error: expected at least one more line '.
+            \           'after “====<…>====” separator line',
+        \}
+let s:svn={}
+let s:iterfuncs={}
+"▶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 getfrominfo
+function s:F.getfrominfo(repo, line, ...)
+    let str=a:line.': '
+    let strlidx=len(str)-1
+    for line in s:F.svn(a:repo, 'info', [], get(a:000, 0, {}), 0, 'infof')
+        if line[:(strlidx)] is# str
+            return line[strlidx+1:]
+        endif
+    endfor
+    call s:_f.throw('infoerr', a:repo.path, a:line)
+endfunction
+"▶1 svn.getrevhex :: repo, rev → hex
+function s:svn.getrevhex(repo, rev)
+    if a:rev=~#'\v^%(0|[1-9]\d*)$'
+        return a:rev
+    endif
+    return s:F.getfrominfo(a:repo, 'Revision', {'revision': ''.a:rev})
+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 :: repo, csdata, lstart::UInt → (cs, line::UInt)
+let s:logseparator=repeat('-', 72)
+let s:csinit={
+            \'branch': 'default',
+            \'renames': {},
+            \'copies': {},
+            \'tags': [],
+            \'bookmarks': [],
+            \'status': {'added': [], 'removed': [], 'modified': []},
+        \}
+let s:logstatchars={
+            \'C': 'modified',
+            \'M': 'modified',
+            \'~': 'modified',
+            \'R': 'modified',
+            \'A': 'added',
+            \'D': 'removed',
+            \'!': 'deleted',
+            \'?': 'unknown',
+            \'I': 'ignored',
+        \}
+let s:hasdateexe=executable('date')
+" TODO HEAD, ... in cs.tags
+" TODO use merge information if available
+function s:F.parsecs(repo, csdata, line)
+    let cs=deepcopy(s:csinit)
+    let lcsdata=len(a:csdata)
+    "▶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, parents
+    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 cs.parents=((cs.rev>1)?([''.(cs.rev-1)]):([]))
+    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 paths
+    if a:csdata[line] is# '<paths>'
+        let line+=1
+        let svnplidx=len(a:repo.svnprefix)-1
+        while line<lcsdata && a:csdata[line] isnot# '</paths>'
+            if a:csdata[line][:4] isnot# '<path' "▶3
+                call s:_f.throw('perr', '<path', a:csdata[line])
+            endif                                "▲3
+            let line+=1
+            let kind=matchstr(a:csdata[line], '\v(kind\=\")@<=\w+\"@=')
+            if empty(kind) "▶3
+                call s:_f.throw('perr', 'kind="..."', a:csdata[line])
+            endif          "▲3
+            let line+=1
+            "▶3 Process copies
+            let source=matchstr(a:csdata[line],'\(copyfrom-path="/\)\@<=.*"\@=')
+            if !empty(source)
+                let line+=1
+                let sourcerev=matchstr(a:csdata[line],
+                            \'\v(copyfrom\-rev\=\")@<=\d+\"@=')
+                if empty(sourcerev)
+                    call s:_f.throw('perr', 'copyfrom-rev="N"', a:csdata[line])
+                endif
+                let line+=1
+                if svnplidx==-1 || source[:(svnplidx)] is# a:repo.svnprefix
+                    let source=source[(svnplidx+1):]
+                else
+                    let source=''
+                endif
+            endif
+            "▲3
+            let match=matchlist(a:csdata[line],
+                        \       'action="\(.\)">/\(.\+\)</path>')[1:2]
+            if empty(match) "▶3
+                call s:_f.throw('perr','action="C">/path</path>',a:csdata[line])
+            endif           "▲3
+            let [action, file]=match
+            "▶3 Add file to list(s)
+            if svnplidx==-1 || file[:(svnplidx)] is# a:repo.svnprefix
+                let file=file[(svnplidx+1):]
+                if has_key(s:logstatchars, action)
+                    let status=s:logstatchars[action]
+                    let cs.status[status]+=[file]
+                else
+                    call s:_f.throw('perr', 'action="'.
+                                \           join(keys(s:logstatchars, '/')).'"',
+                                \   action)
+                endif
+                if !empty(source)
+                    let cs.copies[file]=source
+                endif
+            endif
+            "▲3
+            let line+=1
+        endwhile
+        "▶3 Move some copies to renames
+        for [destination, source] in items(cs.copies)
+            if index(cs.status.removed, source)!=-1
+                let cs.renames[destination]=source
+                unlet cs.copies[destination]
+            endif
+        endfor
+        if line>=lcsdata "▶3
+            call s:_f.throw('perr', '</paths>', a:csdata[-1])
+        endif            "▲3
+        let line+=1
+    endif
+    let cs.files=cs.status.added+cs.status.modified
+    let cs.removes=cs.status.removed
+    let cs.changes=cs.files+cs.removes
+    "▶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)
+        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=['--', a:repo.svnroot]
+    let kwargs={'xml': 1, 'verbose': 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(a:repo, log, line)
+        let a:repo.changesets[cs.hex]=cs
+        call insert(cslist, 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)
+        let cslist[-1].children=[]
+        call map(cslist[:-2], 'extend(v:val, {"children": ["".(v:val.rev-1)]})')
+        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 oldtiprev=a:repo.cslist[-1].rev
+    let tiprev=+a:repo.functions.gettiphex(a:repo)
+    if tiprev<oldtiprev
+        while !empty(a:repo.cslist) && a:repo.cslist[-1].rev>tiprev
+            call remove(a:repo.cslist, -1)
+        endwhile
+    elseif tiprev>oldtiprev
+        let cslist=s:F.getchangesets(a:repo, ''.oldtiprev, ''.tiprev)
+        if !empty(cslist)
+            let a:repo.cslist[-1].children=''.(a:repo.cslist[-1].rev+1)
+            call map(cslist[:-2], 'extend(v:val, {"children": '.
+                        \                                '["".(v:val.rev-1)]})')
+            let a:repo.cslist+=cslist
+        endif
+    endif
+    let a:repo.cslist[-1].children=[]
+endfunction
+"▶1 svn.revrange :: repo, rev1, rev2 → [cs]
+function s:svn.revrange(repo, rev1, rev2)
+    if empty(a:repo.cslist)
+        call a:repo.functions.getchangesets(a:repo)
+    else
+        call a:repo.functions.updatechangesets(a:repo)
+    endif
+    let hex1=a:repo.functions.getrevhex(a:repo, a:rev1)
+    let hex2=a:repo.functions.getrevhex(a:repo, a:rev2)
+    let i=len(a:repo.cslist)-1
+    while i>=0 && a:repo.cslist[i].hex isnot# hex1
+        if a:repo.cslist[i].hex is# hex2
+            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)]
+endfunction
+"▶1 svn.getcs :: repo, rev → cs
+function s:svn.getcs(repo, rev)
+    "▶2 Do the best trying to get it from repo.changesets
+    if has_key(a:repo.changesets, a:rev)
+        return a:repo.changesets[a:rev]
+    else
+        let rev=a:repo.functions.getrevhex(a:repo, a:rev)
+        if has_key(a:repo.changesets, rev)
+            return a:repo.changesets[rev]
+        elseif !empty(a:repo.cslist)
+            call a:repo.functions.updatechangesets(a:repo)
+            if has_key(a:repo.changesets, rev)
+                return a:repo.changesets[rev]
+            endif
+        endif
+    endif
+    "▲2
+    let cs=s:F.parsecs(a:repo,
+                \      s:F.svn(a:repo, 'log', ['--', a:repo.svnroot],
+                \              {'revision': rev, 'limit': '1', 'xml': 1,
+                \               'verbose': 1},
+                \              0, 'csf', a:rev), 2)[0]
+    let 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 iterfuncs.ancestors
+let s:iterfuncs.ancestors={}
+function s:iterfuncs.ancestors.start(repo, opts)
+    let cslist=copy(a:repo.functions.revrange(a:repo, '1', a:opts.revision))
+    return {'cslist': cslist}
+endfunction
+function s:iterfuncs.ancestors.next(d)
+    while !empty(a:d.cslist)
+        let cs=remove(a:d.cslist, -1)
+        if !empty(cs.changes)
+            return cs
+        endif
+    endwhile
+    return 0
+endfunction
+"▶1 iterfuncs.changesets
+let s:iterfuncs.changesets={}
+function s:iterfuncs.changesets.start(repo, opts)
+    return {'cslist': copy(a:repo.functions.getchangesets(a:repo))}
+endfunction
+let s:iterfuncs.changesets.next=s:iterfuncs.ancestors.next
+"▶1 iterfuncs.revrange
+let s:iterfuncs.revrange={}
+function s:iterfuncs.revrange.start(repo, opts)
+    let cslist=copy(a:repo.functions.revrange(a:repo, a:opts.revrange[0],
+                \                                     a:opts.revrange[1]))
+    return {'cslist': cslist}
+endfunction
+let s:iterfuncs.revrange.next=s:iterfuncs.ancestors.next
+"▶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': a:cs.hex}, 0,
+                    \ 'listf', a:cs.hex)[:-2]
+        let dirs=[]
+        " FIXME Investigate whether s:_r.os.pathsep should be used here instead
+        call filter(r, 'v:val[-1:] is# "/" ? [0, add(dirs, v:val)][0] : 1')
+        let a:cs.alldirs=dirs
+    elseif a:prop is# 'children'
+        let lastrev=str2nr(a:repo.functions.gettiphex(a:repo))
+        if a:cs.rev<lastrev
+            let r=[''.(a:cs.rev+1)]
+        endif
+    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 cancache=1
+    "▶2 Get --extensions arguments
+    let diffopts=s:_r.utils.diffopts(a:opts, a:repo.diffopts, s:difftrans)
+    if has_key(diffopts, 'unified') && diffopts.unified && diffopts.unified!=3
+        call s:_f.throw('u3', diffopts.unified)
+    endif
+    let reverse=get(a:opts, 'reverse', 0)
+    let kwargs={}
+    let args=[]
+    for [k, v] in items(diffopts)
+        if v
+            let args+=['--extensions', '--'.k]
+        endif
+    endfor
+    "▶2 Get revision arguments
+    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
+                call s:_f.throw('rnimp')
+            endif
+            let cancache=0
+        else
+            if reverse
+                let kwargs.revision=a:rev1.':'.kwargs.revision
+            else
+                let kwargs.revision.=':'.a:rev1
+            endif
+        endif
+    endif
+    "▶2 Get files arguments
+    if !empty(a:files)
+        " FIXME Work with case when local repository root is not remote 
+        " repository root
+        let args+=['--']+a:files
+    endif
+    "▶2 Process cache
+    if cancache
+        let cachekey=string(args).string(sort(items(kwargs)))
+        if !has_key(a:repo, 'diffcache')
+            let a:repo.diffcache={}
+        elseif has_key(a:repo.diffcache, cachekey)
+            let cache=a:repo.diffcache[cachekey]
+            let cache.acccount+=1
+            return copy(cache.diff)
+        endif
+    endif
+    "▲2
+    let r=s:F.svn(a:repo, 'diff', args, kwargs, 1,
+                \ 'difff', a:rev1, a:rev2, join(a:files, ', '))
+    "▶2 Remove cache entries if needed
+    if cancache
+        let cache={'diff': copy(r), 'acccount': 1}
+        if len(keys(a:repo.diffcache))==50
+            let dcitems=items(a:repo.diffcache)
+            let [minacccountkey, ccache]=remove(dcitems, 0)
+            let minacccount=ccache.acccount
+            for [key, ccache] in dcitems
+                if ccache.acccount<minacccount
+                    let minacccountkey=key
+                    let minacccount=ccache.acccount
+                endif
+            endfor
+            call remove(a:repo.diffcache, minacccountkey)
+        endif
+        let a:repo.diffcache[cachekey]=cache
+    endif
+    "▲2
+    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 status :: repo, files → (status, revstatus)
+let s:statchars=[{
+            \'C': 'modified',
+            \'M': 'modified',
+            \'~': 'modified',
+            \'R': 'modified',
+            \'A': 'added',
+            \'D': 'removed',
+            \'!': 'deleted',
+            \'?': 'unknown',
+            \'I': 'ignored',}, {
+            \'C': 'modified',
+            \'M': 'modified',}]
+function s:F.status(repo, files)
+    let r=deepcopy(s:initstatdct)
+    let args=[]
+    if !empty(a:files)
+        let args+=['--']+a:files
+    endif
+    let slines=s:F.svn(a:repo, 'status', args, {}, 0, 'statf')
+    let revstatus={}
+    for line in slines
+        let status=line[:6]
+        let file=line[8:]
+        let col=0
+        for colschars in s:statchars
+            if has_key(colschars, status[col])
+                let state=colschars[status[col]]
+                let r[state]+=[file]
+                let revstatus[file]=state
+                break
+            endif
+            let col+=1
+        endfor
+    endfor
+    return [r, revstatus]
+endfunction
+"▶1 statfromdiff :: repo, diff, ofs, fs, files → (status, revstatus)
+" ofs :: [file]
+"  fs :: Either status [file]
+function s:F.statfromdiff(repo, diff, ofs, fs, files)
+    "▶2 Initialize variables
+    let diff=copy(a:diff)
+    let r=deepcopy(s:initstatdct)
+    let diffre=a:repo.functions.diffre(a:repo, {})
+    let revstatus={}
+    let file=0
+    let hasfiles=!empty(a:files)
+    let usesstatus=(type(a:fs)==type({}))
+    "▶2 Main cycle: get statuses
+    while !empty(diff)
+        let filematch=matchlist(remove(diff, 0), diffre)[1:1]
+        if empty(filematch)
+            if file is 0
+                continue
+            endif
+            "▶3 Find === separator line
+            while !empty(diff) && diff[0]!~#'[^=]'
+                call remove(diff, 0)
+            endwhile
+            if empty(diff)
+                break
+            endif
+            call remove(diff, 0)
+            if empty(diff)
+                call s:_f.throw('iediff')
+            endif
+            "▶3 Get file status
+            let binary=0
+            if diff[0][:2] isnot# '---'
+                " Binary file
+                let state='modified'
+                let binary=1
+            elseif diff[0][-12:] is# '(revision 0)'
+                let state='added'
+            else
+                let state='modified'
+            endif
+            "▶3 If file was modified check: we can’t trust this information
+            if state is# 'modified'
+                if usesstatus
+                    if index(a:ofs, file)==-1
+                        let state='added'
+                    elseif has_key(a:fs, file)
+                        let state=a:fs[file]
+                    endif
+                else
+                    " File additions are normally caught by ---.*(revision 0)?
+                    " if index(a:ofs, file)==-1
+                        " let state='added'
+                    if index(a:fs, file)==-1
+                        let state='removed'
+                    endif
+                endif
+            endif
+            "▶3 Reset values
+            let r[state]+=[file]
+            let revstatus[file]=state
+            let file=0
+            "▶3 Skip unneeded lines
+            if !binary
+                call remove(diff, 0, 1)
+                while !empty(diff) && stridx('@-+ ', diff[0][0])!=-1
+                    call remove(diff, 0)
+                endwhile
+            endif
+            "▲3
+        elseif hasfiles && index(a:files, filematch[0])==-1
+            continue
+        else
+            let file=filematch[0]
+        endif
+    endwhile
+    "▲2
+    return [r, revstatus]
+endfunction
+"▶1 statreverse :: status → revstatus
+function s:F.statreverse(status)
+    let r={}
+    call map(deepcopy(a:status),
+                \'map(v:val, "extend(r, {v:val : ''".v:key."''})")')
+    return r
+endfunction
+"▶1 svn.status :: repo[, rev1[, rev2[, files[, clean]]]]
+let s:initstatdct={
+            \'modified': [],
+            \   'added': [],
+            \ 'removed': [],
+            \ 'deleted': [],
+            \ 'unknown': [],
+            \ 'ignored': [],
+            \   'clean': [],
+        \}
+" TODO Try using diff --summarize
+function s:svn.status(repo, ...)
+    let requiresclean=(a:0>3 && a:4)
+    "▶2 Simple case: we can use “svn status”
+    if !a:0 || (a:1 is 0 && !(a:0>1 && a:2 isnot 0))
+        let [r, revstatus]=s:F.status(a:repo, get(a:000, 2, []))
+        if requiresclean
+            let allfiles=a:repo.functions.getcsprop(a:repo, 'BASE', 'allfiles')
+            if a:0>2 && !empty(a:3)
+                let allfiles=filter(copy(allfiles), 'index(a:3, v:val)!=-1')
+            endif
+        endif
+    "▶2 Not so complicated case: cs and its parent or itself
+    elseif a:0>1 && !empty(a:1) && !empty(a:2) && a:1.a:2!~#'\D' &&
+                \abs(a:1-a:2)<=1
+        let r=copy(s:initstatdct)
+        if requiresclean
+            let allfiles=copy(a:repo.functions.getcsprop(a:repo,a:1,'allfiles'))
+            if a:0>2 && !empty(a:3)
+                call filter(allfiles, 'index(a:3, v:val)!=-1')
+            endif
+        endif
+        if abs(a:1-a:2)==1
+            let reverse=(a:2<a:1)
+            let s=a:repo.functions.getcs(a:repo, ''.max([+a:1, +a:2])).status
+            if a:0>2 && !empty(a:3)
+                call map(copy(s), 'filter(copy(v:val), "index(a:3,v:val)!=-1")')
+            endif
+            let revs=s:F.statreverse(s)
+            if requiresclean
+                call filter(allfiles, '!has_key(revs, v:val)')
+            endif
+            call extend(r, s)
+            "▶3 Reversing range
+            if reverse
+                let [r.deleted, r.unknown]=[r.unknown, r.deleted]
+                let [r.added,   r.removed]=[r.removed, r.added  ]
+            endif
+            "▲3
+        endif
+        if requiresclean
+            let r.clean=allfiles
+        endif
+        return r
+    "▶2 Complicated case: diff
+    else
+        "▶3 Get diff
+        let dargs=[a:repo]
+        let reverse=0
+        let rspec=[]
+        if a:1 is 0
+            if a:0>1 && a:2 isnot 0
+                let reverse=1
+            endif
+        else
+            let rspec+=[a:1]
+        endif
+        if a:0>1 && a:2 isnot 0
+            let rspec+=[a:2]
+        endif
+        let dargs+=repeat([0], 2-len(rspec))+rspec
+        if a:0>2 && !empty(a:3)
+            let dargs+=[a:3]
+        else
+            let dargs+=[[]]
+        endif
+        let dargs+=[{}]
+        let diff=call(a:repo.functions.diff, dargs, {})
+        "▶3 Getting status
+        let usescur=(len(rspec)==1)
+        let rsallfiless=map(copy(rspec),
+                    \       'a:repo.functions.getcsprop(a:repo, v:val, '.
+                    \                                  '"allfiles")')
+        if a:0>2 && !empty(a:3)
+            call map(rsallfiless, 'filter(v:val, "index(a:3, v:val)!=-1")')
+        endif
+        if usescur
+            let fs=s:F.status(a:repo, get(a:000, 2, []))[1]
+            call filter(fs, 'v:val is# "deleted" || v:val is# "removed"')
+            let ofs=rsallfiless[0]
+        else
+            let [ofs, fs]=rsallfiless
+        endif
+        "▲3
+        let [r, revstatus]=s:F.statfromdiff(a:repo, diff, ofs, fs,
+                    \                       get(a:000, 2, []))
+        "▶3 Reversing range
+        if reverse
+            let [r.deleted, r.unknown]=[r.unknown, r.deleted]
+            let [r.added,   r.removed]=[r.removed, r.added  ]
+        endif
+        "▲3
+        let allfiles=rsallfiless[0]
+    endif
+    "▲2
+    if exists('allfiles')
+        let r.clean=filter(copy(allfiles), '!has_key(revstatus, v:val)')
+    endif
+    return r
+endfunction
+"▶1 svn.annotate :: repo, rev, file → [(file, hex, linenumber)]
+" TODO use merge history if available
+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=[]
+    for line in lines
+        let r+=[[a:file, matchstr(line, '\v\d+'), 0]]
+    endfor
+    return r
+endfunction
+"▶1 svn.commit :: repo, message[, files[, user[, date[, _]]]]
+function s:svn.commit(repo, message, ...)
+    let args=[]
+    if a:0
+        if !empty(a:1)
+            let args+=['--']+a:1
+            call s:_r.utils.addfiles(a:repo, a:1)
+        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
+    return s:_r.utils.usefile(a:repo, a:message, 'file', 'message',
+                \             s:F.svnm, args, {}, 0, 'cif')
+endfunction
+"▶1 svn.update :: repo, rev, force → + FS
+function s:svn.update(repo, rev, force)
+    let kwargs={}
+    if a:force
+        let kwargs.force=1
+        let kwargs.accept='theirs-full'
+    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
+" FIXME For some reason :AuJ in drivers-subversion-subdir test is not actually 
+" removing file
+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)
+    let dir=s:_r.os.path.dirname(a:glob)
+    let glob=s:_r.os.path.basename(a:glob)
+    return s:F.svnm(a:repo, 'propset', ['svn:ignore', glob, dir], {}, 0,
+                \   'ignf', a:glob)
+endfunction
+"▶1 svn.getrepoprop :: repo, propname → a
+function s:svn.getrepoprop(repo, prop)
+    if a:prop is# 'url'
+        return a:repo.svnurl
+    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,
+                \'initprops': ['rev', 'hex', 'parents', 'tags', 'bookmarks',
+                \              'branch', 'time', 'user', 'description',
+                \              'renames', 'copies', 'files', 'changes',
+                \              'removes'],
+                \'has_merges': 0, 'iterfuncs': deepcopy(s:iterfuncs),}
+    "▶2 Get svnprefix
+    let str1='URL: '
+    let str1lidx=len(str1)-1
+    let str2='Repository Root: '
+    let str2lidx=len(str2)-1
+    for line in s:F.svn(repo, 'info', [], {}, 0, 'infof')
+        if line[:(str1lidx)] is# str1
+            let repo.svnurl=line[str1lidx+1:]
+        elseif line[:(str2lidx)] is# str2
+            let repo.svnroot=line[str2lidx+1:]
+            " XXX Assuming repository root always goes after URL
+            break
+        endif
+    endfor
+    let repo.svnprefix=repo.svnurl[(len(repo.svnroot)+1):]
+    if !empty(repo.svnprefix)
+        let repo.svnprefix.='/'
+    endif
+    "▲2
+    return repo
+endfunction
+"▶1 svn.checkdir :: dir → Bool
+function s:F.checkdir(dir)
+    return s:_r.os.path.isdir(s:_r.os.path.join(a:dir, '.svn'))
+endfunction
+let s:svn.checkdir=s:F.checkdir
+"▶1 svn.getroot :: dir → Maybe dir
+function s:svn.getroot(dir)
+    let checkeddirs=[]
+    let dir=s:_r.os.path.dirname(a:dir)
+    while !(dir is# get(checkeddirs, -1, 0))
+        if !s:F.checkdir(dir)
+            break
+        endif
+        call insert(checkeddirs, dir)
+        let dir=s:_r.os.path.dirname(dir)
+    endwhile
+    if empty(checkeddirs)
+        return a:dir
+    endif
+    let uuid=s:F.getfrominfo({'path': a:dir}, 'Repository UUID')
+    for dir in checkeddirs
+        if s:F.getfrominfo({'path': dir}, 'Repository UUID') is# uuid
+            return dir
+        endif
+    endfor
+    return a:dir
+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=▶,▲

plugin/aurum/log.vim

     "▶3 Get grapher
     if get(a:repo, 'has_octopus_merges', 1)
         let literfuncs=s:iterfuncs.git
+    elseif get(a:repo, 'has_merges', 1)
+        let literfuncs=s:iterfuncs.hg
     else
-        let literfuncs=s:iterfuncs.hg
+        let literfuncs=s:iterfuncs.simple
     endif
     "▶3 Initialize variables
     let haslimit=(has_key(a:opts, 'limit') && a:opts.limit)
 endfunction
 "▶1 iterfuncs: loggers
 "▶2 iterfuncs.git
-" TODO Fix skipping changesets if possible
 let s:iterfuncs.git={}
 function s:iterfuncs.git.start(repo, opts, ...)
     let graph=s:F.glog.graph_init(get(a:000, 0, []), a:opts, a:repo)
         return [text.text, 0, 0]
     endif
 endfunction
+"▶2 iterfuncs.simple
+let s:iterfuncs.simple={}
+function s:iterfuncs.simple.start(repo, opts, ...)
+    return {'opts': a:opts, 'repo': a:repo, 'showparents': get(a:000, 0, [])}
+endfunction
+function s:iterfuncs.simple.proccs(d, cs)
+    if has_key(a:d.opts.skipchangesets, a:cs.hex)
+        return [[], 0, 0]
+    endif
+    let text=a:d.opts.templatefunc(a:cs, a:d.opts, a:d.repo)
+    let text.block_r=[[0, 0],
+                \     [len(text.text)-1,
+                \      max(map(copy(text.text), 'len(v:val)'))]]
+    let char='@o'[(index(a:d.showparents, a:cs.hex)==-1)]
+    call map(text.text, '(v:key ? "|" : char)." ".v:val')
+    call s:F.glog.addcols(text.special, 2)
+    let text.special.bullet=[0, 0, char]
+    return [text.text, text.block_r, text.special]
+endfunction
 "▶1 temp
 "▶2 s:templates
 let s:templates={

plugin/aurum/repo.vim

 "▶1
 scriptencoding utf-8
 if !exists('s:_pluginloaded')
-    execute frawor#Setup('2.2', {'@/resources': '0.0',
+    execute frawor#Setup('2.4', {'@/resources': '0.0',
                 \                       '@/os': '0.0',
                 \                  '@/options': '0.0',
                 \             '@aurum/bufvars': '0.0',}, 0)
     endwhile
     return stats
 endfunction
+"▶1 grep :: repo, pattern, [file], [Either hex (hex, hex)], ic, _ → qflist
+function s:deffuncs.grep(repo, pattern, files, revs, ic, wdf)
+    if empty(a:revs)
+        let cs=a:repo.functions.getwork(a:repo)
+        let filelist=copy(a:repo.functions.getcsprop(a:repo, cs, 'allfiles'))
+        if !empty(a:files)
+            call filter(filelist, 'index(a:files, v:val)!=-1')
+        endif
+        call map(filelist, 's:_r.os.path.join(a:repo.path, v:val)')
+        call filter(filelist, 'filereadable(v:val)')
+        let expr='readfile(fspec, "b")'
+    else
+        let css=[]
+        for rspec in a:revs
+            if type(rspec)==type([])
+                let css+=a:repo.functions.revrange(a:repo, rspec[0], rspec[1])
+            else
+                let css+=[a:repo.functions.getcs(a:repo, rspec)]
+            endif
+            unlet rspec
+        endfor
+        let filelist=[]
+        let gcspexpr='a:repo.functions.getcsprop(a:repo, v:val, "allfiles")'
+        let mexpr='extend(filelist, '.
+                    \    'map('.((empty(a:files))?
+                    \               (gcspexpr):
+                    \               ('filter(copy('.gcspexpr.'), '.
+                    \                       '"index(a:files, v:val)!=-1")')).
+                    \        ', "[css[".v:key."].hex, v:val]"))'
+        call map(copy(css), mexpr)
+        let expr='a:repo.functions.readfile(a:repo, fspec[0], fspec[1])'
+    endif
+    let r=[]
+    let fexpr='v:val[1]=~'.('?#'[!a:ic]).string(a:pattern)
+    for fspec in filelist
+        let r+=map(filter(map(copy(eval(expr)), '[v:key, v:val]'), fexpr),
+                    \'{"filename": fspec,"lnum": v:val[0]+1,"text": v:val[1]}')
+    endfor
+    return r
+endfunction
 "▶1 copy
 function s:deffuncs.copy(repo, force, source, target)
     let src=s:_r.os.path.normpath(s:_r.os.path.join(a:repo.path, a:source))
             unlet driver
             let driver=s:F.getdriver(path, 'dir')
             if driver isnot 0
+                if has_key(driver.functions, 'getroot')
+                    let newpath=driver.functions.getroot(path)
+                    if newpath is 0
+                        continue
+                    endif
+                    let path=newpath
+                endif
                 break
             endif
             let olddir=path

plugin/aurum/status.vim

     execute frawor#Setup('1.1', {'@/resources': '0.0',
                 \            '@aurum/cmdutils': '0.0',
                 \                      '@/fwc': '0.2',
-                \                '@aurum/repo': '2.0',
+                \                '@aurum/repo': '2.4',
                 \                '@aurum/edit': '1.0',
                 \                 '@/commands': '0.0',
                 \                  '@/options': '0.0',
         let opts[key]=a:repo.functions.getrevhex(a:repo, opts[key])
     endfor
     let bvar={}
+    let requiresclean=0
+    if has_key(opts, 'show')
+        if index(opts.show, 'all')==-1
+            let show=s:F.parseshow(opts.show)
+            let requiresclean=(index(show, 'clean')!=-1)
+        else
+            let show=s:allshow
+            let requiresclean=1
+        endif
+    else
+        let show=s:defshow
+    endif
     let status=a:repo.functions.status(a:repo, get(opts, 'rev',   0),
-                \                              get(opts, 'wdrev', 0))
+                \                              get(opts, 'wdrev', 0),
+                \                              0, requiresclean)
     let bvar.status=status
     let bvar.types=[]
     let bvar.chars=[]
     let bvar.files=[]
-    if has_key(opts, 'show')
-        if index(opts.show, 'all')==-1
-            let show=s:F.parseshow(opts.show)
-        else
-            let show=s:allshow
-        endif
-    else
-        let show=s:defshow
-    endif
     let isrecord=get(opts, 'record', 0)
     let statlines=[]
     for [type, files] in filter(sort(items(status)), 'index(show,v:val[0])!=-1')
     except AurumError:
         pass
 
-def get_status(path, rev1=None, rev2=None, files=None):
+def get_status(path, rev1=None, rev2=None, files=None, clean=None):
     try:
         if rev1 is None and rev2 is None:
             rev1='.'
         repo=g_repo(path)
         if hasattr(repo, 'status'):
-            if files is None:
+            if not files:
                 m=None
             else:
                 m=match.match(None, None, files, exact=True)
-            status=repo.status(rev1, rev2, ignored=True, clean=True, unknown=True,
-                               match=m)
+            status=repo.status(rev1, rev2, ignored=True, clean=clean,
+                               unknown=True, match=m)
             vim.eval('extend(r, '+nonutf_dumps({'modified': status[0],
                                                    'added': status[1],
                                                  'removed': status[2],

test/createsvnrepo.zsh

+#!/bin/zsh
+export TZ=UTC
+svnadmin create svntestreposerver
+# tar -xJf svntestreposerver.tar.xz
+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 subdir
+> file-2 << EOF
+Line 1
+First line
+Seventh line
+Second line
+EOF
+svn add file-2
+svn commit --message 'Added file-2'
+perl -p -i -e 's/Line 1/Zero line/; s/Second line/Third line/;' file-2
+svn commit --message 'Replace “Line 1” with “Zero line”'
+cd ..
+echo '9’th line' >> file-under-svn-control
+svn commit --message 'Added 9’th line to file-under-svn-control'
+cd subdir
+perl -p -i -e 's/Seventh line/Second line/' file-2
+svn commit --message 'Fix: s/Seventh/Second/'
+cd ..
+
+cd ..
+tar -cJvf svntestreposerver.tar.xz svntestreposerver

test/drivers-subversion-subdir.in

+:let g:curtest='svntest-subdir'
+:let g:tipname='HEAD'
+:let g:testedfile='svntest-subdirrepo/file-2'
+:command -nargs=0 WTF call WriteFile(readfile(g:testedfile, 'b'))
+:W{{{1 Annotate
+:Run! AuAnnotate file ./svntest-subdirrepo/file-2 | wincmd w
+:bwipeout!
+:bwipeout!
+:W{{{1 Log: everything
+:Run! AuLog ./svntest-subdirrepo/ stat patch showfiles showrenames showcopies
+:bwipeout!
+:W{{{1 Status: current
+:R call writefile(['Unknown file contents'], 'unknown-file')
+:R call writefile(['New file contents'], 'new-file')
+:AuTrack ./svntest-subdirrepo/new-file
+:R call delete('file-2')
+:AuJunk ./svntest-subdirrepo/pqr
+:AuJunk ignoreglob ./svntest-subdirrepo/ghi
+:Run AuStatus
+:bwipeout!
+:R call writefile(['Zero line'], 'file-2')
+:W{{{1 Status: relative to other (1)
+:Run AuStatus rev 4
+:bwipeout!
+:W{{{1 Status: relative to other (2)
+:Run AuStatus wdrev 4
+:bwipeout!
+:W{{{1 Status: between two revisions
+:R !svn revert -R .
+:Run! AuStatus ./svntest-subdirrepo/ rev HEAD wdrev 4
+:bwipeout!
+:W{{{1 Commit
+:edit ./svntest-subdirrepo/newfile.vim
+iW In file newfile.vim:write
+:Run! AuCommit message Added\ newfile.vim type unknown ./svntest-subdirrepo/newfile.vim
+:WT
+:W{{{1 Commit 2
+oW File newfile.vim, second line:write
+:Run! AuCommit
+Added second line to newfile.vim
+// It was not required really:write
+:WT
+:W{{{1 Update
+:AuUpdate 6 ./svntest-subdirrepo/
+:WTF
+:W{{{1 Update: force
+:write! ./svntest-subdirrepo/file-2
+:AuUpdate! 7 ./svntest-subdirrepo/
+:WTF
+:W{{{1 Update: to tip
+:R AuUpdate
+:WTF
+:W{{{1 Move
+:AuMove ./svntest-subdirrepo/pqr ./svntest-subdirrepo/stu
+:AuCommit message Moved\ pqr\ to\ stu repo ./svntest-subdirrepo/ all
+:WTa showrenames showcopies
+:W{{{1 Move: copy
+:AuMove copy ./svntest-subdirrepo/stu ./svntest-subdirrepo/pqr
+:AuCommit message Copied\ stu\ to\ pqr repo ./svntest-subdirrepo/ all
+:WTa showrenames showcopies
+:W{{{1 Junk: forget, remove
+:call WriteFile(filereadable('./svntest-subdirrepo/pqr').filereadable('./svntest-subdirrepo/stu'))
+:AuJunk forget ./svntest-subdirrepo/pqr
+:AuJunk remove ./svntest-subdirrepo/stu
+:call WriteFile(filereadable('./svntest-subdirrepo/pqr').filereadable('./svntest-subdirrepo/stu'))
+:AuCommit ./svntest-subdirrepo/**
+ggddGld:write
+:WT
+:W{{{1 Grep: working directory
+:Run! AuGrep W repo ./svntest-subdirrepo/ | cwindow
+:cclose
+:W{{{1 Grep: HEAD
+:let repo=aurum#repository()
+:Run! AuGrep W revision HEAD repo ./svntest-subdirrepo/ | cwindow
+:cclose
+:W{{{1 Junk: ignore, ignoreglob
+:write! ./svntest-subdirrepo/file.vim
+:write! ./svntest-subdirrepo/ignoredfile.vim
+:write! ./svntest-subdirrepo/ignoredfile2.vim
+:W{{{2 Status before junk
+:Run! AuStatus ./svntest-subdirrepo/
+:bwipeout!
+:AuJunk ignore ./svntest-subdirrepo/file.vim
+:AuJunk ignoreglob ./svntest-subdirrepo/ignored*
+:W{{{2 Status after junk
+:Run! AuStatus ./svntest-subdirrepo/
+:bwipeout!

test/drivers-subversion-subdir.ok

+{{{1 Annotate
+7 Replace “Line 1” with “Zero li… / zyx
+6 Added file-2                    / zyx
+9 Fix: s/Seventh/Second/          / zyx
+7 Replace “Line 1” with “Zero li… / zyx
+{{{1 Log: everything
+@ Changeset 9:9
+| Commited 29 Янв 2012 10:09 by zyx
+| Files: file-2
+| @ Fix: s/Seventh/Second/
+| $  file-2 | 1 1
+| $1 files changed, 1 insertions, 1 deletions
+| :Index: file-2
+| :===================================================================
+| :--- file-2	(revision 8)
+| :+++ file-2	(revision 9)
+| :@@ -1,4 +1,4 @@
+| : Zero line
+| : First line
+| :-Seventh line
+| :+Second line
+| : Third line
+| 
+o Changeset 7:7
+| Commited 29 Янв 2012 10:09 by zyx
+| Files: file-2
+| @ Replace “Line 1” with “Zero line”
+| $  file-2 | 2 2
+| $1 files changed, 2 insertions, 2 deletions
+| :Index: file-2
+| :===================================================================
+| :--- file-2	(revision 6)
+| :+++ file-2	(revision 7)
+| :@@ -1,4 +1,4 @@
+| :-Line 1
+| :+Zero line
+| : First line
+| : Seventh line
+| :-Second line
+| :+Third line
+| 
+o Changeset 6:6
+| Commited 29 Янв 2012 10:09 by zyx
+| Files: file-2
+| @ Added file-2
+| $  file-2 | 4 0
+| $1 files changed, 4 insertions, 0 deletions
+| :Index: file-2
+| :===================================================================
+| :--- file-2	(revision 0)
+| :+++ file-2	(revision 6)
+| :@@ -0,0 +1,4 @@
+| :+Line 1
+| :+First line
+| :+Seventh line
+| :+Second line
+| 
+o Changeset 4:4
+| Commited 08 Янв 2012 20:45 by zyx
+| Files: pqr
+| @ Added seventh line to file-under-svn-control
+| @ Added def, ghi, mno
+| @ Added subdir/pqr
+| @ Added subdir2, subdir2/stu
+| $  pqr | 1 0
+| $1 files changed, 1 insertions, 0 deletions
+| :Index: pqr
+| :===================================================================
+| :--- pqr	(revision 0)
+| :+++ pqr	(revision 4)
+| :@@ -0,0 +1 @@
+| :+Mno
+| 
+{{{1 Status: current
+A new-file
+! file-2
+M .
+R pqr
+? unknown-file
+{{{1 Status: relative to other (1)
+A file-2
+A new-file
+R pqr
+{{{1 Status: relative to other (2)
+A pqr
+R file-2
+R new-file
+{{{1 Status: between two revisions
+R file-2
+{{{1 Commit
+W In file newfile.vim
+o Changeset 10
+| @ Added newfile.vim
+{{{1 Commit 2
+
+# Modified newfile.vim
+o Changeset 11
+| @ Added second line to newfile.vim
+| @ // It was not required really
+{{{1 Update
+Line 1
+First line
+Seventh line
+Second line
+{{{1 Update: force
+Zero line
+First line
+Seventh line
+Third line
+{{{1 Update: to tip
+Zero line
+First line
+Second line
+Third line
+{{{1 Move
+o Changeset 12
+| Renamed pqr to stu
+| @ Moved pqr to stu
+{{{1 Move: copy
+o Changeset 13
+| Copied stu to pqr
+| @ Copied stu to pqr
+{{{1 Junk: forget, remove
+11
+11
+o Changeset 14
+| @ Removed pqr
+{{{1 Grep: working directory
+svntest-subdirrepo/newfile.vim|1| W In file newfile.vim
+svntest-subdirrepo/newfile.vim|2| W File newfile.vim, second line
+{{{1 Grep: HEAD
+aurum://file:%ETMPDIR%%-test%-svntest-subdirrepo:14:newfile.vim|1| W In file newfile.vim
+aurum://file:%ETMPDIR%%-test%-svntest-subdirrepo:14:newfile.vim|2| W File newfile.vim, second line
+{{{1 Junk: ignore, ignoreglob
+{{{2 Status before junk
+? ignoredfile.vim
+? pqr
+? ignoredfile2.vim
+? new-file
+? file.vim
+? unknown-file
+{{{2 Status after junk
+M .
+? pqr
+? new-file
+? file.vim
+? unknown-file

test/drivers-subversion.in

+:let g:curtest='svntest'
+:let g:tipname='HEAD'
+:let g:testedfile='svntestrepo/file-under-svn-control'
+:command -nargs=0 WTF call WriteFile(readfile(g:testedfile, 'b'))
+:W{{{1 Annotate
+:Run! AuAnnotate file ./svntestrepo/--force | wincmd w
+:bwipeout!
+:bwipeout!
+:W{{{1 Log: everything
+:Run! AuLog ./svntestrepo/ stat patch showfiles showrenames showcopies
+:bwipeout!
+:W{{{1 Status: current
+:R call writefile(['Unknown file contents'], 'unknown-file')
+:R call writefile(['New file contents'], 'new-file')
+:AuTrack ./svntestrepo/new-file
+:R call delete('file-under-svn-control')
+:AuJunk ./svntestrepo/def
+:AuJunk ignore ./svntestrepo/ghi
+:Run AuStatus
+:bwipeout!
+:R call writefile(['Zero line'], 'file-under-svn-control')
+:W{{{1 Status: relative to other (1)
+:Run AuStatus rev 3
+:bwipeout!
+:W{{{1 Status: relative to other (2)
+:Run AuStatus wdrev 3
+:bwipeout!
+:W{{{1 Status: between two revisions
+:R !svn revert -R .
+:Run! AuStatus ./svntestrepo/ rev HEAD wdrev 2
+:bwipeout!
+:W{{{1 Commit
+:edit ./svntestrepo/newfile.vim
+iW In file newfile.vim:write
+:Run! AuCommit message Added\ newfile.vim type unknown ./svntestrepo/newfile.vim
+:WT
+:W{{{1 Commit 2
+oW File newfile.vim, second line:write
+:Run! AuCommit
+Added second line to newfile.vim
+// It was not required really:write
+:WT
+:W{{{1 Update
+:AuUpdate 3 ./svntestrepo/
+:WTF
+:W{{{1 Update: force
+:write! ./svntestrepo/file-under-svn-control
+:AuUpdate! 4 ./svntestrepo/
+:WTF
+:W{{{1 Update: to tip
+:R AuUpdate
+:WTF
+:W{{{1 Move
+:AuMove ./svntestrepo/--force ./svntestrepo/-f
+:AuCommit message Moved\ --force\ to\ -f repo ./svntestrepo/ all
+:WTa showrenames showcopies
+:W{{{1 Move: copy
+:AuMove copy ./svntestrepo/-f ./svntestrepo/--force
+:AuCommit message Copied\ -f\ to\ --force repo ./svntestrepo/ all
+:WTa showrenames showcopies
+:W{{{1 Junk: forget, remove
+:call WriteFile(filereadable('./svntestrepo/-f').filereadable('./svntestrepo/--force'))
+:AuJunk forget ./svntestrepo/-f
+:AuJunk remove ./svntestrepo/--force
+:call WriteFile(filereadable('./svntestrepo/-f').filereadable('./svntestrepo/--force'))
+:AuCommit ./svntestrepo/**
+ggddGld:write
+:WT
+:W{{{1 Grep: working directory
+:Run! AuGrep W repo ./svntestrepo/ | cwindow
+:cclose
+:W{{{1 Grep: HEAD
+:let repo=aurum#repository()
+:Run! AuGrep W revision HEAD repo ./svntestrepo/ | cwindow
+:cclose
+:W{{{1 Junk: ignore, ignoreglob
+:write! ./svntestrepo/file.vim
+:write! ./svntestrepo/subdir/file.vim
+:write! ./svntestrepo/ignoredfile.vim
+:write! ./svntestrepo/ignoredfile2.vim
+:W{{{2 Status before junk
+:Run! AuStatus ./svntestrepo/
+:bwipeout!
+:AuJunk ignore ./svntestrepo/subdir/file.vim
+:AuJunk ignoreglob ./svntestrepo/ignored*
+:W{{{2 Status after junk
+:Run! AuStatus ./svntestrepo/
+:bwipeout!

test/drivers-subversion.ok

+{{{1 Annotate
+3 Added --force                   / zyx
+{{{1 Log: everything
+@ Changeset 9:9
+| Commited 29 Янв 2012 10:09 by zyx
+| Files: subdir/file-2
+| @ Fix: s/Seventh/Second/
+| $  subdir/file-2 | 1 1
+| $1 files changed, 1 insertions, 1 deletions
+| :Index: subdir/file-2
+| :===================================================================
+| :--- subdir/file-2	(revision 8)
+| :+++ subdir/file-2	(revision 9)
+| :@@ -1,4 +1,4 @@
+| : Zero line
+| : First line
+| :-Seventh line
+| :+Second line
+| : Third line
+| 
+o Changeset 8:8
+| Commited 29 Янв 2012 10:09 by zyx
+| Files: file-under-svn-control
+| @ Added 9’th line to file-under-svn-control
+| $  file-under-svn-control | 1 0
+| $1 files changed, 1 insertions, 0 deletions
+| :Index: file-under-svn-control
+| :===================================================================
+| :--- file-under-svn-control	(revision 7)
+| :+++ file-under-svn-control	(revision 8)
+| :@@ -5,3 +5,4 @@
+| : Fifth line
+| : Sixth line
+| : Seventh line
+| :+9’th line
+| 
+o Changeset 7:7
+| Commited 29 Янв 2012 10:09 by zyx
+| Files: subdir/file-2
+| @ Replace “Line 1” with “Zero line”
+| $  subdir/file-2 | 2 2
+| $1 files changed, 2 insertions, 2 deletions
+| :Index: subdir/file-2
+| :===================================================================
+| :--- subdir/file-2	(revision 6)
+| :+++ subdir/file-2	(revision 7)
+| :@@ -1,4 +1,4 @@
+| :-Line 1
+| :+Zero line
+| : First line
+| : Seventh line
+| :-Second line
+| :+Third line
+| 
+o Changeset 6:6
+| Commited 29 Янв 2012 10:09 by zyx
+| Files: subdir/file-2
+| @ Added file-2
+| $  subdir/file-2 | 4 0
+| $1 files changed, 4 insertions, 0 deletions
+| :Index: subdir/file-2
+| :===================================================================
+| :--- subdir/file-2	(revision 0)
+| :+++ subdir/file-2	(revision 6)
+| :@@ -0,0 +1,4 @@
+| :+Line 1
+| :+First line
+| :+Seventh line
+| :+Second line
+| 
+o Changeset 5:5
+| Commited 08 Янв 2012 20:45 by zyx
+| Files: file-under-svn-control
+| @ Replaced “Fourth” with “4’th”
+| $  file-under-svn-control | 1 1
+| $1 files changed, 1 insertions, 1 deletions
+| :Index: file-under-svn-control
+| :===================================================================
+| :--- file-under-svn-control	(revision 4)
+| :+++ file-under-svn-control	(revision 5)
+| :@@ -1,7 +1,7 @@
+| : First line
+| : Second line
+| : Third line
+| :-Fourth line
+| :+4’th line
+| : Fifth line
+| : Sixth line
+| : Seventh line
+| 
+o Changeset 4:4
+| Commited 08 Янв 2012 20:45 by zyx
+| Files: subdir2/stu, ghi, mno, subdir/pqr, subdir2, def, file-under-svn-control
+| @ Added seventh line to file-under-svn-control
+| @ Added def, ghi, mno
+| @ Added subdir/pqr
+| @ Added subdir2, subdir2/stu
+| $  def                    | 1 0
+| $  file-under-svn-control | 1 0
+| $  ghi                    | 1 0
+| $  mno                    | 1 0
+| $  subdir/pqr             | 1 0
+| $  subdir2/stu            | 1 0
+| $6 files changed, 6 insertions, 0 deletions
+| :Index: ghi
+| :===================================================================
+| :--- ghi	(revision 0)
+| :+++ ghi	(revision 4)
+| :@@ -0,0 +1 @@
+| :+Def
+| :Index: subdir/pqr
+| :===================================================================
+| :--- subdir/pqr	(revision 0)
+| :+++ subdir/pqr	(revision 4)
+| :@@ -0,0 +1 @@
+| :+Mno
+| :Index: mno
+| :===================================================================
+| :--- mno	(revision 0)
+| :+++ mno	(revision 4)
+| :@@ -0,0 +1 @@
+| :+Ghi
+| :Index: subdir2/stu
+| :===================================================================
+| :--- subdir2/stu	(revision 0)
+| :+++ subdir2/stu	(revision 4)
+| :@@ -0,0 +1 @@
+| :+Pqr
+| :Index: file-under-svn-control
+| :===================================================================
+| :--- file-under-svn-control	(revision 3)
+| :+++ file-under-svn-control	(revision 4)
+| :@@ -4,3 +4,4 @@
+| : Fourth line
+| : Fifth line
+| : Sixth line
+| :+Seventh line
+| :Index: def
+| :===================================================================
+| :--- def	(revision 0)
+| :+++ def	(revision 4)
+| :@@ -0,0 +1 @@
+| :+Abc
+| 
+o Changeset 3:3
+| Commited 08 Янв 2012 20:45 by zyx
+| Files: --force
+| @ Added --force
+| $  --force | 1 0
+| $1 files changed, 1 insertions, 0 deletions
+| :Index: --force
+| :===================================================================
+| :--- --force	(revision 0)