Commits

ZyX_I committed 54d5eb8

Refactoring: moved some @aurum code into @aurum/bufvars, @aurum/cmdutils and @aurum/repo
moved :AuAnnotate, :AuStatus, :AuLog, :AuVimDiff, :AuCommit to @aurum/annotate, ...
Brought back logmaps test lost while renaming :AuGlog to :AuLog

Comments (0)

Files changed (18)

ftplugin/aurumannotate.vim

 endif
 setlocal noswapfile
 setlocal nomodeline
-execute frawor#Setup('0.0', {'@aurum': '0.0',
-            \        '@aurum/vimdiff': '0.0',
-            \       '@aurum/annotate': '0.0',
-            \            '@/mappings': '0.0',
-            \                  '@/os': '0.0',})
+execute frawor#Setup('0.0', {'@aurum/repo': '0.0',
+            \             '@aurum/bufvars': '0.0',
+            \             '@aurum/vimdiff': '0.0',
+            \            '@aurum/annotate': '0.0',
+            \                 '@/mappings': '0.0',
+            \                       '@/os': '0.0',})
 let s:_messages={
             \ 'nofile': 'File was added in this revision',
             \'norfile': 'File is not present in the working directory',
 function s:F.runmap(action, ...)
     "▶2 Initialize variables
     let buf=bufnr('%')
-    let bvar=s:_r.aurum.bufvars[buf]
+    let bvar=s:_r.bufvars[buf]
     let rev=+matchstr(getline('.'), '\m\S\+')
     let hex=bvar.repo.functions.getrevhex(bvar.repo, rev)
     let cs=bvar.repo.functions.getcs(bvar.repo, hex)
                         \fnameescape('aurum://annotate:'.epath.':'.hex.':'.file)
             setlocal scrollbind
             let abuf=bufnr('%')
-            let newbvar=s:_r.aurum.bufvars[abuf]
+            let newbvar=s:_r.bufvars[abuf]
             execute bufwinnr(bvar.annbuf).'wincmd w'
         endif
         let file='aurum://file:'.epath.':'.hex.':'.file
         endif
     "▶2 `update' action
     elseif a:action is# 'update'
-        call s:_r.aurum.update(bvar.repo, hex, v:count)
+        call s:_r.repo.update(bvar.repo, hex, v:count)
     "▶2 `previous' and `next' actions
     elseif a:action is# 'previous' || a:action is# 'next'
         let c=((a:action is# 'previous')?(v:count1):(-v:count1))
                     \                                         bvar.file)
         setlocal scrollbind
         let abuf=bufnr('%')
-        let newbvar=s:_r.aurum.bufvars[abuf]
+        let newbvar=s:_r.bufvars[abuf]
         if hasannbuf
             execute bufwinnr(bvar.annbuf).'wincmd w'
             setlocal noscrollbind

ftplugin/aurumcommit.vim

 endif
 setlocal noswapfile
 setlocal nomodeline
-execute frawor#Setup('0.0', {'@aurum': '0.0',
-            \            '@/mappings': '0.0',
-            \         '@aurum/commit': '0.0',})
+execute frawor#Setup('0.0', {'@aurum/bufvars': '0.0',
+            \                    '@/mappings': '0.0',
+            \                 '@aurum/commit': '0.0',})
 "▶1 com.runcommap
 function s:F.runcommap(action, ...)
     let buf=get(a:000, 0, bufnr('%'))
-    let bvar=s:_r.aurum.bufvars[buf]
+    let bvar=s:_r.bufvars[buf]
     if a:action is# 'commit'
         call s:_r.commit.finish(bvar)
         if !a:0

ftplugin/aurumlog.vim

 endif
 setlocal noswapfile
 setlocal nomodeline
-execute frawor#Setup('0.0', {'@aurum': '0.0',
-            \                  '@/os': '0.0',
-            \            '@/mappings': '0.0',})
+execute frawor#Setup('0.0', {'@aurum/cmdutils': '0.0',
+            \                 '@aurum/bufvars': '0.0',
+            \                    '@aurum/repo': '0.0',
+            \                           '@/os': '0.0',
+            \                     '@/mappings': '0.0',})
 "▶1 bisect :: [a], function + self → a
 function s:F.bisect(list, function)
     let llist=len(a:list)
 "▶1 cr
 function s:F.cr(...)
     "▶2 Get changeset, current special, encode options
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     let [blockstart, blockend, hex]=s:F.getblock(bvar)
     if a:0
         let spname=a:1
     endif
     let epath=escape(bvar.repo.path, ':\')
     let cs=bvar.repo.changesets[hex]
-    let opts=s:_r.aurum.encodeopts(bvar.opts)
+    let opts=s:_r.cmdutils.encodeopts(bvar.opts)
     "▶2 Commit actions based on current special
     "▶3 branch: add `branch' filter
     if spname is# 'branch'
 endfunction
 "▶1 gethexfile
 function s:F.gethexfile()
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     let [blockstart, blockend, hex]=s:F.getblock(bvar)
     let spname=s:F.findCurSpecial(bvar, hex, blockstart[0])
     let cs=bvar.repo.changesets[hex]
     if file is 0
         return ''
     endif
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     let epath=escape(bvar.repo.path, ':\')
     return s:F.cwin(bvar).":silent edit ".
                 \fnameescape('aurum://file:'.epath.':'.hex.':'.file)."\n"
     if file is 0
         return ''
     endif
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     let cs=bvar.repo.changesets[hex]
     let epath=escape(bvar.repo.path, ':\')
     let efile=escape(file, '\;:')
     if file is 0
         return ''
     endif
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     let cs=bvar.repo.changesets[hex]
     let epath=escape(bvar.repo.path, ':\')
     if a:0 && a:1
 endfunction
 "▶1 next
 function s:F.next()
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     let [blockstart, blockend, hex]=s:F.getblock(bvar)
     let hex=bvar.repo.functions.getnthparent(bvar.repo, hex, -v:count1).hex
     return "\<C-\>\<C-n>".(bvar.csstarts[hex]+1).'gg'
 endfunction
 "▶1 prev
 function s:F.prev()
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     let [blockstart, blockend, hex]=s:F.getblock(bvar)
     let hex=bvar.repo.functions.getnthparent(bvar.repo, hex, v:count1).hex
     return "\<C-\>\<C-n>".(bvar.csstarts[hex]+1).'gg'
     if file is 0
         return ''
     endif
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
-    let opts=s:_r.aurum.encodeopts(bvar.opts)
+    let bvar=s:_r.bufvars[bufnr('%')]
+    let opts=s:_r.cmdutils.encodeopts(bvar.opts)
     let epath=escape(bvar.repo.path, ':\')
     return ':silent edit '.
                 \fnameescape('aurum://log:'.epath.':'.opts.
 endfunction
 "▶1 update
 function s:F.update()
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     let [blockstart, blockend, hex]=s:F.getblock(bvar)
-    call s:_r.aurum.update(bvar.repo, hex, v:count)
+    call s:_r.repo.update(bvar.repo, hex, v:count)
     return "\<C-\>\<C-n>:silent edit\n"
 endfunction
 "▶1 AuLog mapping group

ftplugin/aurumstatus.vim

 setlocal textwidth=0
 setlocal noswapfile
 setlocal nomodeline
-execute frawor#Setup('0.0', {'@aurum': '0.0',
-            \        '@aurum/vimdiff': '0.0',
-            \            '@/mappings': '0.0',
-            \                  '@/os': '0.0',})
+execute frawor#Setup('0.0', {'@aurum/bufvars': '0.0',
+            \                '@aurum/vimdiff': '0.0',
+            \                    '@/mappings': '0.0',
+            \                          '@/os': '0.0',})
 "▶1 runmap
 let s:noacttypes={
             \    'open': ['deleted'],
 let s:noacttypes.revdiff    = s:noacttypes.diff
 let s:noacttypes.revvimdiff = s:noacttypes.vimdiff
 function s:F.runmap(action)
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     if empty(bvar.types)
         return ''
     endif
 "▶1 Первая загрузка
 scriptencoding utf-8
 if !exists('s:_pluginloaded')
-    "▶2 Объявление переменных
+    "▶2 frawor#Setup
     execute frawor#Setup('0.0', {'@/commands': '0.0',
                 \               '@/functions': '0.0',
                 \                '@/mappings': '0.0',
                 \                      '@/os': '0.1',
                 \            '@/autocommands': '0.0',
-                \               '@/resources': '0.0',
-                \                     '@/fwc': '0.3',
+                \           '@aurum/cmdutils': '0.0',
+                \                     '@/fwc': '0.2',
                 \                 '@/options': '0.0',
                 \           '@aurum/annotate': '0.0',
                 \             '@aurum/status': '0.0',
                 \                '@aurum/log': '0.0',
                 \             '@aurum/commit': '0.0',
-                \            '@aurum/vimdiff': '0.0',
-                \  '@aurum/drivers/mercurial': '0.0',}, 0)
+                \               '@aurum/repo': '0.0',
+                \            '@aurum/bufvars': '0.0',
+                \            '@aurum/vimdiff': '0.0',}, 0)
     "▶2 Команды
     call FraworLoad('@/commands')
     call FraworLoad('@/functions')
     " TODO vimdiff mapping to aurum://diff
     " TODO make :AuLog accept date ranges
     let s:addargs={'Update': {'bang': 1}}
-    for s:cmd in ['Log', 'Annotate', 'Diff', 'File', 'Status', 'Commit',
-                \ 'Update', 'VimDiff']
+    for s:cmd in ['Diff', 'File', 'Update']
         let s:part=tolower(s:cmd[:3])
         if len(s:cmd)>4 && stridx('aeiouy', s:part[-1:])!=-1
             let s:part=s:part[:-2]
     let s:aubwfunc={}
     call s:_f.augroup.add('Aurum',[['BufReadCmd',  'aurum://*',0,[s:auefunc,0]],
                 \                  ['FileReadCmd', 'aurum://*',0,[s:auefunc,1]],
-                \                  ['BufWipeOut',  'aurum://*',0, s:aubwfunc],
                 \                 ])
     "▲2
     finish
 endif
 "▶1 Globals
 let s:_options={
-            \'diffopts':  {'default': {},
-            \              'checker': 'dict {numlines         range 0 inf '.
-            \                               '?in diffoptslst  bool}'},
-            \'usestatwin': {'default': 1, 'filter': 'bool'},
             \'remembermsg': {'default': 1, 'filter': 'bool'},
-            \'cachetime': {'default': 30, 'checker': 'range 0 inf'},
         \}
-let s:patharg='either (path d, match @\v^\w+%(\+\w+)*\V://\v|^\:$@)'
-let s:repoarg=':*F.getrepo(":") ('.s:patharg.
-            \                   '|*F.getrepo '.
-            \                   '#nrepo '.
-            \                   'type {})'
-let s:nogetrepoarg=':":" ('.s:patharg.')'
-let s:cmds=['new', 'vnew', 'edit',
-            \'leftabove vnew', 'rightbelow vnew', 'topleft vnew', 'botright vnew',
-            \'aboveleft new',  'belowright new',  'topleft new',  'botright new',
-            \]
-" XXX Some code relies on the fact that all options from s:diffoptslst are
-"     numeric
-let s:diffoptslst=['git', 'reverse', 'ignorews', 'iwsamount', 'iblanks',
-            \      'numlines', 'showfunc', 'alltext', 'dates']
-let s:diffoptsstr=join(map(copy(s:diffoptslst),
-            \          'v:val is# "numlines" ? '.
-            \               '" ?".v:val." range 0 inf" : '.
-            \               '"!?".v:val'))
-call map(s:cmds, 'escape(v:val, " ")')
 let s:_messages={
-            \  'nrepo': 'Failed to find a repository',
             \'nocfile': 'Unsure what should be commited',
             \'noafile': 'Failed to deduce which file to annotate',
             \'nodfile': 'Failed to deduce which file to diff with',
             \ 'nodrev': 'Unsure what revision should be diffed with',
             \'nocread': 'Cannot read aurum://commit',
         \}
-let s:bufvars={}
-let s:drivers={'mercurial': s:_r.mercurial}
-let s:revcompstr='in *F.getrevlist()'
-let s:cmdcompstr='first (in cmds, idof command)'
-"▶1 dirty :: repo, file → Bool
-function s:F.dirty(repo, file)
-    let status=a:repo.functions.status(a:repo)
-    for [type, files] in items(status)
-        if type is# 'ignored' || type is# 'clean'
-            continue
-        endif
-        if index(files, a:file)!=-1
-            return 1
-        endif
-    endfor
-    return 0
-endfunction
-"▶1 getnthparent :: repo, rev, n → cs
-function s:F.getnthparent(repo, rev, n)
-    let r=a:repo.functions.getcs(a:repo, a:rev)
-    let key=((a:n>0)?('parents'):('children'))
-    for i in range(1, abs(a:n))
-        let rl=a:repo.functions.getcsprop(a:repo, r, key)
-        if empty(rl)
-            break
-        endif
-        let r=a:repo.functions.getcs(a:repo, rl[0])
-    endfor
-    return r
-endfunction
-"▶1 reltorepo :: repo, path → rpath
-function s:F.reltorepo(repo, path)
-    return join(s:_r.os.path.split(s:_r.os.path.relpath(a:path,
-                \                                       a:repo.path))[1:], '/')
-endfunction
-"▶1 difftobuffer
-function s:F.difftobuffer(repo, buf, ...)
-    let diff=call(a:repo.functions.diff, [a:repo]+a:000, {})
-    let oldbuf=bufnr('%')
-    if oldbuf!=a:buf
-        execute 'buffer' a:buf
-    endif
-    call s:F.setlines(diff, 0)
-    if oldbuf!=a:buf
-        execute 'buffer' oldbuf
-    endif
-endfunction
-"▶1 repotype :: path → Maybe String
-function s:F.repotype(path)
-    if a:path=~#'\v^\w+%(\+\w+)*\V://' ||
-                \s:_r.os.path.isdir(s:_r.os.path.join(a:path, '.hg'))
-        return 'mercurial'
-    endif
-    return 0
-endfunction
-"▶1 getrepo :: path → repo
-function s:F.getrepo(path)
-    if empty(a:path)
-        let path=s:_r.os.path.realpath('.')
-    elseif a:path is# ':'
-        let buf=bufnr('%')
-        if has_key(s:bufvars, buf) && has_key(s:bufvars[buf], 'repo')
-            let path=s:bufvars[buf].repo.path
-        elseif has_key(s:bufvars, buf) && s:bufvars[buf].command is# 'copy'
-            let path=s:_r.os.path.dirname(
-                        \s:_r.os.path.realpath(s:bufvars[buf].file))
-        elseif empty(&buftype) && isdirectory(expand('%:p:h'))
-            let path=s:_r.os.path.realpath(expand('%:p:h'))
-        else
-            let path=s:_r.os.path.realpath('.')
-        endif
-    elseif stridx(a:path, '://')==-1
-        let path=s:_r.os.path.realpath(a:path)
-    else
-        let path=a:path
-    endif
-    if stridx(path, '://')==-1
-        let olddir=''
-        while path isnot# olddir && s:F.repotype(path) is 0
-            let olddir=path
-            let path=fnamemodify(path, ':h')
-        endwhile
-    endif
-    let repotype=s:F.repotype(path)
-    if repotype is 0
-        return 0
-    endif
-    let repo=s:drivers[repotype].repo(path)
-    if repo is 0
-        return 0
-    endif
-    let repo.type=repotype
-    if !has_key(repo, 'functions')
-        let repo.functions=copy(s:drivers[repotype])
-    endif
-    if !has_key(repo.functions, 'difftobuffer')
-        let repo.functions.difftobuffer=s:F.difftobuffer
-    endif
-    if !has_key(repo.functions, 'reltorepo')
-        let repo.functions.reltorepo=s:F.reltorepo
-    endif
-    if !has_key(repo.functions, 'dirty')
-        let repo.functions.dirty=s:F.dirty
-    endif
-    if !has_key(repo.functions, 'getnthparent')
-        let repo.functions.getnthparent=s:F.getnthparent
-    endif
-    let repo.diffopts=copy(s:_f.getoption('diffopts'))
-    return repo
-endfunction
 "▶1 setlines :: [String], read::Bool → + buffer
 function s:F.setlines(lines, read)
     let d={'set': function((a:read)?('append'):('setline'))}
         call d.set('.', a:lines)
     endif
 endfunction
-"▶1 globtopattern :: glob → pattern
-function s:F.globtopattern(glob)
-    " XXX If more metacharacters will be supported, they must be added to 
-    " escape() calls in s:F.filehistory in ftplugin/aurumlog
-    return '\V\^'.substitute(substitute(substitute(substitute(substitute(
-                \substitute(substitute(substitute(substitute(substitute(a:glob,
-                \'\v\\(.)', '\="\\{".char2nr(submatch(1))."}"', 'g'),
-                \'\V//\+',  '/',              'g'),
-                \'\V/\$',   '',                ''),
-                \'\V**/',   '\\(**/\\)\\=',   'g'),
-                \'\V**',    '\\.\\\\{42}',    'g'),
-                \'\V*',     '\\\\{91}^/]\\*', 'g'),
-                \'\V?',     '\\.',            'g'),
-                \'\V[',     '\\[',            'g'),
-                \'\V\\{'.char2nr('\').'}', '\\\\', 'g'),
-                \'\V\\{\(\d\+\)}', '\=nr2char(submatch(1))', 'g').'\v($|\/)'
-endfunction
-"▶1 encodeopts :: opts → String
-function s:F.encodeopts(opts)
-    let r=''
-    let crrestrict=get(a:opts, 'crrestrict', 0)
-    for [key, value] in filter(items(a:opts), 'crrestrict isnot# v:val[0]')
-        if type(value)==type({}) || key is# 'crrestrict' || key is# 'filepats'
-                    \            || key is# 'revs'
-            unlet value
-            continue
-        endif
-        let r.=key.':'
-        if type(value)==type([])
-            let r.=join(map(copy(value), 'escape(v:val, "\\:,;")'), ';')
-        else
-            let r.=escape(value, '\:,')
-        endif
-        let r.=','
-        unlet value
-    endfor
-    return r
-endfunction
-"▶1 getfile :: [path] → path
-function s:F.getfile(files)
-    let file=0
-    if !empty(a:files)
-        if len(a:files)==1
-            let file=a:files[0]
-        else
-            let choice=inputlist(['Select file (0 to cancel):']+
-                        \               map(copy(a:files),
-                        \                   '(v:key+1).". ".v:val'))
-            if choice
-                let file=a:files[choice-1]
-            endif
-        endif
-    endif
-    return file
-endfunction
-"▶1 getrrf :: opts, failmsg + buf → (hasbuf, repo, rev, file)
-let s:rrffailresult=[0, 0, 0, 0]
-function s:F.getrrf(opts, failmsg, ann)
-    let hasbuf=0
-    let file=0
-    "▶2 a:opts.file file → (repo?)
-    if has_key(a:opts, 'file')
-        if a:ann!=-1 && a:opts.repo is# ':'
-            let repo=s:F.getrepo(s:_r.os.path.dirname(a:opts.file))
-            let file=s:F.reltorepo(repo, a:opts.file)
-        else
-            let file=a:opts.file
-        endif
-        if !has_key(a:opts, 'rev')
-            let rev=0
-        endif
-    "▲2
-    elseif has_key(s:bufvars, bufnr('%')) &&
-                \has_key(s:bufvars[bufnr('%')], 'command')
-        let bvar=s:bufvars[bufnr('%')]
-        "▶2 +aurum://file bvar → (repo, rev, file)
-        if bvar.command is# 'file'
-            let repo=bvar.repo
-            let  rev=bvar.rev
-            let file=bvar.file
-            let hasbuf=1
-        "▶2 +aurum://copy bvar → (file), file → (repo), (rev=0)
-        elseif bvar.command is# 'copy'
-            if a:ann!=-1
-                let repo=s:F.getrepo(s:_r.os.path.dirname(bvar.file))
-            endif
-            let  rev=0
-            if a:ann==-1
-                let file=bvar.file
-            else
-                let file=s:F.reltorepo(repo, bvar.file)
-            endif
-            let hasbuf=1
-        "▶2 *aurum://status bvar → (repo, rev), "." → (file)
-        elseif bvar.command is# 'status'
-            let repo=bvar.repo
-            let  rev=get(bvar.opts, 'rev1', 0)
-            if empty(bvar.files)
-                call s:_f.throw(a:failmsg)
-            else
-                let file=bvar.files[line('.')-1]
-            endif
-            if a:ann!=-1
-                topleft new
-            endif
-        "▶2 |aurum://diff bvar → (repo, rev, file?)
-        elseif bvar.command is# 'diff'
-            let repo=bvar.repo
-            let  rev=empty(bvar.rev2) ? bvar.rev1 : bvar.rev2
-            let file=s:F.getdifffile(bvar)
-            if file is 0
-                return s:rrffailresult
-            endif
-            if a:ann!=-1
-                leftabove vnew
-            endif
-        "▶2 *aurum://commit bvar → (repo, file?)
-        elseif bvar.command is# 'commit'
-            let repo=bvar.repo
-            let file=s:F.getfile(bvar.files)
-            if file is 0
-                return s:rrffailresult
-            endif
-            if a:ann!=-1
-                topleft new
-            endif
-        "▶2 -aurum://annotate bvar → (repo, file), "." → (rev)
-        elseif bvar.command is# 'annotate'
-            let repo=bvar.repo
-            let file=bvar.file
-            if !has_key(a:opts, 'rev')
-                let rev=matchstr(getline('.'), '\v\d+')
-                let rev=repo.functions.getrevhex(repo, rev)
-                let annrev=repo.functions.getrevhex(repo, bvar.rev)
-                if rev is# annrev
-                    if a:ann!=1
-                        " Don't do the following if we are not annotating
-                    elseif has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
-                        execute bufwinnr(bvar.annbuf).'wincmd w'
-                    else
-                        let epath=escape(repo.path, ':\')
-                        setlocal scrollbind
-                        execute 'silent rightbelow vsplit'
-                                    \fnameescape('aurum://file:'.epath.':'.
-                                    \                            rev.  ':'.
-                                    \                            file)
-                        let bvar.annbuf=bufnr('%')
-                        setlocal scrollbind
-                    endif
-                    return s:rrffailresult
-                endif
-            endif
-            if a:ann!=-1
-                if winnr('$')>1
-                    wincmd c
-                endif
-                if has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
-                    execute bufwinnr(bvar.annbuf).'wincmd w'
-                endif
-            endif
-        "▶2 Unknown command
-        else
-            call s:_f.throw(a:failmsg)
-        endif
-    "▶2 buf → (repo, file), (rev=0)
-    elseif filereadable(expand('%'))
-        if a:ann!=-1
-            let repo=s:F.getrepo(':')
-        endif
-        let  rev=0
-        if a:ann==-1
-            let file=expand('%')
-        else
-            let file=s:F.reltorepo(repo, expand('%'))
-        endif
-        let hasbuf=1
-    "▲2
-    else
-        call s:_f.throw(a:failmsg)
-    endif
-    if a:ann!=-1
-        "▶2 repo
-        if !exists('repo')
-            " TODO opts.repo may be ignored. This should be documented
-            let repo=s:F.getrepo(a:opts.repo)
-            if type(repo)!=type({})
-                call s:_f.throw('nrepo')
-            endif
-            let file=s:F.reltorepo(repo, file)
-        endif
-        "▶2 rev
-        if exists('rev')
-            if rev isnot 0
-                let rev=repo.functions.getrevhex(repo, rev)
-            endif
-        else
-            " TODO opts.rev may be ignored. This should be documented
-            let rev=repo.functions.getrevhex(repo, get(a:opts, 'rev', '.'))
-        endif
-        "▲2
-    endif
-    return [hasbuf, exists('repo') ? repo : 0, rev, file]
-endfunction
-"▶1 getcrepo :: [ repo] → repo
-function s:F.getcrepo(...)
-    if a:0
-        return a:1
-    endif
-    let buf=bufnr('%')
-    if !has_key(s:bufvars, buf)
-        let s:bufvars[buf]={'command': 0}
-    endif
-    let bvar=s:bufvars[buf]
-    if has_key(bvar, 'cachedrepo') &&
-                \localtime()-bvar.cachedtime<bvar.cachetime
-        let repo=bvar.cachedrepo
-    else
-        let repo=s:F.getrepo(':')
-        let bvar.cachedrepo=repo
-        let bvar.cachedtime=localtime()
-    endif
-    if !has_key(bvar, 'cachetime')
-        let bvar.cachetime=s:_f.getoption('cachetime')
-    endif
-    return repo
-endfunction
-"▶1 getrevlist :: [ repo] → [String]
-function s:F.getrevlist(...)
-    let repo=call(s:F.getcrepo, a:000, {})
-    return       repo.functions.getrepoprop(repo, 'tagslist')+
-                \repo.functions.getrepoprop(repo, 'brancheslist')+
-                \repo.functions.getrepoprop(repo, 'bookmarkslist')
-endfunction
-"▶1 getbranchlist :: [ repo] → [String]
-function s:F.getbranchlist(...)
-    let repo=call(s:F.getcrepo, a:000, {})
-    return repo.functions.getrepoprop(repo, 'brancheslist')
-endfunction
 "▶1 closebuf :: bvar → + buf
 function s:F.closebuf(bvar)
     let r="\<C-\>\<C-n>"
     let r.=':bwipeout '.bufnr('%')."\n"
     return r
 endfunction
-"▶1 update
-function s:F.update(repo, rev, count)
-    let rev=a:rev
-    if a:count>1
-        let rev=a:repo.functions.getnthparent(a:repo, rev, a:count-1).hex
-    endif
-    return a:repo.functions.update(a:repo, rev, 0)
-endfunction
-"▶1 getdifffile :: bvar + cursor → file
-function s:F.getdifffile(bvar)
-    if len(a:bvar.files)==1
-        return a:bvar.files[0]
-    endif
-    let diffre=a:bvar.repo.functions.diffre(a:bvar.repo, a:bvar.opts)
-    let lnum=search(diffre, 'bcnW')
-    if !lnum
-        return 0
-    endif
-    return get(matchlist(getline(lnum), diffre), 1, 0)
-endfunction
 "▶1 auefunc
 "▶2 repotuplesplit :: str, UInt → (repo, String, ...)
 function s:F.repotuplesplit(str, num)
     let tail=a:str
     let repopath=matchstr(tail, '\v^(\\.|[^\\:]+)')
     let tail=tail[len(repopath)+1:]
-    let repo=s:F.getrepo(substitute(repopath,'\v\\([\\:])','\1','g'))
+    let repo=s:_r.repo.get(substitute(repopath,'\v\\([\\:])','\1','g'))
     let r=[repo]
     let i=0
     while i<a:num-1
         let [repo, opts]=s:F.repotupleoptssplit(tail, 0)
         if has_key(opts, 'files')
             let opts.files=split(opts.files, '\v%(\\@<!\\%(\\\\)*)@<!;')
-            let opts.filepats=map(copy(opts.files), 's:F.globtopattern(v:val)')
+            let opts.filepats=map(copy(opts.files),
+                        \         's:_r.cmdutils.globtopat(v:val)')
         endif
         if has_key(opts, 'revrange')
             let opts.revrange=split(opts.revrange, ';')
         endif
         for key in filter(['merges', 'patch', 'limit', 'stat', 'showfiles']+
-                    \     s:diffoptslst,
+                    \     s:_r.repo.diffoptslst,
                     \     'has_key(opts, v:val)')
             let opts[key]=+opts[key]
         endfor
         let bvar={'repo': repo, 'opts': opts, 'read': a:read}
         call s:_r.log.setup(bvar)
         if !a:read
-            let s:bufvars[buf]=bvar
+            let s:_r.bufvars[buf]=bvar
             setlocal filetype=aurumlog bufhidden=wipe
             runtime ftplugin/aurumlog.vim
         endif
         let rev=repo.functions.getrevhex(repo, rev)
         call s:F.setlines(repo.functions.readfile(repo, rev, file), a:read)
         if !a:read
-            let s:bufvars[buf]={'repo': repo, 'rev': rev, 'file': file}
+            let s:_r.bufvars[buf]={'repo': repo, 'rev': rev, 'file': file}
         endif
         if exists('#filetypedetect#BufRead')
             execute 'doautocmd filetypedetect BufRead'
         let bvar={'repo': repo, 'rev': rev, 'file': file, 'read': a:read}
         call s:_r.annotate.setup(bvar)
         if !a:read
-            let s:bufvars[buf]=bvar
+            let s:_r.bufvars[buf]=bvar
             setlocal filetype=aurumannotate bufhidden=wipe
             runtime ftplugin/aurumannotate.vim
         endif
         "▲3
         let filelist=map(split(files, '\v%(\\@<!\\%(\\\\)*)@<!;'),
                     \    'substitute(v:val, ''\v\\([\\;:])'', "\\1", "g")')
-        " XXX All options should be from s:diffoptslst list
+        " XXX All options should be from s:_r.repo.diffoptslst list
         call map(opts, '+v:val')
         if a:read
             call s:F.setlines(repo.functions.diff(repo, rev1, rev2,
                         \                              filelist, opts), 1)
         else
-            let s:bufvars[buf]={'repo': repo, 'rev1': rev1, 'rev2': rev2,
-                        \       'files': filelist, 'opts': opts,
-                        \       'originfiles': files}
+            let s:_r.bufvars[buf]={'repo': repo, 'rev1': rev1, 'rev2': rev2,
+                        \          'files': filelist, 'opts': opts,
+                        \          'originfiles': files}
             call repo.functions.difftobuffer(repo, buf, rev1, rev2,
                         \                    filelist, opts)
             setlocal filetype=diff
         let [repo, opts]=s:F.repotupleoptssplit(tail, 0)
         if has_key(opts, 'files')
             let opts.files=split(opts.files, '\v%(\\@<!\\%(\\\\)*)@<!;')
-            let opts.filepats=map(copy(opts.files), 's:F.globtopattern(v:val)')
+            let opts.filepats=map(copy(opts.files),
+                        \         's:_r.cmdutils.globtopat(v:val)')
         endif
         if has_key(opts, 'show')
             let opts.show=split(opts.show, ';')
         let bvar={'repo': repo, 'opts': opts, 'read': a:read}
         call s:_r.status.setup(bvar)
         if !a:read
-            let s:bufvars[buf]=bvar
+            let s:_r.bufvars[buf]=bvar
             setlocal filetype=aurumstatus bufhidden=wipe
             runtime ftplugin/aurumstatus.vim
         endif
                     \'remembermsg': s:_f.getoption('remembermsg')}
         let bvar.files=map(split(files, '\v%(\\@<!\\%(\\\\)*)@<!;'),
                     \      'substitute(v:val, ''\v\\([\;])'', "\\1", "g")')
-        let s:bufvars[buf]=bvar
+        let s:_r.bufvars[buf]=bvar
         setlocal modifiable noreadonly
         augroup AuCommitMessage
-            autocmd BufWriteCmd <buffer>
-                        \ : call s:_r.commit.finish(s:bufvars[expand('<abuf>')])
+            autocmd BufWriteCmd <buffer> 
+                        \call s:_r.commit.finish(s:_r.bufvars[expand('<abuf>')])
                         \ | call feedkeys("\<C-\>\<C-n>:bwipeout!\n")
         augroup END
         setlocal buftype=acwrite
         let file=tail
         call s:F.setlines(readfile(file, 'b'), a:read)
         if !a:read
-            let s:bufvars[buf]={'file': file}
+            let s:_r.bufvars[buf]={'file': file}
         endif
         if exists('#filetypedetect#BufRead')
             execute 'doautocmd filetypedetect BufRead'
     endif
     "▲2
     if !a:read
-        let s:bufvars[buf].command=command
+        let s:_r.bufvars[buf].command=command
     endif
     file
 endfunction
 "▶1 aurum://file mappings
 function s:F.runfilemap(action)
     let buf=bufnr('%')
-    let bvar=s:bufvars[buf]
+    let bvar=s:_r.bufvars[buf]
     let cmd="\<C-\>\<C-n>"
     if a:action is# 'exit'
         return s:F.closebuf(bvar)
     elseif a:action is# 'update'
-        call s:F.update(bvar.repo, bvar.rev, v:count)
+        call s:_r.repo.update(bvar.repo, bvar.rev, v:count)
         return ''
     elseif a:action is# 'previous' || a:action is# 'next'
         let c=((a:action is# 'previous')?(v:count1):(-v:count1))
 "▶1 aurum://diff mappings
 function s:F.rundiffmap(action)
     let buf=bufnr('%')
-    let bvar=s:bufvars[buf]
+    let bvar=s:_r.bufvars[buf]
     let cmd="\<C-\>\<C-n>"
     if a:action is# 'exit'
         return s:F.closebuf(bvar)
     elseif a:action is# 'update'
         let rev=(empty(bvar.rev1)?(bvar.rev2):(bvar.rev1))
-        call s:F.update(bvar.repo, rev, v:count)
+        call s:_r.repo.update(bvar.repo, rev, v:count)
         return ''
     elseif a:action is# 'previous' || a:action is# 'next'
         let c=((a:action is# 'previous')?(v:count1):(-v:count1))
         let cmd.=':edit '.
                     \fnameescape('aurum://diff:'.epath.':'.rev1.':'.rev2.':'.
                     \                            bvar.originfiles.':'.
-                    \                            s:F.encodeopts(bvar.opts))."\n"
+                    \                  s:_r.cmdutils.encodeopts(bvar.opts))."\n"
         let cmd.=":bwipeout ".buf."\n"
     elseif a:action is# 'open'
-        let file=s:F.getdifffile(bvar)
+        let file=s:_r.cmdutils.getdifffile(bvar)
         if file is 0
             return ''
         endif
             \  'Exit': {'lhs': 'X', 'rhs': ['exit'    ]},
             \  'Open': {'lhs': 'o', 'rhs': ['open'    ]},
         \}, {'func': s:F.rundiffmap, 'silent': 1, 'mode': 'n', 'dontmap': 1,})
-"▶1 logfunc
-function s:logfunc.function(repo, opts)
-    if type(a:repo)!=type({})
-        call s:_f.throw('nrepo')
-    endif
-    if has_key(a:opts, 'files')
-        call map(a:opts.files, 'a:repo.functions.reltorepo(a:repo, v:val)')
-    endif
-    let opts=s:F.encodeopts(a:opts)
-    let epath=escape(a:repo.path, ':\')
-    let cmd=get(a:opts, 'cmd', 'silent new')
-    execute cmd fnameescape('aurum://log:'.epath.':'.opts)
-endfunction
-let s:logfunc['@FWC']=['-onlystrings '.
-            \          '['.s:repoarg.']'.
-            \          '{ *?files    (type "")'.
-            \          '  *?ignfiles in [patch renames diff] ~start'.
-            \          '   ?date     match /\v%(\d\d%(\d\d)?|[*.])'.
-            \                                '%(\-%(\d\d?|[*.])'.
-            \                                '%(\-%(\d\d?|[*.])'.
-            \                                '%([ _]%(\d\d?|[*.])'.
-            \                                '%(\:%(\d\d?|[*.]))?)?)?)?/'.
-            \          '   ?search   isreg'.
-            \          '   ?user     isreg'.
-            \          '   ?branch   type ""'.
-            \          '   ?limit    range 1 inf'.
-            \          '   ?revision type ""'.
-            \          ' +2?revrange type "" type ""'.
-            \          '  !?merges'.
-            \          '  !?patch'.
-            \          '  !?stat'.
-            \          '  !?showfiles'.
-            \          '  !?showrenames'.
-            \          s:diffoptsstr.
-            \          '   ?cmd      type ""'.
-            \          '}', 'filter']
-call add(s:logcomp, substitute(substitute(
-            \substitute(substitute(substitute(substitute(s:logfunc['@FWC'][0],
-            \'\V|*F.getrepo',          '',                                  ''),
-            \'\vfiles\s+\([^)]*\)',    'files path',                        ''),
-            \'\Vcmd\s\+type ""',       'cmd first (in cmds, idof command)', ''),
-            \'\vrevision\s+\Vtype ""', 'revision '.s:revcompstr,            ''),
-            \'\vbranch\s+\Vtype ""',   'branch '.substitute(s:revcompstr, 'rev',
-            \                                               'branch', ''),  ''),
-            \'\vrevrange\s+\Vtype "" type ""',
-            \                         'revrange '.s:revcompstr.' '.s:revcompstr,
-            \                                                             ''))
-"▶1 annfunc
-function s:annfunc.function(opts)
-    let [hasannbuf, repo, rev, file]=s:F.getrrf(a:opts, 'noafile', 1)
-    if repo is 0
-        return
-    endif
-    if rev is 0
-        let rev=repo.work_hex
-    endif
-    let epath=escape(repo.path, ':\')
-    if hasannbuf
-        let annbuf=bufnr('%')
-    else
-        " TODO Check for errors
-        let afile='aurum://file:'.epath.':'.rev.':'.file
-        let existed=bufexists(afile)
-        execute 'silent edit' fnameescape(afile)
-        let annbuf=bufnr('%')
-        if !existed
-            setlocal bufhidden=wipe
-        endif
-    endif
-    setlocal scrollbind
-    execute 'silent leftabove 42vsplit '.
-                \fnameescape('aurum://annotate:'.epath.':'.rev.':'.file)
-    setlocal scrollbind
-    let buf=bufnr('%')
-    call s:_r.annotate.setannbuf(s:bufvars[buf], buf, annbuf)
-endfunction
-let s:_augroups+=['AuAnnotateBW']
-let s:annfunc['@FWC']=['-onlystrings'.
-            \          '{  repo  '.s:nogetrepoarg.
-            \          '  ?file  type ""'.
-            \          '  ?rev   type ""'.
-            \          '}', 'filter']
-call add(s:anncomp,
-            \substitute(substitute(s:annfunc['@FWC'][0],
-            \'\vfile\s+type\s*\V""', 'file path',         ''),
-            \'\vrev\s+type\s*\V""',  'rev '.s:revcompstr, ''))
-"▶1 statfunc
-function s:statfunc.function(repo, opts)
-    if type(a:repo)!=type({})
-        call s:_f.throw('nrepo')
-    endif
-    let opts=[]
-    if has_key(a:opts, 'files')
-        call map(a:opts.files, 'a:repo.functions.reltorepo(a:repo, v:val)')
-    endif
-    for [key, value] in items(a:opts)
-        let opt=key.':'
-        if key is# 'files'
-            let opt.=join(map(copy(value), 'escape(v:val, "\\:;,")'), ';')
-        elseif key is# 'show'
-            let opt.=join(value, ';')
-        else
-            let opt.=escape(value, '\:,')
-        endif
-        call add(opts, opt)
-        unlet value
-    endfor
-    let statf='aurum://status:'.escape(a:repo.path, ':\').':'.join(opts, ',')
-    let cmd='silent botright new'
-    if has_key(a:opts, 'cmd')
-        let cmd=a:opts.cmd
-    else
-        if s:_f.getoption('usestatwin') && bufexists(statf)
-            let swnr=bufwinnr(statf)
-            if swnr!=-1
-                execute swnr.'wincmd w'
-                let cmd='silent edit'
-            endif
-        endif
-    endif
-    execute cmd fnameescape(statf)
-    if !has_key(a:opts, 'cmd')
-        let lnum=line('$')
-        if winheight(0)>lnum
-            execute 'resize' lnum
-        endif
-    endif
-endfunction
-let s:statfunc['@FWC']=['-onlystrings '.
-            \           '['.s:repoarg.']'.
-            \           '{ *?files     (type "")'.
-            \           '   ?rev1      (type "")'.
-            \           '   ?rev2      (type "")'.
-            \           '  *?show      (either (in [modified added removed '.
-            \                                      'deleted unknown ignored '.
-            \                                      'clean all] ~start, '.
-            \                                  'match /\v^[MARDUIC!?]+$/))'.
-            \           '   ?cmd       (type "")'.
-            \           '}', 'filter']
-call add(s:statcomp,
-            \substitute(substitute(substitute(substitute(s:statfunc['@FWC'][0],
-            \'\V|*F.getrepo',             '',                    ''),
-            \'\vfiles\s+\([^)]*\)',       'files path',          ''),
-            \'\Vcmd\s\+(type "")',        'cmd '.  s:cmdcompstr, ''),
-            \'\vrev([12])\s+\V(type "")', 'rev\1 '.s:revcompstr, ''))
 "▶1 difffunc
 function s:difffunc.function(opts, ...)
     let repo=a:opts.repo
-    if type(repo)!=type({})
-        call s:_f.throw('nrepo')
-    endif
+    call s:_r.cmdutils.checkrepo(repo)
     let files=map(filter(copy(a:000), 'v:val isnot# ":"'),
                 \ 'repo.functions.reltorepo(repo, v:val)')
     let hascur = (len(a:000)!=len(files))
     "▶2 Filter out requested files
     call map(csfiles, 'join(s:_r.os.path.split(v:val)[1:], "/")')
     let filelist=[]
-    for pattern in map(copy(files), 's:F.globtopattern(v:val)')
+    for pattern in map(copy(files), 's:_r.cmdutils.globtopat(v:val)')
         let filelist+=filter(csfiles, 'v:val=~#pattern && '.
                     \                 'index(filelist, v:val)==-1')
     endfor
     if hascur
-        let [h, rep, rev, curfile]=s:F.getrrf({'repo': ':'}, 'nocurf', -1)
-        unlet rep rev h
+        let curfile=s:_r.cmdutils.getrrf({'repo': ':'}, 'nocurf', -1)[3]
         let curfile=repo.functions.reltorepo(repo, curfile)
         if index(csfiles, curfile)!=-1 && index(filelist, curfile)==-1
             let filelist+=[curfile]
         return
     endif
     "▲2
-    let opts=s:F.encodeopts(filter(copy(a:opts),
-                \                       'index(s:diffoptslst, v:key)!=-1'))
+    let opts=s:_r.cmdutils.encodeopts(filter(copy(a:opts),
+                \                    'index(s:_r.repo.diffoptslst, v:key)!=-1'))
     let epath=escape(repo.path, ':\')
     let efiles=join(map(filelist, 'escape(v:val, "\\;:")'), ';')
     let prevbuf=bufnr('%')
                 \ fnameescape('aurum://diff:'.epath.':'.rev1.':'.rev2.
                 \                         ':'.efiles.':'.opts)
     if !has_key(a:opts, 'cmd')
-        let s:bufvars[bufnr('%')].prevbuf=prevbuf
+        let s:_r.bufvars[bufnr('%')].prevbuf=prevbuf
     endif
 endfunction
 let s:difffunc['@FWC']=['-onlystrings '.
-            \           '{  repo     '.s:repoarg.
+            \           '{  repo     '.s:_r.cmdutils.repoarg.
             \           '  ?rev1     type ""'.
             \           '  ?rev2     type ""'.
             \           '  ?changes  type ""'.
-            \           s:diffoptsstr.
+            \           s:_r.repo.diffoptsstr.
             \           '  ?cmd      type ""'.
             \           '}'.
             \           '+ type ""', 'filter']
-" FIXME :AuD rev2 {tab} completion
 call add(s:diffcomp,
             \substitute(substitute(substitute(substitute(s:difffunc['@FWC'][0],
-            \'\V|*F.getrepo',                   '',                  ''),
-            \'\V+ type ""',                     '+ (path)',          ''),
-            \'\Vcmd\s\+type ""',                'cmd '.s:cmdcompstr, ''),
-            \'\v(rev[12]|changes)\s+\Vtype ""', '\1 '. s:revcompstr, ''))
+            \'\V|*_r.repo.get',                 '',                   ''),
+            \'\V+ type ""',                     '+ (path)',           ''),
+            \'\Vcmd\s\+type ""',                'cmd '.s:_r.comp.cmd, ''),
+            \'\v(rev[12]|changes)\s+\Vtype ""', '\1 '. s:_r.comp.rev, 'g'))
 "▶1 filefunc
 function s:filefunc.function(rev, file, opts)
     let opts=extend(copy(a:opts), {'rev': a:rev})
             let opts.file=a:file
         endif
     endif
-    let [hasbuf, repo, rev, file]=s:F.getrrf(opts, 'noffile', 0)
+    let [hasbuf, repo, rev, file]=s:_r.cmdutils.getrrf(opts, 'noffile', 0)
     if repo is 0
         return
     endif
         let &filetype=filetype
     endif
     if !has_key(a:opts, 'cmd')
-        let s:bufvars[bufnr('%')].prevbuf=prevbuf
+        let s:_r.bufvars[bufnr('%')].prevbuf=prevbuf
     endif
 endfunction
 let s:filefunc['@FWC']=['-onlystrings '.
             \           '[:"."    type "" '.
             \           '[:=(0)   either (match /\L/, path fr)]]'.
-            \           '{  repo '.s:nogetrepoarg.
+            \           '{  repo '.s:_r.cmdutils.nogetrepoarg.
             \           ' !?replace'.
             \           '  ?cmd    type ""}', 'filter']
-" FIXME Check file completion
 call add(s:filecomp,
             \substitute(substitute(substitute(s:filefunc['@FWC'][0],
-            \'\V:=(0)\v\s+either\V(\[^)]\+)', 'path',              ''),
-            \'\Vcmd\s\+type ""',              'cmd '.s:cmdcompstr, ''),
-            \'\V:"."\s\+type ""',             s:revcompstr,        ''))
+            \'\V:=(0)\s\+either (\[^)]\+)', 'path',                         ''),
+            \'\Vcmd\s\+type ""',            'cmd '.s:_r.comp.cmd,           ''),
+            \'\V:"."\s\+type ""', 'either ((type ""), '.s:_r.comp.rev.')',  ''))
 "▶1 updfunc
 function s:updfunc.function(bang, rev, repo)
-    if type(a:repo)!=type({})
-        call s:_f.throw('nrepo')
-    endif
+    call s:_r.cmdutils.checkrepo(a:repo)
     let rev=a:repo.functions.getrevhex(a:repo, a:rev)
     return a:repo.functions.update(a:repo, rev, a:bang)
 endfunction
 let s:updfunc['@FWC']=['-onlystrings _ '.
             \          '[:"tip" type ""'.
-            \          '['.s:repoarg.']]', 'filter']
+            \          '['.s:_r.cmdutils.repoarg.']]', 'filter']
 call add(s:updcomp,
             \substitute(substitute(substitute(s:updfunc['@FWC'][0],
-            \'\V _',                '',           ''),
-            \'\V|*F.getrepo',       '',           ''),
-            \'\V:"tip"\s\+type ""', s:revcompstr, ''))
-"▶1 commfunc
-function s:commfunc.function(opts, ...)
-    let repo=a:opts.repo
-    if type(repo)!=type({})
-        call s:_f.throw('nrepo')
-    endif
-    "▶2 Get file list
-    if a:0 && index(a:000, ':')!=-1
-        let files=[]
-    elseif a:0
-        if has_key(a:opts, 'type')
-            let types=s:_r.status.parseshow(a:opts.type)
-        endif
-        let filepats=map(copy(a:000), 's:F.globtopattern('.
-                    \                 'repo.functions.reltorepo(repo, v:val))')
-        let status=repo.functions.status(repo)
-        let files=[]
-        let statfiles={}
-        for [type, sfiles] in items(status)
-            if type is# 'clean' || type is# 'ignored' ||
-                        \(exists('types')?(index(types, type)==-1):
-                        \                 (type is# 'unknown' ||
-                        \                  type is# 'deleted'))
-                continue
-            endif
-            let curfiles=[]
-            for pattern in filepats
-                let curfiles+=filter(copy(sfiles), 'v:val=~#pattern')
-            endfor
-            if !empty(curfiles)
-                let statfiles[type]=curfiles
-                let files+=curfiles
-            endif
-        endfor
-    elseif filereadable(expand('%'))
-        let files=[repo.functions.reltorepo(repo, expand('%'))]
-        if !repo.functions.dirty(repo, files[0])
-            call s:_f.throw('nocfile')
-        endif
-    else
-        call s:_f.throw('nocfile')
-    endif
-    "▲2
-    return s:_r.commit.commit(repo, a:opts, files)
-endfunction
-let s:commfunc['@FWC']=['-onlystrings '.
-            \           '{  repo '.s:repoarg.
-            \           ' *?type      (either (in [modified added removed '.
-            \                                     'deleted uknown] ~start, '.
-            \                                 'match /\v^[MARDU?!]+$/))'.
-            \           '  ?message   (type "")'.
-            \           '  ?user      (type "")'.
-            \           '  ?date      match /\v%(^%(\d*\d\d-)?'.
-            \                                    '%(%(1[0-2]|0?[1-9])-'.
-            \                                      '%(3[01]|0?[1-9]|[12]\d)))?'.
-            \                                 '%(%(^|[ _])%(2[0-3]|[01]\d)'.
-            \                                            '\:[0-5]\d'.
-            \                                            '%(\:[0-5]\d)?)?$/'.
-            \           ' !?closebranch'.
-            \           '}'.
-            \           '+ type ""', 'filter']
-call add(s:commcomp,
-            \substitute(substitute(s:commfunc['@FWC'][0],
-            \'\V|*F.getrepo',  '',         ''),
-            \'\V+ type ""',    '+ (path)', ''))
-"▶1 vimdfunc
-function s:vimdfunc.function(opts, ...)
-    let [hasbuf, repo, rev, file]=s:F.getrrf(a:opts, 'nodfile', 0)
-    if repo is 0
-        return
-    endif
-    let revs=[]
-    if rev isnot 0
-        let rev=repo.functions.getrevhex(repo, rev)
-    endif
-    if a:0
-        let revs+=map(copy(a:000), 'repo.functions.getrevhex(repo, v:val)')
-        if len(revs)==1 || get(a:opts, 'curfile', 0)
-            call insert(revs, rev)
-        endif
-    else
-        let revs+=[rev, repo.work_hex]
-    endif
-    if revs[1] is# revs[0]
-        let revs[1]=get(repo.functions.getcs(repo, '.').parents, 0, 0)
-        if revs[1] is 0
-            call s:_f.throw('nodrev')
-        endif
-    endif
-    let epath=escape(repo.path, ':\')
-    let frev=remove(revs, 0)
-    if hasbuf
-        let fbuf=bufnr('%')
-    else
-        if frev is 0
-            execute 'silent edit' fnameescape(s:_r.os.path.join(repo.path,file))
-        else
-            execute 'silent edit '.
-                        \fnameescape('aurum://file:'.epath.':'.frev.':'.file)
-        endif
-        let fbuf=bufnr('%')
-    endif
-    let usewin=get(a:opts, 'usewin', -1)
-    for rev in revs
-        if rev is 0
-            let f=file
-        else
-            let f='aurum://file:'.epath.':'.rev.':'.file
-        endif
-        if hasbuf && len(revs)==1
-            let existed=bufexists(f)
-            call s:_r.vimdiff.split(f, usewin)
-            if !existed
-                setlocal bufhidden=wipe
-            endif
-        else
-            execute 'silent diffsplit' fnameescape(f)
-        endif
-    endfor
-    if bufwinnr(fbuf)!=-1
-        execute bufwinnr(fbuf).'wincmd w'
-    endif
-endfunction
-let s:vimdfunc['@FWC']=['-onlystrings '.
-            \           '{  repo  '.s:nogetrepoarg.
-            \           '  ?file  type ""'.
-            \           ' !?curfile'.
-            \           ' !?usewin'.
-            \           '}'.
-            \           '+ type ""', 'filter']
-call add(s:vimdcomp,
-            \substitute(substitute(s:vimdfunc['@FWC'][0],
-            \'\vfile\s+type\s*\V""', 'file path',       ''),
-            \'\V+ type ""',          '+ '.s:revcompstr, ''))
-"▶1 aubwfunc
-function s:aubwfunc.function()
-    let buf=+expand('<abuf>')
-    if has_key(s:bufvars, buf)
-        let bvar=s:bufvars[buf]
-        if !has_key(bvar, 'command')
-        elseif bvar.command is# 'status' && get(bvar.opts, 'record', 0)
-            let eval='<SNR>'.s:_sid.'_Eval'
-            call feedkeys("\<C-\>\<C-n>:call ".
-                    \      "call(".eval."('s:bufvars[".buf."].recunload'), ".
-                    \           "[".eval."('s:bufvars[".buf."]')], {}) | ".
-                    \"call ".eval."('remove(s:bufvars, ".buf.")')\n", 'n')
-            return
-        endif
-        unlet s:bufvars[buf]
-    endif
-endfunction
-"▶1 Post aurum resource
-call s:_f.postresource('aurum', {'bufvars': s:bufvars,
-            \              'globtopattern': s:F.globtopattern,
-            \                 'encodeopts': s:F.encodeopts,
-            \                    'getrepo': s:F.getrepo,
-            \                     'getrrf': s:F.getrrf,
-            \                     'update': s:F.update,
-            \               'nogetrepoarg': s:nogetrepoarg,}, 1)
+            \'\V _',                '',            ''),
+            \'\V|*_r.repo.get',     '',            ''),
+            \'\V:"tip"\s\+type ""', s:_r.comp.rev, ''))
 "▶1
-call frawor#Lockvar(s:, '_pluginloaded,bufvars')
+call frawor#Lockvar(s:, '_pluginloaded,_r')
 " vim: ft=vim ts=4 sts=4 et fmr=▶,▲

plugin/aurum/annotate.vim

 scriptencoding utf-8
 if !exists('s:_pluginloaded')
     execute frawor#Setup('0.0', {'@/table': '0.1',
+                \        '@aurum/cmdutils': '0.0',
+                \         '@aurum/bufvars': '0.0',
+                \                  '@/fwc': '0.3',
+                \             '@/commands': '0.0',
+                \            '@/functions': '0.0',
                 \            '@/resources': '0.0',}, 0)
+    call FraworLoad('@/commands')
+    call FraworLoad('@/functions')
+    let s:anncomp=[]
+    let s:annfunc={}
+    call s:_f.command.add('AuAnnotate', s:annfunc, {'nargs': '*',
+                \                                'complete': s:anncomp})
     finish
 elseif s:_pluginloaded
     finish
     endif
 endfunction
 let s:_augroups+=['AuAnnotateBW']
+"▶1 annfunc
+function s:annfunc.function(opts)
+    let [hasannbuf, repo, rev, file]=s:_r.cmdutils.getrrf(a:opts, 'noafile', 1)
+    if repo is 0
+        return
+    endif
+    if rev is 0
+        let rev=repo.work_hex
+    endif
+    let epath=escape(repo.path, ':\')
+    if hasannbuf
+        let annbuf=bufnr('%')
+    else
+        " TODO Check for errors
+        let afile='aurum://file:'.epath.':'.rev.':'.file
+        let existed=bufexists(afile)
+        execute 'silent edit' fnameescape(afile)
+        let annbuf=bufnr('%')
+        if !existed
+            setlocal bufhidden=wipe
+        endif
+    endif
+    setlocal scrollbind
+    execute 'silent leftabove 42vsplit '.
+                \fnameescape('aurum://annotate:'.epath.':'.rev.':'.file)
+    setlocal scrollbind
+    let buf=bufnr('%')
+    call s:F.setannbuf(s:_r.bufvars[buf], buf, annbuf)
+endfunction
+let s:_augroups+=['AuAnnotateBW']
+let s:annfunc['@FWC']=['-onlystrings'.
+            \          '{  repo  '.s:_r.cmdutils.nogetrepoarg.
+            \          '  ?file  type ""'.
+            \          '  ?rev   type ""'.
+            \          '}', 'filter']
+call add(s:anncomp,
+            \substitute(substitute(s:annfunc['@FWC'][0],
+            \'\vfile\s+type\s*\V""', 'file path',          ''),
+            \'\vrev\s+type\s*\V""',  'rev '.s:_r.comp.rev, ''))
 "▶1 post resource
 call s:_f.postresource('annotate', {'setup': s:F.setup,
             \                   'setannbuf': s:F.setannbuf})

plugin/aurum/bufvars.vim

+"▶1 
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.0', {'@/resources': '0.0',}, 0)
+    finish
+elseif s:_pluginloaded
+    finish
+endif
+let s:bufvars={}
+"▶1 aubwfunc
+function s:F.bufwipeout()
+    let buf=+expand('<abuf>')
+    if has_key(s:bufvars, buf)
+        let bvar=s:bufvars[buf]
+        if !has_key(bvar, 'command')
+        elseif bvar.command is# 'status' && get(bvar.opts, 'record', 0)
+            let eval='<SNR>'.s:_sid.'_Eval'
+            call feedkeys("\<C-\>\<C-n>:call ".
+                    \      "call(".eval."('s:bufvars[".buf."].recunload'), ".
+                    \           "[".eval."('s:bufvars[".buf."]')], {}) | ".
+                    \"call ".eval."('remove(s:bufvars, ".buf.")')\n", 'n')
+            return
+        endif
+        unlet s:bufvars[buf]
+    endif
+endfunction
+augroup AurumBufVars
+    autocmd BufWipeOut * :call s:F.bufwipeout()
+augroup END
+let s:_augroups+=['AurumBufVars']
+"▶1
+call s:_f.postresource('bufvars', s:bufvars, 1)
+"▶1
+call frawor#Lockvar(s:, '_pluginloaded,bufvars')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

plugin/aurum/cmdutils.vim

+"▶1
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.0', {'@/resources': '0.0',
+                \                       '@/os': '0.0',
+                \                  '@/options': '0.0',
+                \                '@aurum/repo': '0.0',
+                \             '@aurum/bufvars': '0.0',}, 0)
+    finish
+elseif s:_pluginloaded
+    finish
+endif
+let s:patharg='either (path d, match @\v^\w+%(\+\w+)*\V://\v|^\:$@)'
+let s:repoarg=':*_r.repo.get(":") ('.s:patharg.
+            \                     '|*_r.repo.get '.
+            \                     '#nrepo '.
+            \                     'type {})'
+let s:nogetrepoarg=':":" ('.s:patharg.')'
+let s:_messages={
+            \  'nrepo': 'Failed to find a repository',
+        \}
+"▶1 globtopattern :: glob → pattern
+function s:F.globtopattern(glob)
+    " XXX If more metacharacters will be supported, they must be added to 
+    " escape() calls in s:F.filehistory in ftplugin/aurumlog
+    return '\V\^'.substitute(substitute(substitute(substitute(substitute(
+                \substitute(substitute(substitute(substitute(substitute(a:glob,
+                \'\v\\(.)', '\="\\{".char2nr(submatch(1))."}"', 'g'),
+                \'\V//\+',  '/',              'g'),
+                \'\V/\$',   '',                ''),
+                \'\V**/',   '\\(**/\\)\\=',   'g'),
+                \'\V**',    '\\.\\\\{42}',    'g'),
+                \'\V*',     '\\\\{91}^/]\\*', 'g'),
+                \'\V?',     '\\.',            'g'),
+                \'\V[',     '\\[',            'g'),
+                \'\V\\{'.char2nr('\').'}', '\\\\', 'g'),
+                \'\V\\{\(\d\+\)}', '\=nr2char(submatch(1))', 'g').'\v($|\/)'
+endfunction
+"▶1 encodeopts :: opts → String
+function s:F.encodeopts(opts)
+    let r=''
+    let crrestrict=get(a:opts, 'crrestrict', 0)
+    for [key, value] in filter(items(a:opts), 'crrestrict isnot# v:val[0]')
+        if type(value)==type({}) || key is# 'crrestrict' || key is# 'filepats'
+                    \            || key is# 'revs'
+            unlet value
+            continue
+        endif
+        let r.=key.':'
+        if type(value)==type([])
+            let r.=join(map(copy(value), 'escape(v:val, "\\:,;")'), ';')
+        else
+            let r.=escape(value, '\:,')
+        endif
+        let r.=','
+        unlet value
+    endfor
+    return r
+endfunction
+"▶1 getdifffile :: bvar + cursor → file
+function s:F.getdifffile(bvar)
+    if len(a:bvar.files)==1
+        return a:bvar.files[0]
+    endif
+    let diffre=a:bvar.repo.functions.diffre(a:bvar.repo, a:bvar.opts)
+    let lnum=search(diffre, 'bcnW')
+    if !lnum
+        return 0
+    endif
+    return get(matchlist(getline(lnum), diffre), 1, 0)
+endfunction
+"▶1 getfile :: [path] → path
+function s:F.getfile(files)
+    let file=0
+    if !empty(a:files)
+        if len(a:files)==1
+            let file=a:files[0]
+        else
+            let choice=inputlist(['Select file (0 to cancel):']+
+                        \               map(copy(a:files),
+                        \                   '(v:key+1).". ".v:val'))
+            if choice
+                let file=a:files[choice-1]
+            endif
+        endif
+    endif
+    return file
+endfunction
+"▶1 getrrf :: opts, failmsg + buf → (hasbuf, repo, rev, file)
+let s:rrffailresult=[0, 0, 0, 0]
+function s:F.getrrf(opts, failmsg, ann)
+    let hasbuf=0
+    let file=0
+    "▶2 a:opts.file file → (repo?)
+    if has_key(a:opts, 'file')
+        if a:ann!=-1 && a:opts.repo is# ':'
+            let repo=s:_r.repo.get(s:_r.os.path.dirname(a:opts.file))
+            let file=repo.functions.reltorepo(repo, a:opts.file)
+        else
+            let file=a:opts.file
+        endif
+        if !has_key(a:opts, 'rev')
+            let rev=0
+        endif
+    "▲2
+    elseif has_key(s:_r.bufvars, bufnr('%')) &&
+                \has_key(s:_r.bufvars[bufnr('%')], 'command')
+        let bvar=s:_r.bufvars[bufnr('%')]
+        "▶2 +aurum://file bvar → (repo, rev, file)
+        if bvar.command is# 'file'
+            let repo=bvar.repo
+            let  rev=bvar.rev
+            let file=bvar.file
+            let hasbuf=1
+        "▶2 +aurum://copy bvar → (file), file → (repo), (rev=0)
+        elseif bvar.command is# 'copy'
+            if a:ann!=-1
+                let repo=s:_r.repo.get(s:_r.os.path.dirname(bvar.file))
+            endif
+            let  rev=0
+            if a:ann==-1
+                let file=bvar.file
+            else
+                let file=repo.functions.reltorepo(repo, bvar.file)
+            endif
+            let hasbuf=1
+        "▶2 *aurum://status bvar → (repo, rev), "." → (file)
+        elseif bvar.command is# 'status'
+            let repo=bvar.repo
+            let  rev=get(bvar.opts, 'rev1', 0)
+            if empty(bvar.files)
+                call s:_f.throw(a:failmsg)
+            else
+                let file=bvar.files[line('.')-1]
+            endif
+            if a:ann!=-1
+                topleft new
+            endif
+        "▶2 |aurum://diff bvar → (repo, rev, file?)
+        elseif bvar.command is# 'diff'
+            let repo=bvar.repo
+            let  rev=empty(bvar.rev2) ? bvar.rev1 : bvar.rev2
+            let file=s:F.getdifffile(bvar)
+            if file is 0
+                return s:rrffailresult
+            endif
+            if a:ann!=-1
+                leftabove vnew
+            endif
+        "▶2 *aurum://commit bvar → (repo, file?)
+        elseif bvar.command is# 'commit'
+            let repo=bvar.repo
+            let file=s:F.getfile(bvar.files)
+            if file is 0
+                return s:rrffailresult
+            endif
+            if a:ann!=-1
+                topleft new
+            endif
+        "▶2 -aurum://annotate bvar → (repo, file), "." → (rev)
+        elseif bvar.command is# 'annotate'
+            let repo=bvar.repo
+            let file=bvar.file
+            if !has_key(a:opts, 'rev')
+                let rev=matchstr(getline('.'), '\v\d+')
+                let rev=repo.functions.getrevhex(repo, rev)
+                let annrev=repo.functions.getrevhex(repo, bvar.rev)
+                if rev is# annrev
+                    if a:ann!=1
+                        " Don't do the following if we are not annotating
+                    elseif has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
+                        execute bufwinnr(bvar.annbuf).'wincmd w'
+                    else
+                        let epath=escape(repo.path, ':\')
+                        setlocal scrollbind
+                        execute 'silent rightbelow vsplit'
+                                    \fnameescape('aurum://file:'.epath.':'.
+                                    \                            rev.  ':'.
+                                    \                            file)
+                        let bvar.annbuf=bufnr('%')
+                        setlocal scrollbind
+                    endif
+                    return s:rrffailresult
+                endif
+            endif
+            if a:ann!=-1
+                if winnr('$')>1
+                    wincmd c
+                endif
+                if has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
+                    execute bufwinnr(bvar.annbuf).'wincmd w'
+                endif
+            endif
+        "▶2 Unknown command
+        else
+            call s:_f.throw(a:failmsg)
+        endif
+    "▶2 buf → (repo, file), (rev=0)
+    elseif filereadable(expand('%'))
+        if a:ann!=-1
+            let repo=s:_r.repo.get(':')
+        endif
+        let  rev=0
+        if a:ann==-1
+            let file=expand('%')
+        else
+            let file=repo.functions.reltorepo(repo, expand('%'))
+        endif
+        let hasbuf=1
+    "▲2
+    else
+        call s:_f.throw(a:failmsg)
+    endif
+    if a:ann!=-1
+        "▶2 repo
+        if !exists('repo')
+            " TODO opts.repo may be ignored. This should be documented
+            let repo=s:_r.repo.get(a:opts.repo)
+            if type(repo)!=type({})
+                call s:_f.throw('nrepo')
+            endif
+            let file=repo.functions.reltorepo(repo, file)
+        endif
+        "▶2 rev
+        if exists('rev')
+            if rev isnot 0
+                let rev=repo.functions.getrevhex(repo, rev)
+            endif
+        else
+            " TODO opts.rev may be ignored. This should be documented
+            let rev=repo.functions.getrevhex(repo, get(a:opts, 'rev', '.'))
+        endif
+        "▲2
+    endif
+    return [hasbuf, exists('repo') ? repo : 0, rev, file]
+endfunction
+"▶1 checkrepo
+function s:F.checkrepo(repo)
+    if type(a:repo)!=type({})
+        call s:_f.throw('nrepo')
+    endif
+    return 1
+endfunction
+"▶1 Post cmdutils resource
+call s:_f.postresource('cmdutils', {'globtopat': s:F.globtopattern,
+            \                      'encodeopts': s:F.encodeopts,
+            \                          'getrrf': s:F.getrrf,
+            \                     'getdifffile': s:F.getdifffile,
+            \                       'checkrepo': s:F.checkrepo,
+            \                    'nogetrepoarg': s:nogetrepoarg,
+            \                         'repoarg': s:repoarg,
+            \})
+"▶1 Some completion-related globals
+let s:_options={
+            \'cachetime': {'default': 30, 'checker': 'range 0 inf'},
+        \}
+let s:cmds=['new', 'vnew', 'edit',
+            \'leftabove vnew', 'rightbelow vnew', 'topleft vnew', 'botright vnew',
+            \'aboveleft new',  'belowright new',  'topleft new',  'botright new',
+            \]
+call map(s:cmds, 'escape(v:val, " ")')
+"▶1 getcrepo :: [ repo] → repo
+function s:F.getcrepo(...)
+    if a:0
+        return a:1
+    endif
+    let buf=bufnr('%')
+    if !has_key(s:_r.bufvars, buf)
+        let s:_r.bufvars[buf]={'command': 0}
+    endif
+    let bvar=s:_r.bufvars[buf]
+    if has_key(bvar, 'cachedrepo') &&
+                \localtime()-bvar.cachedtime<bvar.cachetime
+        let repo=bvar.cachedrepo
+    else
+        let repo=s:_r.repo.get(':')
+        let bvar.cachedrepo=repo
+        let bvar.cachedtime=localtime()
+    endif
+    if !has_key(bvar, 'cachetime')
+        let bvar.cachetime=s:_f.getoption('cachetime')
+    endif
+    return repo
+endfunction
+"▶1 getrevlist :: [ repo] → [String]
+function s:F.getrevlist(...)
+    let repo=call(s:F.getcrepo, a:000, {})
+    return       repo.functions.getrepoprop(repo, 'tagslist')+
+                \repo.functions.getrepoprop(repo, 'brancheslist')+
+                \repo.functions.getrepoprop(repo, 'bookmarkslist')
+endfunction
+"▶1 getbranchlist :: [ repo] → [String]
+function s:F.getbranchlist(...)
+    let repo=call(s:F.getcrepo, a:000, {})
+    return repo.functions.getrepoprop(repo, 'brancheslist')
+endfunction
+"▶1 Post comp resource
+call s:_f.postresource('comp', {'rev': 'in *_r.comp.revlist',
+            \                   'cmd': 'first (in _r.comp.cmdslst, idof cmd)',
+            \                   'branch': 'in *_r.comp.branchlist',
+            \                   'revlist': s:F.getrevlist,
+            \                   'branchlist': s:F.getbranchlist,
+            \                   'cmdslst': s:cmds,
+            \})
+"▶1
+call frawor#Lockvar(s:, '_pluginloaded,_r')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

plugin/aurum/commit.vim

 "▶1 
 scriptencoding utf-8
 if !exists('s:_pluginloaded')
-    execute frawor#Setup('0.0', {'@/resources': '0.0',}, 0)
+    execute frawor#Setup('0.0', {'@/resources': '0.0',
+                \              '@aurum/status': '0.0',
+                \            '@aurum/cmdutils': '0.0',
+                \                      '@/fwc': '0.3',
+                \                '@aurum/repo': '0.0',
+                \                 '@/commands': '0.0',
+                \                '@/functions': '0.0',}, 0)
+    call FraworLoad('@/commands')
+    call FraworLoad('@/functions')
+    let s:commcomp=[]
+    let s:commfunc={}
+    call s:_f.command.add('AuCommit', s:commfunc, {'nargs': '*',
+                \                               'complete': s:commcomp})
     finish
 elseif s:_pluginloaded
     finish
                 \                     a:bvar.user, a:bvar.date,
                 \                     a:bvar.closebranch)
 endfunction
+"▶1 commfunc
+function s:commfunc.function(opts, ...)
+    let repo=a:opts.repo
+    call s:_r.cmdutils.checkrepo(repo)
+    "▶2 Get file list
+    if a:0 && index(a:000, ':')!=-1
+        let files=[]
+    elseif a:0
+        if has_key(a:opts, 'type')
+            let types=s:_r.status.parseshow(a:opts.type)
+        endif
+        let filepats=map(copy(a:000), 's:_r.cmdutils.globtopat('.
+                    \                 'repo.functions.reltorepo(repo, v:val))')
+        let status=repo.functions.status(repo)
+        let files=[]
+        let statfiles={}
+        for [type, sfiles] in items(status)
+            if type is# 'clean' || type is# 'ignored' ||
+                        \(exists('types')?(index(types, type)==-1):
+                        \                 (type is# 'unknown' ||
+                        \                  type is# 'deleted'))
+                continue
+            endif
+            let curfiles=[]
+            for pattern in filepats
+                let curfiles+=filter(copy(sfiles), 'v:val=~#pattern')
+            endfor
+            if !empty(curfiles)
+                let statfiles[type]=curfiles
+                let files+=curfiles
+            endif
+        endfor
+    elseif filereadable(expand('%'))
+        let files=[repo.functions.reltorepo(repo, expand('%'))]
+        if !repo.functions.dirty(repo, files[0])
+            call s:_f.throw('nocfile')
+        endif
+    else
+        call s:_f.throw('nocfile')
+    endif
+    "▲2
+    return s:F.commit(repo, a:opts, files)
+endfunction
+let s:commfunc['@FWC']=['-onlystrings '.
+            \           '{  repo '.s:_r.cmdutils.repoarg.
+            \           ' *?type      (either (in [modified added removed '.
+            \                                     'deleted uknown] ~start, '.
+            \                                 'match /\v^[MARDU?!]+$/))'.
+            \           '  ?message   (type "")'.
+            \           '  ?user      (type "")'.
+            \           '  ?date      match /\v%(^%(\d*\d\d-)?'.
+            \                                    '%(%(1[0-2]|0?[1-9])-'.
+            \                                      '%(3[01]|0?[1-9]|[12]\d)))?'.
+            \                                 '%(%(^|[ _])%(2[0-3]|[01]\d)'.
+            \                                            '\:[0-5]\d'.
+            \                                            '%(\:[0-5]\d)?)?$/'.
+            \           ' !?closebranch'.
+            \           '}'.
+            \           '+ type ""', 'filter']
+call add(s:commcomp,
+            \substitute(substitute(s:commfunc['@FWC'][0],
+            \'\V|*_r.repo.get',  '',         ''),
+            \'\V+ type ""',      '+ (path)', ''))
 "▶1 Post resource
 call s:_f.postresource('commit', {'commit': s:F.commit,
             \                     'finish': s:F.finish,})

plugin/aurum/drivers/mercurial.vim

     endfor
 endfunction
 "▶1 hg.updatechangesets
+" FIXME make it also update changeset tags/bookmarks (or just purge these keys?)
 function s:hg.updatechangesets(repo)
     let d={}
     let start=len(a:repo.cslist)-2

plugin/aurum/log.vim

 scriptencoding utf-8
 if !exists('s:_pluginloaded')
     execute frawor#Setup('0.0', {'@/table': '0.1',
+                \        '@aurum/cmdutils': '0.0',
+                \                  '@/fwc': '0.3',
+                \            '@aurum/repo': '0.0',
+                \             '@/commands': '0.0',
+                \            '@/functions': '0.0',
                 \            '@/resources': '0.0',
                 \              '@/options': '0.0',}, 0)
+    call FraworLoad('@/commands')
+    call FraworLoad('@/functions')
+    let s:logcomp=[]
+    let s:logfunc={}
+    call s:_f.command.add('AuLog', s:logfunc, {'nargs': '*',
+                \                           'complete': s:logcomp})
     finish
 elseif s:_pluginloaded
     finish
     endif
 endfunction
 let s:_augroups+=['AuLogNoInsert']
+"▶1 logfunc
+function s:logfunc.function(repo, opts)
+    call s:_r.cmdutils.checkrepo(a:repo)
+    if has_key(a:opts, 'files')
+        call map(a:opts.files, 'a:repo.functions.reltorepo(a:repo, v:val)')
+    endif
+    let opts=s:_r.cmdutils.encodeopts(a:opts)
+    let epath=escape(a:repo.path, ':\')
+    let cmd=get(a:opts, 'cmd', 'silent new')
+    execute cmd fnameescape('aurum://log:'.epath.':'.opts)
+endfunction
+let s:logfunc['@FWC']=['-onlystrings '.
+            \          '['.s:_r.cmdutils.repoarg.']'.
+            \          '{ *?files    (type "")'.
+            \          '  *?ignfiles in [patch renames diff] ~start'.
+            \          '   ?date     match /\v%(\d\d%(\d\d)?|[*.])'.
+            \                                '%(\-%(\d\d?|[*.])'.
+            \                                '%(\-%(\d\d?|[*.])'.
+            \                                '%([ _]%(\d\d?|[*.])'.
+            \                                '%(\:%(\d\d?|[*.]))?)?)?)?/'.
+            \          '   ?search   isreg'.
+            \          '   ?user     isreg'.
+            \          '   ?branch   type ""'.
+            \          '   ?limit    range 1 inf'.
+            \          '   ?revision type ""'.
+            \          ' +2?revrange type "" type ""'.
+            \          '  !?merges'.
+            \          '  !?patch'.
+            \          '  !?stat'.
+            \          '  !?showfiles'.
+            \          '  !?showrenames'.
+            \          s:_r.repo.diffoptsstr.
+            \          '   ?cmd      type ""'.
+            \          '}', 'filter']
+call add(s:logcomp, substitute(substitute(
+            \substitute(substitute(substitute(substitute(s:logfunc['@FWC'][0],
+            \'\V|*_r.repo.get',        '',                                  ''),
+            \'\vfiles\s+\([^)]*\)',    'files path',                        ''),
+            \'\Vcmd\s\+type ""',       'cmd first (in cmds, idof command)', ''),
+            \'\vrevision\s+\Vtype ""', 'revision '.s:_r.comp.rev,           ''),
+            \'\vbranch\s+\Vtype ""',   'branch '.s:_r.comp.branch,          ''),
+            \'\vrevrange\s+\Vtype "" type ""',
+            \                         'revrange '.s:_r.comp.rev.' '.
+            \                                     s:_r.comp.rev,            ''))
 "▶1 Post resource
 call s:_f.postresource('log', {'setup': s:F.setup})
 "▶1

plugin/aurum/record.vim

 "▶1 
 scriptencoding utf-8
 if !exists('s:_pluginloaded')
-    execute frawor#Setup('0.0', {'@aurum': '0.0',
-                \                  '@/os': '0.0',
-                \                 '@/fwc': '0.0',
-                \            '@/mappings': '0.0',
-                \            '@/commands': '0.0',
-                \           '@/functions': '0.0',
-                \         '@aurum/commit': '0.0',
-                \             '@/options': '0.0',}, 0)
+    execute frawor#Setup('0.0', {'@aurum/bufvars': '0.0',
+                \                          '@/os': '0.0',
+                \                         '@/fwc': '0.0',
+                \                    '@/mappings': '0.0',
+                \                    '@/commands': '0.0',
+                \                   '@/functions': '0.0',
+                \                 '@aurum/commit': '0.0',
+                \               '@aurum/cmdutils': '0.0',
+                \                   '@aurum/repo': '0.0',
+                \                     '@/options': '0.0',}, 0)
     call FraworLoad('@/commands')
     call FraworLoad('@/functions')
     let s:reccomp=[]
 function s:recfunc.function(opts, ...)
     let files=copy(a:000)
     if !empty(files) && a:opts.repo isnot# ':'
-        let repo=s:_r.aurum.getrepo(s:_r.os.path.dirname(files[0]))
+        let repo=s:_r.repo.get(s:_r.os.path.dirname(files[0]))
     else
-        let repo=s:_r.aurum.getrepo(a:opts.repo)
+        let repo=s:_r.repo.get(a:opts.repo)
     endif
     let epath=escape(repo.path, ':\')
     call map(files, 'repo.functions.reltorepo(repo, v:val)')
     let w:aurecid='AuRecordStatus'
     setlocal nomodifiable
     call s:_f.mapgroup.map('AuRecord', bufnr('%'))
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     " 0: not included, unmodified
     " 1: not included,   modified
     " 2:     included, unmodified
 let s:_augroups+=['AuRecordStatus']
 " XXX options message, user, date and closebranch are used by com.commit
 let s:recfunc['@FWC']=['-onlystrings '.
-            \          '{  repo '.s:_r.aurum.nogetrepoarg.
+            \          '{  repo '.s:_r.cmdutils.nogetrepoarg.
             \          '  ?message           type ""'.
             \          '  ?date              type ""'.
             \          '  ?user              type ""'.
     endfor
     if lwnr is 0 || rwnr is 0
         execute swnr.'wincmd w'
-        let bvar=s:_r.aurum.bufvars[bufnr('%')]
+        let bvar=s:_r.bufvars[bufnr('%')]
         if winnr('$')>1
             only!
         endif
 function s:F.runstatmap(action, ...)
     "▶2 buf, bvar, reset
     let buf=get(a:000, 0, bufnr('%'))
-    let bvar=s:_r.aurum.bufvars[buf]
+    let bvar=s:_r.bufvars[buf]
     setlocal modifiable
     if !a:0 && b:changedtick!=bvar.prevct
         call s:_f.warn('uchngs')
                     call s:_r.os.run(['chmod', '+x', fullpath])
                 endif
             endif
-            if !has_key(s:_r.aurum.bufvars, bufnr('%'))
-                let s:_r.aurum.bufvars[bufnr('%')]={}
+            if !has_key(s:_r.bufvars, bufnr('%'))
+                let s:_r.bufvars[bufnr('%')]={}
             endif
-            call extend(s:_r.aurum.bufvars[bufnr('%')], {'recfile': file,
-                        \                   'recmodified': modified,
-                        \                   'recfullpath': fullpath,
-                        \                    'recnewfile': 0,})
+            call extend(s:_r.bufvars[bufnr('%')], {'recfile': file,
+                        \                      'recmodified': modified,
+                        \                      'recfullpath': fullpath,
+                        \                       'recnewfile': 0,})
             if exists('backupfile')
-                let s:_r.aurum.bufvars[bufnr('%')].recbackupfile=backupfile
+                let s:_r.bufvars[bufnr('%')].recbackupfile=backupfile
             else
-                let s:_r.aurum.bufvars[bufnr('%')].recnewfile=1
+                let s:_r.bufvars[bufnr('%')].recnewfile=1
             endif
         endif
     "▶2 commit
             call s:F.unload(bvar)
         else
             let w:aurecid='AuRecordCommitMessage'
-            let cbvar=s:_r.aurum.bufvars[bufnr('%')]
+            let cbvar=s:_r.bufvars[bufnr('%')]
             let cbvar.sbvar=bvar
         endif
         return
 "▶1 runleftmap
 function s:F.runleftmap(action)
     let [lwnr, rwnr, swnr]=s:F.getwnrs()
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
+    let bvar=s:_r.bufvars[bufnr('%')]
     if a:action is# 'discard'
         execute lwnr.'wincmd w'
         execute rwnr.'wincmd w'
         let [lwnr, rwnr, swnr]=s:F.getwnrs()
         execute swnr.'wincmd w'
         if !bvar.recmodified
-            let sbvar=s:_r.aurum.bufvars[bufnr('%')]
+            let sbvar=s:_r.bufvars[bufnr('%')]
             if bvar.recnewfile
                 call s:F.restorebackup(bvar.recfullpath, 0)
                 call filter(sbvar.newfiles, 'v:val isnot# bvar.recfullpath')
         return s:F.runstatmap('discard')
     elseif a:action is# 'ignore'
         call s:F.runleftmap('discard')
-        let sbvar=s:_r.aurum.bufvars[bufnr('%')]
+        let sbvar=s:_r.bufvars[bufnr('%')]
         let fidx=index(sbvar.files, bvar.recfile)
         if sbvar.statuses[fidx]>1
             let sbvar.statuses[fidx]-=2

plugin/aurum/repo.vim

+"▶1
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.0', {'@/resources': '0.0',
+                \                       '@/os': '0.0',
+                \                  '@/options': '0.0',
+                \   '@aurum/drivers/mercurial': '0.0',
+                \             '@aurum/bufvars': '0.0',}, 0)
+    finish
+elseif s:_pluginloaded
+    finish
+endif
+let s:drivers={'mercurial': s:_r.mercurial}
+let s:_options={
+            \'diffopts':  {'default': {},
+            \              'checker': 'dict {numlines         range 0 inf '.
+            \                               '?in diffoptslst  bool}'},
+        \}
+" XXX Some code relies on the fact that all options from s:diffoptslst are
+"     numeric
+let s:diffoptslst=['git', 'reverse', 'ignorews', 'iwsamount', 'iblanks',
+            \      'numlines', 'showfunc', 'alltext', 'dates']
+let s:diffoptsstr=join(map(copy(s:diffoptslst),
+            \          'v:val is# "numlines" ? '.
+            \               '" ?".v:val." range 0 inf" : '.
+            \               '"!?".v:val'))
+"▶1 dirty :: repo, file → Bool
+function s:F.dirty(repo, file)
+    let status=a:repo.functions.status(a:repo)
+    for [type, files] in items(status)
+        if type is# 'ignored' || type is# 'clean'
+            continue
+        endif
+        if index(files, a:file)!=-1
+            return 1
+        endif
+    endfor
+    return 0
+endfunction
+"▶1 getnthparent :: repo, rev, n → cs
+function s:F.getnthparent(repo, rev, n)
+    let r=a:repo.functions.getcs(a:repo, a:rev)
+    let key=((a:n>0)?('parents'):('children'))
+    for i in range(1, abs(a:n))
+        let rl=a:repo.functions.getcsprop(a:repo, r, key)
+        if empty(rl)
+            break
+        endif
+        let r=a:repo.functions.getcs(a:repo, rl[0])
+    endfor
+    return r
+endfunction
+"▶1 reltorepo :: repo, path → rpath
+function s:F.reltorepo(repo, path)
+    return join(s:_r.os.path.split(s:_r.os.path.relpath(a:path,
+                \                                       a:repo.path))[1:], '/')
+endfunction
+"▶1 difftobuffer
+function s:F.difftobuffer(repo, buf, ...)
+    let diff=call(a:repo.functions.diff, [a:repo]+a:000, {})
+    let oldbuf=bufnr('%')
+    if oldbuf!=a:buf
+        execute 'buffer' a:buf
+    endif
+    call s:F.setlines(diff, 0)
+    if oldbuf!=a:buf
+        execute 'buffer' oldbuf
+    endif
+endfunction
+"▶1 repotype :: path → Maybe String