Commits

ZyX_I  committed 854a38b Merge

Merge

  • Participants
  • Parent commits 6604476, aa4eeb6
  • Branches bzr-support

Comments (0)

Files changed (70)

 76e6f59af6a65c3c677fda5c290eee7a9a951140 release-1.5.3
 cad40c3d7996f874a10a828887f5f15007b205db release-1.5.4
 b3f985df76852f0924bb87f5617371a362a595c9 release-1.5.5
+260f55aa6a895764c0f460999de603de41eb589c release-1.5.6
+53b14be74208bac948c81b42312817ae34fb253e release-1.5.7

File README.markdown

 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#line405-0)).
+  - Partially committing changes ([:AuRecord](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#%3AAuRecord)).
 
-  - Viewing file state at particular revision ([aurum://file](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line677-0), [:AuFile](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line161-0)).
+  - Viewing file state at particular revision ([aurum://file](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#aurum%3A%2F%2Ffile), [:AuFile](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#%3AAuFile)).
 
   - Viewing uncommited changes in a vimdiff, as well as changes between 
-    specific revisions ([:AuVimDiff](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line448-0)). It is also possible to open multiple 
+    specific revisions ([:AuVimDiff](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#%3AAuVimDiff)). 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#line260-0)). Output is highly customizable.
+  - Viewing revisions log ([:AuLog](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#%3AAuLog)). Output is highly customizable.
 
-  - Viewing working directory status ([:AuStatus](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line409-0)).
+  - Viewing working directory status ([:AuStatus](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#%3AAuStatus)).
 
-  - Commiting changes ([:AuCommit](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line102-0)), commit messages are remembered in case of 
-    rollback ([g:aurum_remembermsg](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line1049-0)).
+  - Commiting changes ([:AuCommit](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#%3AAuCommit)), commit messages are remembered in case of 
+    rollback ([g:aurum_remembermsg](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#g%3Aaurum_remembermsg)).
 
   - 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#line203-0)). For mercurial it also 
+    attached: useful for sharing) ([:AuHyperlink](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#%3AAuHyperlink)). For mercurial it also 
     supports git and subversion revisions (in case you are using hg-git and 
     hgsubversion respectively).
 
-  - [aurum#changeset()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line497-0), [aurum#repository()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line493-0) and [aurum#status()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line501-0) functions 
+  - [aurum#changeset()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#aurum%23changeset%28%29), [aurum#repository()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#aurum%23repository%28%29) and [aurum#status()](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#aurum%23status%28%29) 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#line839-0)), 
+Most commands can be reached with a set of mappings (see [aurum-mappings](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#aurum-mappings)), 
 all mappings are customizable.
 
 
 
   - [frawor](https://bitbucket.org/ZyX_I/frawor)
 
+  - (optional) [ansi_esc_echo](https://bitbucket.org/ZyX_I/ansi_esc_echo)
+
+  - (optional) one of
+
+      - [Command-T](http://www.vim.org/scripts/script.php?script_id=3025)
+
+      - [ctrlp](http://www.vim.org/scripts/script.php?script_id=3736)
+
+      - [FuzzyFinder](http://www.vim.org/scripts/script.php?script_id=1984)
+
+      - [unite](http://www.vim.org/scripts/script.php?script_id=3396)
+
+      - [ku](http://www.vim.org/scripts/script.php?script_id=2337)
+
+      - [tlib](http://www.vim.org/scripts/script.php?script_id=1863)
+
+    for [:AuFile](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#%3AAuFile) prompt option and a number of OpenAny/AnnotateAny mappings.
+
 (with their dependencies).
 
 

File aurum-addon-info.txt

 {
     "name": "aurum",
-    "version": "1.5.5",
+    "version": "1.5.7",
     "author": "ZyX <kp-pav@yandex.ru>",
     "maintainer": "ZyX <kp-pav@yandex.ru>",
     "description": "Plugin for dealing with source files under various VCS control",
     "scriptnumber": 3828,
     "files": [
         "autoload/Powerline/Functions/aurum.vim",
+        "autoload/Powerline/Matches.vim",
         "autoload/Powerline/Segments/aurum.vim",
+        "autoload/Powerline/Themes/parts/aurum.vim",
         "autoload/aurum.vim",
         "autoload/aurum/annotate.vim",
         "autoload/aurum/branch.vim",
         "autoload/aurum/lineutils.vim",
         "autoload/aurum/log.vim",
         "autoload/aurum/log/templates.vim",
+        "autoload/aurum/maputils.vim",
         "autoload/aurum/move.vim",
         "autoload/aurum/name.vim",
         "autoload/aurum/other.vim",
         "autoload/aurum/track.vim",
         "autoload/aurum/update.vim",
         "autoload/aurum/vimdiff.vim",
+        "autoload/fuf/aurum.vim",
+        "autoload/unite/kinds/aurum.vim",
+        "autoload/unite/sources/aurum.vim",
         "doc/aurum.txt",
         "ftplugin/aurumannotate.vim",
         "ftplugin/aurumcommit.vim",
         "misc/map-cmdline.csinfo",
         "plugin/aurum.vim",
         "plugin/aurum/cache.vim",
-        "python/aurum.py",
+        "python/aurum/__init__.py",
+        "python/aurum/aumercurial.py",
+        "python/aurum/rcdriverfuncs.py",
+        "python/aurum/repeatedcmd.py",
+        "python/aurum/utils.py",
+        "ruby/aurum-command-t-rubyinit.rb",
+        "ruby/command-t/aurum_controller.rb",
+        "ruby/command-t/finder/aurum_finder.rb",
+        "ruby/command-t/scanner/aurum_scanner.rb",
         "syntax/aurumannotate.vim",
         "syntax/aurumcommit.vim",
         "syntax/aurumlog.vim",
         "syntax/aurumstatus.vim",
+        "test/addEmessages.vim",
         "test/addmessages.vim",
         "test/cmd+maps-commit.in",
         "test/cmd+maps-commit.ok",
+        "test/cmd+maps-vimdiff.in",
+        "test/cmd+maps-vimdiff.ok",
         "test/cmd-annotate-buffers.in",
         "test/cmd-annotate-buffers.ok",
         "test/cmd-annotate.in",
         "test/cmd-update.ok",
         "test/cmd-vimdiff-full.in",
         "test/cmd-vimdiff-full.ok",
-        "test/cmd-vimdiff.in",
-        "test/cmd-vimdiff.ok",
         "test/cmdaus.in",
         "test/cmdaus.ok",
         "test/copyout-postproc.zsh",
+        "test/cptowine.zsh",
         "test/creategitrepo.zsh",
         "test/createhgrepo.zsh",
         "test/createsvnrepo.zsh",
         "test/hgtestrepo.tar.xz",
         "test/maps-annotate.in",
         "test/maps-annotate.ok",
+        "test/maps-commit-diff.in",
+        "test/maps-commit-diff.ok",
         "test/maps-diff.in",
         "test/maps-diff.ok",
         "test/maps-file.in",
         "test/maps-file.ok",
         "test/maps-log.in",
         "test/maps-log.ok",
+        "test/maps-record-undo.in",
+        "test/maps-record-undo.ok",
         "test/maps-status.in",
         "test/maps-status.ok",
         "test/opts-remembermsg.in",
         "test/util-add-cloned-repo.zsh",
         "test/util-add-modified-repo.zsh",
         "test/vimrc",
+        "test/wine/cmd+maps-vimdiff.ok",
         "test/wine/cmd-annotate-buffers.ok",
         "test/wine/cmd-annotate.ok",
         "test/wine/cmd-branch.ok",
         "test/wine/cmd-file.ok",
         "test/wine/cmd-grep.ok",
         "test/wine/cmd-record.ok",
-        "test/wine/cmd-vimdiff.ok",
+        "test/wine/cmd-vimdiff-full.ok",
         "test/wine/maps-annotate.ok",
         "test/wine/maps-diff.ok",
         "test/wine/maps-file.ok",
         "test/wine/maps-log.ok",
         "test/wine/maps-status.ok",
         "test/wine/opts-vimdiffusewin.ok",
-        "test/wine/vimdiff-full.ok"
+        "test/wine/regression-vimdiff-nodiffoff.ok"
     ],
 }

File autoload/Powerline/Functions/aurum.vim

-execute frawor#Setup('0.0', {'autoload/aurum': '0.1'})
+execute frawor#Setup('0.0', {'@%aurum': '0.1',
+            \        '@%aurum/bufvars': '0.0',})
 function Powerline#Functions#aurum#GetBranch(symbol)
     let r=aurum#branch()
     return empty(r) ? '' : a:symbol.' '.r
     return (empty(r) || r is# 'clean') ? '' : toupper(r[0])
 endfunction
 let s:_functions+=['Powerline#Functions#aurum#GetStatus']
+function Powerline#Functions#aurum#GetRepoPath()
+    let repo=aurum#repository()
+    return empty(repo) ? '' : fnamemodify(repo.path, ':~')
+endfunction
+let s:_functions+=['Powerline#Functions#aurum#GetRepoPath']
+function Powerline#Functions#aurum#GetOptions()
+    return get(get(s:_r.bufvars, bufnr('%'), {}), 'ploptions', '')
+endfunction
+let s:_functions+=['Powerline#Functions#aurum#GetOptions']

File autoload/Powerline/Matches.vim

+if exists('s:loading')
+	finish
+endif
+let s:loading=1
+call extend(g:Powerline#Matches#matches, {
+	\'ft_aurumstatus':   Pl#Match#Add('getwinvar(a:window, "&ft")', '^aurumstatus$'  ),
+	\'ft_aurumannotate': Pl#Match#Add('getwinvar(a:window, "&ft")', '^aurumannotate$'),
+\})
+unlet s:loading
+" vim: ft=vim ts=4 sts=4 sw=4 noet

File autoload/Powerline/Segments/aurum.vim

-let g:Powerline#Segments#aurum#segments = Pl#Segment#Init(['aurum',
+let s:seg=expand('<sfile>:t:r')
+let g:Powerline#Segments#{s:seg}#segments = Pl#Segment#Init([s:seg,
             \ 1,
-            \ Pl#Segment#Create('branch', '%{Powerline#Functions#aurum#GetBranch("$BRANCH")}'),
-            \ Pl#Segment#Create('status', '%{Powerline#Functions#aurum#GetStatus()}'),
+            \ Pl#Segment#Create('branch',     '%{Powerline#Functions#aurum#GetBranch("$BRANCH")}'),
+            \ Pl#Segment#Create('status',     '%{Powerline#Functions#aurum#GetStatus()}'),
+            \ Pl#Segment#Create('repository', '%{Powerline#Functions#aurum#GetRepoPath()}'),
+            \ Pl#Segment#Create('options',    '%{Powerline#Functions#aurum#GetOptions()}'),
             \])

File autoload/Powerline/Themes/parts/aurum.vim

+source <sfile>:h:h:h/Matches.vim
+let Powerline#Themes#parts#aurum#part=[
+    \Pl#Theme#Buffer('ft_aurumstatus'
+            \ , ['static_str.name', 'Status']
+            \ , Pl#Segment#Truncate()
+            \ , 'aurum:repository'
+            \ , Pl#Segment#Split()
+            \ , 'aurum:options'
+    \),
+    \
+    \Pl#Theme#Buffer('ft_aurumannotate'
+            \ , ['static_str.name', 'Ann']
+            \ , Pl#Segment#Truncate()
+            \ , Pl#Segment#Split()
+            \ , 'aurum:options'
+    \),
+\]

File autoload/aurum.vim

 "▶1 
 scriptencoding utf-8
-execute frawor#Setup('0.1', {'@%aurum/repo': '5.0',
-            \                '@aurum/cache': '2.0',
+execute frawor#Setup('0.1', {'@%aurum/repo': '5.4',
+            \                '@aurum/cache': '2.3',
             \            '@%aurum/cmdutils': '4.0',})
 "▶1 getcrf
 function s:F.id(val)
     endif
     return [cbvar, repo, file]
 endfunction
+"▶1 _unload
+function s:._unload()
+    for adesc in values(s:akeys)
+        call adesc.repo.functions.aremove(adesc.repo, adesc.rcid)
+    endfor
+endfunction
+"▶1 anew
+let s:abuffers={}
+let s:akeys={}
+let s:astarted=0
+function s:F.anew(buf, repo, key, func, ...)
+    if !s:astarted
+        let s:astarted=1
+        lockvar s:astarted
+        call s:_f.addwiper(s:F.aupdate)
+    endif
+    if !has_key(s:abuffers, a:buf)
+        let s:abuffers[a:buf]={}
+    endif
+    let key=a:func.string(a:000).a:repo.path
+    if !has_key(s:akeys, key)
+        let interval=s:_r.cache.getinterval(a:key)
+        let rcid=call(a:repo.functions['a'.a:func], [a:repo,interval]+a:000, {})
+        let s:akeys[key]={'repo': a:repo, 'rcid': rcid, 'buffers': {a:buf : 1},
+                    \      'key': key,    'ckey': a:key}
+    else
+        let s:akeys[key].buffers[a:buf]=1
+    endif
+    augroup AuRemoveRC
+        autocmd! BufWipeOut,BufFilePost <buffer> call s:F.abw(+expand('<abuf>'))
+    augroup END
+    let s:abuffers[a:buf][a:key]=s:akeys[key]
+    return s:F.aget(s:akeys[key], 1)
+endfunction
+let s:_augroups+=['AuRemoveRC']
+"▶1 aget
+function s:F.aget(adesc, now)
+    return a:adesc.repo.functions.aget(a:adesc.repo, a:adesc.rcid, a:now)
+endfunction
+"▶1 abw
+function s:F.abw(buf)
+    if has_key(s:abuffers, a:buf)
+        for adesc in values(remove(s:abuffers, a:buf))
+            unlet adesc.buffers[a:buf]
+            if empty(adesc.buffers)
+                unlet s:akeys[adesc.key]
+                call adesc.repo.functions.aremove(adesc.repo, adesc.rcid)
+            endif
+        endfor
+        augroup AuInvalidateStatusCache
+            execute 'autocmd! BufWritePost <buffer='.a:buf.'>'
+        augroup END
+    endif
+endfunction
+"▶1 aupdate
+function s:F.aupdate(key)
+    call map(values(s:abuffers),
+                \'has_key(v:val, a:key) ? s:F.aget(v:val[a:key], 1) : 0')
+endfunction
+"▶1 aswitch
+function s:F.aswitch()
+    let bl=tabpagebuflist()
+    call map(filter(bl, 'has_key(s:abuffers, v:val)'), '"".v:val')
+    for adesc in filter(values(s:akeys), 'empty(filter(keys(v:val.buffers), '.
+                \                                     '"index(bl,v:val)!=-1"))')
+        call adesc.repo.functions.aremove(adesc.repo, adesc.rcid)
+        unlet s:akeys[adesc.key]
+        call map(keys(adesc.buffers), 'remove(s:abuffers[v:val], adesc.ckey)')
+    endfor
+endfunction
+augroup AuRCSwitchBuffers
+    autocmd BufEnter * :call s:F.aswitch()
+augroup END
+let s:_augroups+=['AuRCSwitchBuffers']
 "▶1 aurum#repository
 function aurum#repository()
     " TODO Path instead of buffer cache to reduce number of shell calls
     return s:_r.cache.get('cs', repo.functions.getwork, [repo], {})
 endfunction
 let s:_functions+=['aurum#changeset']
+"▶1 filestatus
+function s:F.filestatus(status)
+    return get(keys(filter(copy(a:status), '!empty(v:val)')), 0, '')
+endfunction
 "▶1 aurum#status
 function aurum#status(...)
     if !empty(&buftype)
         return ''
     endif
+    let buf=bufnr('%')
+    if has_key(s:abuffers, buf) && has_key(s:abuffers[buf], 'status')
+        return s:F.filestatus(s:F.aget(s:abuffers[buf].status, 0))
+    endif
     let [cbvar, repo, file]=s:F.getcrf()
     if repo is 0 || file is 0
         return ''
     endif
+    if has_key(repo.functions, 'astatus')
+        augroup AuInvalidateStatusCache
+            autocmd! BufWritePost <buffer>
+                        \ :call s:F.aget(s:abuffers[expand('<abuf>')].status, 1)
+        augroup END
+        return s:F.filestatus(s:F.anew(buf, repo, 'status', 'status',
+                    \                  0, 0, [file], 1, 1))
+    endif
     augroup AuInvalidateStatusCache
         autocmd! BufWritePost <buffer> :call s:_r.cache.del('status')
     augroup END
-    return get(keys(filter(copy(s:_r.cache.get('status', repo.functions.status,
-                \                              [repo, 0, 0, [file], 1, 1], {})),
-                \          'index(v:val, file)!=-1')), 0, '')
+    return s:F.filestatus(s:_r.cache.get('status', repo.functions.status,
+                \                        [repo, 0, 0, [file], 1, 1], {}))
 endfunction
 let s:_functions+=['aurum#status']
 let s:_augroups+=['AuInvalidateStatusCache']
 "▶1 aurum#branch
 function aurum#branch(...)
+    let buf=bufnr('%')
+    if has_key(s:abuffers, buf) && has_key(s:abuffers[buf], 'branch')
+        return s:F.aget(s:abuffers[buf].branch, 0)
+    endif
     let repo=((a:0)?(a:1):(aurum#repository()))
     if empty(repo)
         return ''
     endif
+    if has_key(repo.functions, 'agetrepoprop')
+        return s:F.anew(buf, repo, 'branch', 'getrepoprop', 'branch')
+    endif
     return s:_r.cache.get('branch', repo.functions.getrepoprop,
                 \         [repo, 'branch'], {})
 endfunction
 let s:_functions+=['aurum#branch']
 "▶1
-call frawor#Lockvar(s:, '_pluginloaded,_r')
+call frawor#Lockvar(s:, '_r,abuffers,akeys,astarted')
 " vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File autoload/aurum/annotate.vim

 scriptencoding utf-8
 execute frawor#Setup('1.0', {'@%aurum/cmdutils': '4.0',
             \                 '@%aurum/bufvars': '0.0',
-            \                    '@%aurum/edit': '1.0',
+            \                    '@%aurum/edit': '1.4',
             \                          '@aurum': '1.0',
             \                     '@/resources': '0.0',
             \                         '@/table': '0.1',})
         execute 'autocmd BufWipeOut,BufHidden <buffer='.a:annbuf.'> '.
                     \':if bufexists('.buf.') | '.
                     \   'call feedkeys("\<C-\>\<C-n>'.
-                    \                 ':silent! bw '.buf.'\n") | '.
+                    \                 ':silent! bw '.buf.'\n\<C-l>") | '.
                     \ 'endif'
     augroup END
 endfunction
     call s:F.setannbuf(s:_r.bufvars[bufnr('%')], annbuf)
 endfunction
 let s:_augroups+=['AuAnnotateBW']
+"▶1 plstrgen
+function s:F.plstrgen(bvar)
+    let cs=a:bvar.repo.functions.getcs(a:bvar.repo, a:bvar.rev)
+    let r=[cs.rev]
+    if !empty(get(cs, 'tags'))
+        let r+=['['.join(cs.tags).']']
+    endif
+    if !empty(get(cs, 'bookmarks'))
+        let r+=['['.join(cs.bookmarks).']']
+    endif
+    if cs.branch isnot# 'default'
+        let r+=['('.(cs.branch).')']
+    endif
+    return join(r)
+endfunction
 "▶1 aurum://annotate
 call s:_f.newcommand({'function': s:F.setup,
             \        'arguments': 2,
-            \         'filetype': 'aurumannotate',})
+            \         'filetype': 'aurumannotate',
+            \         'plstrgen': s:F.plstrgen,})
 "▶1 Post resource
 call s:_f.postresource('annotate', {'setannbuf': s:F.setannbuf,
             \                        'foldopen': s:F.foldopen,})

File autoload/aurum/bufvars.vim

             call feedkeys("\<C-\>\<C-n>:call ".
                     \      "call(".eval."('s:bufvars[".buf."].bwfunc'), ".
                     \           "[".eval."('s:bufvars[".buf."]')], {}) | ".
-                    \"call ".eval."('remove(s:bufvars, ".buf.")')\n", 'n')
+                    \"call ".eval."('remove(s:bufvars, ".buf.")')\n\<C-l>", 'n')
             return
         endif
         unlet s:bufvars[buf]

File autoload/aurum/commit.vim

 "▶1 
 scriptencoding utf-8
-execute frawor#Setup('1.0', {'@/resources': '0.0',
+execute frawor#Setup('1.3', {'@/resources': '0.0',
             \                  '@/options': '0.0',
+            \                       '@/os': '0.0',
             \                     '@aurum': '1.0',
-            \             '@%aurum/status': '1.0',
+            \             '@%aurum/status': '1.2',
             \           '@%aurum/cmdutils': '4.0',
             \            '@%aurum/bufvars': '0.0',
+            \            '@%aurum/vimdiff': '1.1',
             \               '@%aurum/edit': '1.0',
             \               '@aurum/cache': '2.1',})
 let s:_messages={
 let s:_options={
             \'remembermsg':         {'default': 1, 'filter': 'bool'},
             \'bufleaveremembermsg': {'default': 1, 'filter': 'bool'},
+            \'commitautoopendiff':  {'default': 0, 'filter': 'bool'},
+            \'commitinfowincmd':    {
+            \   'default': 'largest_adjacent',
+            \   'checker': 'match /\v^%([jkhlwWtbpvs]|'.
+            \                          'largest%(_%(vertical|'.
+            \                                      'horizontal|'.
+            \                                      'adjacent))?)$/'},
         \}
 "▶1 parsedate string → [year, month, day, hour, minute, second]
 " Date must have one of the following formats (XXX it is not validated):
     endif
     return r
 endfunction
-"▶1 commit :: repo, opts, files, status, types → + repo
+"▶1 vimdiffcb
+function s:F.vimdiffcb(file, bvar, hex)
+    execute 'silent tabnew'
+                \ fnameescape(s:_r.os.path.join(a:bvar.repo.path, a:file))
+    return s:_r.vimdiff.split([['file', a:bvar.repo, a:hex, a:file]], 0)
+endfunction
+"▶1 findwindow :: () -> winexisted::Bool
+function s:F.winsize(wnr)
+    let width=winwidth(a:wnr)
+    return (width>80 ? 80 : width)*winheight(a:wnr)
+endfunction
+function s:F.winsplit()
+    if winwidth(0)>=&textwidth*2
+        execute 'silent' (winwidth(0)-&textwidth)                'vsplit'
+    else
+        execute 'silent' (max([winheight(0)-5, winheight(0)/2]))  'split'
+    endif
+    return 0
+endfunction
+function s:F.findwindow()
+    let wcommand=s:_f.getoption('commitinfowincmd')
+    if len(wcommand)==1
+        let commit_mark=reltime()
+        let wscope=w:
+        let w:aurum_commit_mark=commit_mark
+        try
+            execute 'wincmd' wcommand
+            if              exists('w:aurum_commit_mark')
+                        \&& w:aurum_commit_mark is# commit_mark
+                return s:F.winsplit()
+            endif
+            return (stridx('sv', wcommand)==-1)
+        finally
+            call remove(wscope, 'aurum_commit_mark')
+        endtry
+    elseif wcommand is# 'largest'
+        let maxsize=0
+        let mwnr=0
+        for wnr in filter(range(1, winnr('$')), 'v:val!='.winnr())
+            let size=s:F.winsize(a:wnr)
+            if size>maxsize
+                let maxsize=size
+                let mwnr=a:wnr
+            endif
+        endfor
+        if mwnr
+            execute mwnr.'wincmd w'
+            return 1
+        else
+            return s:F.winsplit()
+        endif
+    else
+        let maxsize=0
+        let mwcmd=0
+        let wcmds={
+                    \'vertical':   ['j', 'k'],
+                    \'horizontal': ['h', 'l'],
+                    \'adjacent':   ['h', 'l', 'j', 'k'],
+                \}[wcommand[8:]]
+        let curwin=winnr()
+        for wcmd in wcmds
+            execute 'wincmd' wcmd
+            if winnr()!=curwin
+                let size=s:F.winsize(0)
+                wincmd p
+                if size>maxsize
+                    let maxsize=size
+                    let mwcmd=wcmd
+                endif
+            endif
+        endfor
+        if mwcmd is 0
+            return s:F.winsplit()
+        else
+            execute 'wincmd' mwcmd
+            return 1
+        endif
+    endif
+endfunction
+"▶1 commit :: repo, opts, files, status, types[, cmd[, bvarpart]] → + repo
 let s:defdate=['strftime("%Y")',
             \  'strftime("%m")',
             \  'strftime("%d")',
 let s:statmsgs.deleted=s:statmsgs.removed
 " TODO Investigate why closing commit buffer on windows consumes next character
 " XXX Do not change names of options used here, see :AuRecord
-function s:F.commit(repo, opts, files, status, types)
+function s:F.commit(repo, opts, files, status, types, ...)
     let user=''
     let date=''
     let message=''
     if !empty(a:files)
         call filter(revstatus, 'index(a:files, v:key)!=-1')
     endif
+    if empty(revstatus)
+        call s:_f.throw('nocom')
+    endif
     for key in filter(['user', 'date', 'message'], 'has_key(a:opts, v:val)')
         let l:{key}=a:opts[key]
     endfor
     endif
     "▲2
     if empty(message)
-        call s:_r.run('silent new', 'commit', a:repo, user, date, cb, a:files)
+        call s:_r.run(((a:0 && a:1 isnot 0)? a:1 : 'silent new'),
+                    \ 'commit', a:repo, user, date, cb, a:files)
+        let bvar=s:_r.bufvars[bufnr('%')]
+        if a:0>1 && a:2 isnot 0
+            call extend(bvar, a:2)
+        endif
+        let bvar.revstatus=revstatus
+        "▶2 Add previous message
         if exists('g:AuPreviousRepoPath') &&
                     \   g:AuPreviousRepoPath is# a:repo.path &&
                     \exists('g:AuPreviousTip') &&
             call cursor(line('$'), col([line('$'), '$']))
             unlet g:AuPreviousRepoPath g:AuPreviousTip g:AuPreviousCommitMessage
         endif
+        "▶2 Add comment
         let fmessage=[]
         for [file, state] in items(revstatus)
             let fmessage+=['# '.s:statmsgs[state].' '.file]
         call sort(fmessage)
         call append('.', fmessage)
         startinsert!
+        "▶2 Open diff
+        if s:_f.getoption('commitautoopendiff')
+            let winexisted=bvar.findwindow()
+            if winexisted
+                let prevbuf=bufnr('%')
+            else
+                let prevbuf=0
+            endif
+            let existed=s:_r.mrun('silent edit', 'diff', bvar.repo, 0, 0,
+                        \                                keys(revstatus), {})
+            if !existed
+                setlocal bufhidden=wipe
+            endif
+            let dbuf=bufnr('%')
+            let dtabpagenr=tabpagenr()
+            silent! %foldopen!
+            wincmd p
+            augroup AuCommitAutoCloseDiff
+                execute 'autocmd BufWipeOut <buffer> '.
+                            \':call s:F.closediffbuf('.dbuf.', '.
+                            \                          dtabpagenr.', '.
+                            \                          prevbuf.', '
+                            \                          existed.', '.
+                            \                          winexisted.')'
+            augroup END
+        endif
+        "▲2
         return 0
     else
         call a:repo.functions.commit(a:repo, message, a:files, user, date, cb)
         return 1
     endif
 endfunction
+let s:_augroups+=['AuCommitAutoCloseDiff']
+"▶1 closediffbuf
+function s:F.closediffbuf(dbuf, dtabpagenr, prevbuf, existed, winexisted)
+    if bufexists(a:dbuf) && count(tabpagebuflist(a:dtabpagenr), a:dbuf)==1
+        let cmds=[]
+        let switchcmds=[]
+        if (a:prevbuf && bufexists(a:prevbuf)) || a:winexisted
+            let curtab=tabpagenr()
+            if curtab!=a:dtabpagenr
+                let cmds+=['tabnext '.a:dtabpagenr]
+                call insert(switchcmds, 'tabnext '.curtab)
+            endif
+            let cmds+=['execute bufwinnr('.a:dbuf.') "wincmd w"']
+            call insert(switchcmds, 'wincmd p')
+            if a:prevbuf && bufexists(a:prevbuf)
+                let cmds+=['buffer '.a:prevbuf]
+            else
+                let cmds+=['bnext']
+            endif
+        endif
+        if !a:existed
+            let cmds+=['if bufexists('.a:dbuf.')', 'bwipeout '.a:dbuf, 'endif']
+        endif
+        let cmds+=switchcmds
+        return feedkeys("\<C-\>\<C-n>:".join(cmds, '|')."\n\<C-l>", 'n')
+    endif
+endfunction
 "▶1 savemsg :: message, bvar → + g:
 function s:F.savemsg(message, bvar)
     if a:message!~#"[^[:blank:]\n]"
     call a:bvar.repo.functions.commit(a:bvar.repo, message, a:bvar.files,
                 \                     a:bvar.user, a:bvar.date,
                 \                     a:bvar.closebranch)
+    call map(copy(s:_r.allcachekeys), 's:_r.cache.wipe(v:val)')
     let a:bvar.did_message=1
-    call feedkeys("\<C-\>\<C-n>:bwipeout!\n")
+    if has_key(a:bvar, 'sbvar')
+        call a:bvar.bwfunc(a:bvar)
+        let a:bvar.bwfunc=a:bvar.sbvar.bwfunc
+    endif
+    call feedkeys("\<C-\>\<C-n>:bwipeout!\n\<C-l>")
 endfunction
 "▶1 commfunc
 function s:cmd.function(opts, ...)
     augroup END
     return {'user': a:user, 'date': a:date, 'files': a:files,
                 \'closebranch': !!a:cb, 'write': s:F.finish,
-                \'did_message': 0}
+                \'did_message': 0, 'vimdiffcb': s:F.vimdiffcb,
+                \'findwindow': s:F.findwindow,}
 endfunction
 let s:_augroups+=['AuCommit']
 function s:commit.write(lines, repo, user, date, cb, files)

File autoload/aurum/drivers/common/hypsites.vim

 "  https://vimpluginloader.svn.sourceforge.net/svnroot/vimpluginloader
 "  http://conque.googlecode.com/svn/trunk
 "  https://zyx.repositoryhosting.com/svn/zyx_t1 / svn+ssh://svn@zyx.repositoryhosting.com/zyx/t1
+"  http://pysvn.tigris.org/svn/pysvn/trunk
 let s:svngcbase='"http://code.google.com/p/".'.s:gcproj
 let s:svngcfile='path[5:]."/".file'
 let s:hyp.svn=[
 \        'log': '"http://".domain."/viewvcs".path[4:]."?view=log"',
 \      'clone': '"svn://".domain.path',
 \       'push': '"svn+ssh://".user."@".domain.path',}],
+\['domain =~? "\\Vtigris.org\\$"',
+\ {     'html': '"http://".domain."/source/browse".path[4:]."/".file."?view=markup&revision=".hex',
+\        'raw': '"http://".domain."/source/browse/*checkout*".path[4:]."/".file."?revision=".hex',
+\   'annotate': '"http://".domain."/source/browse".path[4:]."/".file."?annotate=".hex', 'aline': '"id".(line-1)',
+\   'filehist': '"http://".domain."/source/browse".path[4:]."/".file."?view=log"',
+\      'clone': '"http://".domain.path',
+\       'push': 'url',}],
 \['domain =~? "\\Vrepositoryhosting.com\\$" && protocol is? "https" && path[:3] is? "/svn"', s:rhdicts.svn.0],
 \['domain =~? "\\Vrepositoryhosting.com\\$" && protocol is? "svn+ssh"',                      s:rhdicts.svn.1],
 \]

File autoload/aurum/drivers/git.vim

             \ 'invrng': 'Range %s..%s is invalid for the repository %s, '.
             \           'as well as reverse',
             \    'ppf': 'Failed to run “git %s” for the repository %s: %s',
+            \'anbnimp': 'Can only get branch property using agetrepoprop',
+            \'aconimp': 'Can only get current status for one file',
         \}
 let s:git={}
 let s:_options={
             let files[file]=1
             if status[0] is# 'R'
                 let r.added+=[file]
-                let r.removed+=[remove(s, 0)]
+                let old=remove(s, 0)
+                if (a:0>2 && !empty(a:3)) ? (index(a:3, old)!=-1) : 1
+                    let r.removed+=[old]
+                endif
             elseif status[0] is# 'C'
                 let r.added+=[file]
-                let origfile=remove(s, 0)
-                " FIXME What should be done with origfile?
+                call remove(s, 0)
             elseif status[0] is# 'D'
                 let r.removed+=[file]
             elseif status[1] is# 'D'
 function s:git.grep(repo, pattern, files, revisions, ic, wdfiles)
     let args=['-e', a:pattern, '--']+a:files
     let kwargs={'full-name': 1, 'extended-regexp': 1, 'n': 1, 'z': 1}
-    let gitargs=[a:repo, 'grep', args, kwargs, 1]
     let r=[]
     if !empty(a:revisions)
         let revs=[]
             unlet s
         endfor
         call extend(args, revs, 2)
-        for [revfile, lnum, text] in s:F.parsegrep(call(s:F.git, gitargs, {}))
+    endif
+    let [lines, exit_code]=s:F.git(a:repo, 'grep', args, kwargs, 1, 0)
+    if exit_code
+        " Grep failed because it has found nothing
+        if lines ==# ['']
+            return []
+        " Grep failed for another reason
+        else
+            call s:_f.throw('grepf', a:repo.path, join(lines, "\n"))
+        endif
+    endif
+    if empty(a:revisions)
+        for [file, lnum, text] in s:F.parsegrep(lines)
+            let r+=[{'filename': file, 'lnum': lnum, 'text': text}]
+        endfor
+    else
+        for [revfile, lnum, text] in s:F.parsegrep(lines)
             let cidx=stridx(revfile, ':')
             let rev=revfile[:(cidx-1)]
             let file=revfile[(cidx+1):]
             let r+=[{'filename': [rev, file], 'lnum': lnum, 'text': text}]
         endfor
-    else
-        for [file, lnum, text] in s:F.parsegrep(call(s:F.git, gitargs, {}))
-            let r+=[{'filename': file, 'lnum': lnum, 'text': text}]
-        endfor
     endif
     return r
 endfunction
                     \        'branchf')[:-2]
         return get(filter(branches, 'v:val[0] is# "*"'), 0, '')[2:]
     elseif a:prop is# 'url'
-        let [r, exit_code]=get(s:F.git(a:repo, 'config',
-                    \                  ['remote.origin.pushurl'], {}, 0),
-                    \          0, 0)
+        let [l, exit_code]=s:F.git(a:repo, 'config',
+                    \              ['remote.origin.pushurl'], {}, 0, 0)
+        let r=get(l, 0, 0)
         if exit_code || r is 0
             let r=get(s:F.git(a:repo, 'config', ['remote.origin.url'], {}, 0),
                         \0, 0)
 endfunction
 "▶1 pushpull :: cmd, repo, dryrun, force[, URL[, rev]] → + ?
 function s:F.pushpull(cmd, repo, dryrun, force, ...)
-    let kwargs={'all': 1}
+    let kwargs1={}
+    let kwargs2={'all': 1}
+    let args=[]
+    let cmd=a:cmd
     let args=[]
     if a:0
         if a:1 isnot 0
             let args+=[a:2]
         endif
     endif
-    if (a:cmd is# 'fetch' && !empty(args)) || len(args)>2
-        unlet kwargs.all
+    if (a:cmd is# 'pull' && !empty(args)) || len(args)>2
+        unlet kwargs2.all
     endif
     if a:force
-        let kwargs.force=1
+        let kwargs2.force=1
     endif
     if a:dryrun
-        let kwargs['dry-run']=1
+        if cmd is# 'pull'
+            let cmd='fetch'
+        endif
+        let kwargs2['dry-run']=1
     endif
-    return s:F.git(a:repo, a:cmd, args, kwargs, 0, 'ppf', a:cmd)
+    if a:cmd is# 'pull'
+        let kwargs1['ff-only']=1
+    endif
+    let args=s:_r.utils.kwargstolst(kwargs1)+s:_r.utils.kwargstolst(kwargs2)
+                \+args
+    return s:F.gitm(a:repo, a:cmd, args, {}, 0, 'ppf', a:cmd)
 endfunction
 "▶1 git.push :: repo, dryrun, force[, URL[, rev]]
 function s:git.push(...)
 endfunction
 "▶1 git.pull :: repo, dryrun, force[, URL[, rev]]
 function s:git.pull(...)
-    return call(s:F.pushpull, ['fetch']+a:000, {})
+    return call(s:F.pushpull, ['pull']+a:000, {})
 endfunction
 "▶1 git.repo :: path → repo
 function s:git.repo(path)
 endfunction
 let s:iterfuncs.changesets=s:iterfuncs.revrange
 let s:iterfuncs.ancestors=s:iterfuncs.revrange
+"▶1 astatus, agetcs, agetrepoprop
+if s:_r.repo.userepeatedcmd
+    try
+        python import aurum.rcdriverfuncs
+        let s:addafuncs=1
+    catch
+        let s:addafuncs=0
+    endtry
+    if s:addafuncs
+        function s:git.astatus(repo, interval, ...)
+            if a:0<3 || a:1 isnot 0 || a:2 isnot 0 ||
+                        \type(a:3)!=type([]) || len(a:3)!=1
+                call s:_f.throw('aconimp')
+            endif
+            return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                        \       'aurum.rcdriverfuncs.git_status, '.
+                        \       'vim.eval("a:repo.path"), '.
+                        \       'vim.eval("a:3[0]"))')
+        endfunction
+        function s:git.agetrepoprop(repo, interval, prop)
+            if a:prop isnot# 'branch'
+                call s:_f.throw('anbnimp')
+            endif
+            return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                        \       'aurum.rcdriverfuncs.git_branch, '.
+                        \       'vim.eval("a:repo.path"))')
+        endfunction
+    endif
+endif
 "▶1 Register driver
 call s:_f.regdriver('Git', s:git)
 "▶1

File autoload/aurum/drivers/mercurial.vim

             \  'nocfg': 'No such property of repository %s: %s',
             \'failcfg': 'Failed to get property %s of repository %s',
             \'nlocbms': 'Bookmarks can’t be local',
+            \'anbnimp': 'Can only get branch property using agetrepoprop',
             \'parsefail': 'Failed to parse changeset information',
             \ 'filefail': 'Failed to get file %s '.
             \             'from the repository %s: %s',
 " type :: "modified" | "added" | "removed" | "deleted" | "unknown" | "ignored"
 "       | "clean"
 if s:usepythondriver "▶2
+let s:revargsexpr='v:val is 0? '.
+                \       '"None":'.
+                \ 'v:key>=3?'.
+                \       '(empty(v:val)?"False":"True"):'.
+                \       '"vim.eval(''a:".(v:key+1)."'')"'
 function s:hg.status(repo, ...)
-    let revargs=join(map(copy(a:000), 'v:val is 0? '.
-                \                           '"None":'.
-                \                     '(v:key>3 && v:val is 1)?'.
-                \                           '"True":'.
-                \                           '"vim.eval(''a:".(v:key+1)."'')"'),
-                \    ',')
+    let revargs=join(map(copy(a:000), s:revargsexpr), ',')
     let d={}
     try
         execute s:pya.'get_status(vim.eval("a:repo.path"), '.revargs.')'
             \'I': 'ignored',
             \'C': 'clean',
         \}
-" TODO test whether zero revision may cause bugs in some commands
-function s:hg.status(repo, ...)
+function s:F.statargs(...)
     let args=[]
     let kwargs={'modified': 1,
                 \  'added': 1,
             let args+=['--']+a:3
         endif
     endif
+    return [args, kwargs, reverse]
+endfunction
+" TODO test whether zero revision may cause bugs in some commands
+function s:hg.status(repo, ...)
+    let [args, kwargs, reverse]=call(s:F.statargs, a:000, {})
     let slines=s:F.hg(a:repo, 'status', args, kwargs, 0, 'stat')[:-2]
     if !empty(filter(copy(slines), '!has_key(s:statchars, v:val[0])'))
         call s:_f.throw('statfail', a:repo.path, join(slines, "\n"))
             \    'dates': 'nodates',
         \}
 if s:usepythondriver "▶2
+function s:F.getdiffargs(repo, rev1, rev2, files, opts)
+    return       'vim.eval("a:repo.path"), '.
+                \(empty(a:rev1)  ? 'None' : 'vim.eval("a:rev1")' ).', '.
+                \(empty(a:rev2)  ? 'None' : 'vim.eval("a:rev2")' ).', '.
+                \(empty(a:files) ? '[]'   : 'vim.eval("a:files")').', '.
+                \'vim.eval("diffopts")'
+endfunction
 function s:hg.diff(repo, rev1, rev2, files, opts)
     let r=[]
     let diffopts=s:_r.utils.diffopts(a:opts, a:repo.diffopts, s:difftrans)
     try
-        execute s:pya.'diff(vim.eval("a:repo.path"), '.
-                    \      'vim.eval("a:rev1"), '.
-                    \      'vim.eval("a:rev2"), '.
-                    \      'vim.eval("a:files"), '.
-                    \      'vim.eval("diffopts"))'
+        execute s:pya.'diff('.s:F.getdiffargs(a:repo, a:rev1, a:rev2, a:files,
+                    \                         a:opts).')'
     endtry
     return r
 endfunction
     endif
     try
         let diffopts=s:_r.utils.diffopts(a:opts, a:repo.diffopts, s:difftrans)
-        execute s:pya.'diffToBuffer(vim.eval("a:repo.path"), '.
-                    \              'vim.eval("a:rev1"), '.
-                    \              'vim.eval("a:rev2"), '.
-                    \              'vim.eval("a:files"), '.
-                    \              'vim.eval("diffopts"))'
+        execute s:pya.'diffToBuffer('.s:F.getdiffargs(a:repo, a:rev1, a:rev2,
+                    \                                 a:files, a:opts).')'
     finally
         if oldbuf!=a:buf
             execute 'buffer' oldbuf
     elseif a:cmd is# 'push'
         let kwargs['new-branch']=1
     endif
+    if a:cmd is# 'pull'
+        let kwargs.update=1
+    endif
     return s:F.runcmd(a:repo, a:cmd, args, kwargs)
 endfunction
 "▶1 hg.push :: repo, dryrun, force[, URL[, rev]]
     endif
     return cs
 endfunction
+"▶1 astatus, agetcs, agetrepoprop
+if s:_r.repo.userepeatedcmd
+    if s:usepythondriver
+        function s:hg.astatus(repo, interval, ...)
+            let args = string(a:interval).', '.s:pp.'._get_status, '.
+                        \'vim.eval("a:repo.path"), '
+            let args.= join(map(copy(a:000), s:revargsexpr), ',')
+            return pyeval('aurum.repeatedcmd.new('.args.')')
+        endfunction
+        function s:hg.agetcs(repo, interval, rev)
+            return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                        \                        s:pp.'._get_cs, '.
+                        \                       'vim.eval("a:repo.path"), '.
+                        \                       'vim.eval("a:rev"))')
+        endfunction
+        function s:hg.agetrepoprop(repo, interval, prop)
+            return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                        \                        s:pp.'.get_one_prop, '.
+                        \                       'vim.eval("a:repo.path"), '.
+                        \                       'vim.eval("a:prop"))')
+        endfunction
+    else
+        try
+            python import aurum.rcdriverfuncs
+            let s:addafuncs=1
+        catch
+            let s:addafuncs=0
+        endtry
+        if s:addafuncs
+            function s:hg.astatus(repo, interval, ...)
+                let [args, kwargs, reverse]=call(s:F.statargs, a:000, {})
+                let arglist=s:_r.utils.kwargstolst(kwargs)+args
+                return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                            \       'aurum.rcdriverfuncs.hg_status, '.
+                            \       'vim.eval("a:repo.path"), '.
+                            \       'vim.eval("arglist"), '.
+                            \        (reverse ? 'True' : 'False').')')
+            endfunction
+            function s:hg.agetrepoprop(repo, interval, prop)
+                if a:prop isnot# 'branch'
+                    call s:_f.throw('anbnimp')
+                endif
+                return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                            \       'aurum.rcdriverfuncs.hg_branch, '.
+                            \       'vim.eval("a:repo.path"))')
+            endfunction
+        endif
+    endif
+endif
 "▶1 hg.svnrev :: repo, rev → svnrev
 function s:hg.svnrev(repo, rev)
     let lines=s:F.hg(a:repo, 'svn', ['info'], {'rev': ''.a:rev}, 0, 'svn')
 "▶1 hg.gitrev :: repo, rev → githex
 if s:usepythondriver
 function s:hg.githex(repo, rev)
-    execute s:pya.'git_hash(vim.eval("a:repo.path"), vim.eval("a:rev"))'
+    let d={}
+    try
+        execute s:pya.'git_hash(vim.eval("a:repo.path"), vim.eval("a:rev"))'
+    endtry
+    return d.git_hex
 endfunction
 endif
 "▶1 Register driver

File autoload/aurum/drivers/subversion.vim

             \    'cif': 'Failed to commit changes to the repository %s: %s',
             \   'updf': 'Failed to update to revision %s '.
             \           'in the repository %s: %s',
+            \  'pullf': 'Failed to update 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',
             \           'after “====<…>====” separator line',
             \ 'punimp': 'Cannot “pull” from non-default location',
             \'pulnimp': 'Cannot pull: use update instead',
+            \'aconimp': 'Can only get current status for one file',
         \}
 let s:svn={}
 let s:iterfuncs={}
         if type(a:1)==type(0)
             let kwargs.limit=''.a:1
         else
-            let kwargs.revision=a:1
+            let kwargs.revision=''.a:1
         endif
     elseif a:0==2
         let kwargs.revision=a:1.':'.a:2
         let kwargs.force=1
         let kwargs.accept='theirs-full'
     endif
-    let kwargs.revision=a:rev
+    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
     if a:dryrun
         return s:F.svnm(a:repo, 'log', [], {'revision': 'HEAD:BASE'}, 0)
     else
-        call s:_f.throw('pulnimp')
+        return s:F.svnm(a:repo, 'update', [], {'force': a:force}, 0, 'pullf')
     endif
 endfunction
 "▶1 svn.repo :: path → repo
                 \                                     a:opts.revrange[1]))
     return {'cslist': cslist, 'next': s:F.ancestorsnext}
 endfunction
+"▶1 astatus, agetcs, agetrepoprop
+if s:_r.repo.userepeatedcmd
+    try
+        python import aurum.rcdriverfuncs
+        let s:addafuncs=1
+    catch
+        let s:addafuncs=0
+    endtry
+    if s:addafuncs
+        function s:svn.astatus(repo, interval, ...)
+            if a:0<3 || a:1 isnot 0 || a:2 isnot 0 ||
+                        \type(a:3)!=type([]) || len(a:3)!=1
+                call s:_f.throw('aconimp')
+            endif
+            return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                        \       'aurum.rcdriverfuncs.svn_status, '.
+                        \       'vim.eval("a:repo.path"), '.
+                        \       'vim.eval("a:3[0]"))')
+        endfunction
+    endif
+endif
 "▶1 Register driver
 call s:_f.regdriver('Subversion', s:svn)
 "▶1

File autoload/aurum/edit.vim

 "▶1
 scriptencoding utf-8
-execute frawor#Setup('1.3', {'@/resources': '0.0',
+execute frawor#Setup('1.5', {'@/resources': '0.0',
+            \                       '@/os': '0.0',
             \               '@%aurum/repo': '5.0',
             \          '@%aurum/lineutils': '0.0',
             \            '@%aurum/bufvars': '0.0',
     return r
 endfunction
 "▶1 copy
-function s:F.copy(read, file)
+function s:F.copy(buf, read, file)
     call s:_r.lineutils.setlines(readfile(a:file, 'b'), a:read)
     if !a:read
-        let s:_r.bufvars[bufnr('%')]={'file': a:file, 'command': 'copy'}
+        let s:_r.bufvars[a:buf]={'file': a:file, 'command': 'copy'}
         if exists('#filetypedetect#BufRead')
             execute 'doautocmd filetypedetect BufRead' fnameescape(a:file)
         endif
     endif
 endfunction
 "▶1 edit
-function s:F.edit(rw, file)
+function s:F.ewrite(bvar, lines, file)
+    return writefile(a:lines, a:file, 'b')
+endfunction
+function s:F.edit(buf, rw, file)
     if a:rw>=0
-        call s:F.copy(a:rw, a:file)
+        call s:F.copy(a:buf, a:rw, a:file)
         if !a:rw
             setlocal buftype=acwrite nomodified modifiable noreadonly
-            let s:_r.bufvars[bufnr('%')]={'file': a:file, 'command': 'edit'}
+            let s:_r.bufvars[a:buf]={'file': a:file, 'command': 'edit',
+                        \            'write': s:F.ewrite}
         endif
     elseif a:rw==-1
-        call writefile(s:_r.lineutils.wtransform(getline(1, '$')), a:file, 'b')
+        let bvar=s:_r.bufvars[a:buf]
+        call bvar.write(bvar, s:_r.lineutils.wtransform(getline(1, '$')), a:file)
         setlocal nomodified
     endif
 endfunction
                         \                 | endif
         augroup END
     endif
+    if exists('g:Powerline_loaded')
+        if has_key(a:cdescr, 'plstrgen')
+            let bvar.ploptions=a:cdescr.plstrgen(bvar)
+        endif
+    endif
     file
 endfunction
 let s:_augroups+=['AurumNoInsert']
     " XXX On windows all forward slashes are transformed to backward in @%,
     "     all backward are transformed to forward in <amatch>
     let buf=expand('<abuf>')
+    " FIXME expand("<amatch>") truncates long filenames
     let amatch=expand('<amatch>')
     let tail=amatch[len('aurum://'):]
     let command=tolower(matchstr(tail, '\v^\w+'))
     let tail=tail[len(command)+1:]
     if command is# 'copy' && (a:rw==0 || a:rw==1)
-        return s:F.copy(a:rw, tail)
+        return s:F.copy(buf, a:rw, tail)
     elseif command is# 'edit' && abs(a:rw)<=1
-        return s:F.edit(a:rw, tail)
+        return s:F.edit(buf, a:rw, tail)
     endif
     call s:F.checkcmd(command)
     "▶2 Launch bvar.write if applicable
         let cdescr.mgroup=a:cdescr.mgroup
         let cdescr.mmap=a:plugdict.g._f.mapgroup.map
     endif
-    "▶2 Function keys: `write'
-    if has_key(a:cdescr, 'write')
-        if !exists('*a:cdescr.write')
-            call s:_f.throw('nfun', cname, a:plugdict.id, 'write')
+    "▶2 Function keys: “write”, “plstrgen”
+    for key in ['write', 'plstrgen']
+        if has_key(a:cdescr, key)
+            if !exists('*a:cdescr.'.key)
+                call s:_f.throw('nfun', cname, a:plugdict.id, key)
+            endif
+            let cdescr[key]=a:cdescr[key]
         endif
-        let cdescr.write=a:cdescr.write
-    endif
+    endfor
     "▲2
     let cdescr.id=cname
     let cdescr.plid=a:plugdict.id

File autoload/aurum/file.vim

             let rev=bvar.repo.functions.getnthparent(bvar.repo, bvar.rev, 1).hex
             let file=s:_r.fname('file', bvar.repo, rev, bvar.file)
             let cmd.=':call call(<SNR>'.s:_sid.'_Eval("s:_r.vimdiff.split"), '.
-                        \       '['.string(file).", 0], {})\n:wincmd p\n"
+                        \       '['.string(file).", 0], {})\n"
         endif
     elseif a:action is# 'diff' || a:action is# 'revdiff'
         let opts='repo '.escape(bvar.repo.path, ' ')

File autoload/aurum/hyperlink.vim

 scriptencoding utf-8
-execute frawor#Setup('0.0', {'@aurum': '1.0',
+execute frawor#Setup('0.1', {'@aurum': '1.0',
             \      '@%aurum/cmdutils': '4.0',
             \             '@/options': '0.0',})
 let s:_messages={
     endif
     "▲2
     let url=repo.functions.getrepoprop(repo, 'url')
-    let [protocol, user, domain, port, path]=
+    let [protocol, user, password, domain, port, path]=
                 \matchlist(url, '\v^%(([^:]+)\:\/\/)?'.
-                \                  '%(([^@/:]+)\@)?'.
+                \                  '%(([^@/:]+)'.
+                \                   '%(\:([^@/:]+))?\@)?'.
                 \                   '([^/:]*)'.
                 \                  '%(\:(\d+))?'.
-                \                   '(.*)$')[1:5]
+                \                   '(.*)$')[1:6]
     for [matcher, dict] in s:_f.getoption('hypsites')+repo.hypsites
         if eval(matcher)
             if !has_key(dict, utype)

File autoload/aurum/lineutils.vim

 function s:F.parsecmdarg()
     let r={}
     for arg in filter(split(v:cmdarg),
-                \     'v:val =~# ''\V\^++\v%(%(enc|ff)\=|%(no)bin)''')
+                \     'v:val =~# ''\V\^++\v%(%(enc|ff)\=|%(no)?bin)''')
         let idx=stridx(arg, '=')
         let r[arg[2]]=arg[(idx+1):]
     endfor

File autoload/aurum/maputils.vim

 execute frawor#Setup('0.0', {'@/resources': '0.0',
             \                       '@/os': '0.0',})
 let s:_messages={
-            \'plinst': 'If you install Command-T, Ctrlp or FuzzyFinder '.
-            \          'you will be prompted with much less sucking interface',
+            \'plinst': 'If you install Command-T, Ctrlp, FuzzyFinder, unite, '.
+            \          'ku or tlib you will be prompted with '.
+            \          'much less sucking interface',
         \}
 let s:r={}
 "▶1 update
     endtry
 endfunction
 function s:F.listplugs.commandt.call(files, cbargs, pvargs)
-    let [b:aurum_callback_fun; b:aurum_addargs]=a:cbargs
+    let [b:aurum_cbfun; b:aurum_cbargs]=a:cbargs
     ruby $aurum_old_command_t = $command_t
     ruby $command_t = $aurum_command_t
     ruby $command_t.show_aurum_finder
     autocmd BufUnload <buffer> ruby $command_t = $aurum_old_command_t
 endfunction
 "▶2 ctrlp
-function s:Accept(mode, str)
+function s:CtrlpAccept(mode, str)
     let d={}
-    let d.cbfun=b:aurum_callback_fun
-    let addargs=b:aurum_addargs
+    let d.cbfun=b:aurum_cbfun
+    let cbargs=b:aurum_cbargs
     call ctrlp#exit()
-    return call(d.cbfun, [a:str]+addargs, {})
+    return call(d.cbfun, [a:str]+cbargs, {})
 endfunction
-let s:_functions+=['s:Accept']
+let s:_functions+=['s:CtrlpAccept']
 let s:ctrlp_ext_var={
             \'init': '<SNR>'.s:_sid.'_Eval("s:ctrlp_files")',
-            \'accept': '<SNR>'.s:_sid.'_Accept',
+            \'accept': '<SNR>'.s:_sid.'_CtrlpAccept',
             \'lname': 'changeset files',
             \'sname': 'changeset file',
             \'type': 'path',
 function s:F.listplugs.ctrlp.call(files, cbargs, pvargs)
     let s:ctrlp_files=a:files
     call ctrlp#init(s:ctrlp_id)
-    let [b:aurum_callback_fun; b:aurum_addargs]=a:cbargs
+    let [b:aurum_cbfun; b:aurum_cbargs]=a:cbargs
 endfunction
 "▶2 fuf
 let s:F.listplugs.fuf={}
                 \                               'pvargs': a:pvargs})
     call fuf#launch('aurum', '', 0)
 endfunction
+"▶2 tlib
+" TODO it is possible that tlib is present, but tlib#input#List is not used for 
+"      listing files and hence is “unfamiliar” interface. It should be checked 
+"      last. Also add an option for the user to explicitely select one of the 
+"      plugins.
+let s:F.listplugs.tlib={}
+function s:F.listplugs.tlib.init()
+    try
+        runtime autoload/tlib/input.vim
+        return exists('*tlib#input#List')
+    catch
+        return 0
+    endtry
+endfunction
+function s:F.listplugs.tlib.call(files, cbargs, pvargs)
+    let file=tlib#input#List('s', 'Select changeset file', a:files)
+    if empty(file)
+        return 0
+    endif
+    return call(a:cbargs[0], [file]+a:cbargs[1:], {})
+endfunction
+"▶2 ku
+let s:F.listplugs.ku={}
+function s:KuOpen(candidate)
+    return call(b:aurum_cbfun, [a:candidate.word]+b:aurum_cbargs, {})
+endfunction
+let s:_functions+=['s:KuOpen']
+let s:ku_files=[]
+function s:F.kulist(_)
+    if !exists('b:aurum_files')
+        let b:aurum_files=s:ku_files
+    endif
+    return map(copy(b:aurum_files), '{"word": v:val}')
+endfunction
+function s:F.listplugs.ku.init()
+    try
+        if !exists('*ku#define_source')
+            runtime autoload/ku.vim
+        endif
+        if exists('*ku#define_source')
+            call ku#define_kind({
+                        \                'name': 'csfiles',
+                        \'default_action_table': {'open':
+                        \                   function('<SNR>'.s:_sid.'_KuOpen')},
+                        \   'default_key_table': {},
+                        \})
+            call ku#define_source({
+                        \             'name': 'aurum',
+                        \            'kinds': ['csfiles'],
+                        \'gather_candidates': s:F.kulist,
+                        \})
+            return 1
+        else
+            return 0
+        endif
+    catch
+        return 0
+    endtry
+endfunction
+function s:F.listplugs.ku.call(files, cbargs, pvargs)
+    let [b:aurum_cbfun; b:aurum_cbargs]=a:cbargs
+    let s:ku_files=a:files
+    call ku#start(['aurum'])
+endfunction
+"▶2 unite
+let s:F.listplugs.unite={}
+function s:F.listplugs.unite.init()
+    try
+        if !exists('*unite#start')
+            " XXX For some reason when doing “runtime autoload/unite.vim” vim 
+            "     sources this file for the second time when it finds “function! 
+            "     unite#version” statement. In this case s:save_cpo is unlet 
+            "     when second sourcing is finished and trying to use it when 
+            "     first sourcing is finished results in an error.
+            call unite#version()
+        endif
+        return exists('*unite#start')
+    catch
+        return 0
+    endtry
+endfunction
+function s:F.listplugs.unite.call(files, cbargs, pvargs)
+    call unite#start([['aurum', a:files, a:cbargs]])
+endfunction
 "▶1 promptuser
 function s:r.promptuser(files, cbargs, pvargs)
     if s:plug is 0
 "▶1 Post maputils resource
 call s:_f.postresource('maputils', s:r)
 "▶1
-call frawor#Lockvar(s:, 'plug,ctrlp_id,ctrlp_files')
+call frawor#Lockvar(s:, 'plug,ctrlp_id,ctrlp_files,ku_files')
 " vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File autoload/aurum/record.vim

             \               '@/mappings': '0.0',
             \                   '@aurum': '1.0',
             \             '@aurum/cache': '2.1',
-            \           '@%aurum/commit': '1.0',
+            \           '@%aurum/commit': '1.3',
             \         '@%aurum/cmdutils': '4.0',
             \        '@%aurum/lineutils': '0.0',
-            \             '@%aurum/edit': '1.0',
+            \             '@%aurum/edit': '1.5',
             \          '@%aurum/bufvars': '0.0',})
+let s:hasundo=exists('*undotree')
 let s:_options={
             \'recheight': {'default': 0,
             \               'filter': '(if type "" earg _  range 0 inf)'},
+            \'fullundo':  {'default': s:hasundo,
+            \               'filter': 'bool'},
         \}
 let s:_messages={
             \  'recex': 'There is already one AuRecord tab active '.
             \ 'recnof': 'No files were selected for commiting',
             \   'norm': 'Can’t remove file %s as it was not added',
             \   'noad': 'Can’t add file %s as it is already included',
+            \'noutree': 'No such item in saved undo tree. '.
+            \           'If you can reproduce this error file a bug report',
+            \'tooundo': 'Undone too much changes and cannot redo',
             \ 'undona': "Can’t undo changes. Possible reasons:\n".
-            \           "  1. Current change is the oldest one\n".
-            \           "  2. You did some changes manually and thus buffer ".
+            \           "  1. You did some changes manually and thus buffer ".
             \                "was reset\n".
-            \           "  3. You edited a file which discards undo ".
-            \                "information (supporting undoing edits is too ".
-            \                "complex)",
+            \           "  2. You edited a file which discards undo ".
+            \                "information unless g:aurum_fullundo is set",
             \ 'redona': "Can’t redo changes. Possible reasons:\n".
-            \           "  1. Current change is the newest one\n".
-            \           "  2. You did some changes manually and thus buffer ".
+            \           "  1. You did some changes manually and thus buffer ".
             \                "was reset\n".
-            \           "  3. You edited a file which discards undo ".
-            \                "information (supporting undoing edits is too ".
-            \                "complex)",
+            \           "  2. You edited a file which discards undo ".
+            \                "information unless g:aurum_fullundo is set",
         \}
+"▶1 commitvimdiffcb
+function s:F.commitvimdiffcb(file, bvar, hex)
+    let [lwnr, rwnr, swnr]=s:F.getwnrs()
+
+    execute lwnr.'wincmd w'
+    let file=s:_r.os.path.join(a:bvar.repo.path, a:file)
+    let existed=bufexists(file)
+    execute 'silent edit' fnameescape(file)
+    if !existed
+        setlocal bufhidden=wipe
+    endif
+    diffthis
+
+    execute rwnr.'wincmd w'
+    let existed=s:_r.run('silent edit', 'file', a:bvar.repo, a:hex, a:file)
+    if !existed
+        setlocal bufhidden=wipe
+    endif
+    diffthis
+
+    execute lwnr.'wincmd w'
+endfunction
+"▶1 commitfindwindow
+function s:F.commitfindwindow()
+    let [lwnr, rwnr, swnr]=s:F.getwnrs()
+    execute lwnr.'wincmd w'
+    return 1
+endfunction
 "▶1 write
 function s:F.write(bvar)
     call feedkeys("\<C-\>\<C-n>:call ".
             \      "call(<SNR>".s:_sid."_Eval('s:F.runstatmap'), ".
-            \           "['commit', ".expand('<abuf>')."], {})\n", 'n')
+            \           "['commit', ".expand('<abuf>')."], {})\n","n")
     call map(copy(s:_r.allcachekeys), 's:_r.cache.wipe(v:val)')
 endfunction
 "▶1 recfunc
     let bvar.reset=0
     let bvar.backupfiles={}
     let bvar.filesbackup={}
-    let bvar.newfiles=[]
+    let bvar.newfiles={}
     let bvar.lines=map(copy(bvar.chars), 'v:val." ".bvar.files[v:key]')
     let bvar.swheight=height
     let bvar.startundo=s:F.curundo()
                 \        'autowrite': &autowrite,
                 \     'autowriteall': &autowriteall,
                 \         'autoread': &autoread,}
+    let bvar.fullundo=(s:hasundo && s:_f.getoption('fullundo'))
+    if bvar.fullundo
+        let bvar.undotree={bvar.startundo : {}}
+    endif
     setglobal noautowrite noautowriteall noautoread
-    if !bvar.startundo
-        setglobal undolevels=-1
-    endif
     setlocal noreadonly buftype=acwrite
+    augroup AuRecordVimLeave
+        execute 'autocmd! VimLeave * '.
+                    \   ':if has_key(s:_r.bufvars,'.bvar.bufnr.')'.
+                    \   '|  call s:F.unload(s:_r.bufvars.'.bvar.bufnr.')'.
+                    \   '|endif'
+    augroup END
     if empty(bvar.chars)
         bwipeout!
     endif
 endfunction
 "▶1 curundo :: () → UInt
-if exists('*undotree')
+if s:hasundo
     function s:F.curundo()
         return undotree().seq_cur
     endfunction
     endfor
     let a:bvar.prevct=b:changedtick
     let a:bvar.reset=1
-    if a:bvar.startundo
-        let a:bvar.undolevels=&undolevels
+    if s:hasundo
         let a:bvar.startundo=s:F.curundo()
+        let savedundolevels=&undolevels
         setglobal undolevels=-1
+        execute "normal! A \<BS>\e"
+        let &undolevels=savedundolevels
+        if a:bvar.fullundo
+            let a:bvar.undotree={a:bvar.startundo : {}}
+        endif
     endif
 endfunction
 "▶1 supdate
-function s:F.supdate(bvar)
+function s:F.supdate(bvar, prevundo)
     if b:changedtick!=a:bvar.prevct
         let a:bvar.prevct=b:changedtick
         if a:bvar.reset
-            if has_key(a:bvar, 'undolevels')
-                let &g:undolevels=a:bvar.undolevels
-                unlet a:bvar.undolevels
-            endif
             let a:bvar.reset=0
         endif
+        let curundo=s:F.curundo()
+        if              a:bvar.fullundo
+                    \&& has_key(a:bvar.undotree, a:prevundo)
+                    \&& curundo!=a:prevundo
+                    \&& !has_key(a:bvar.undotree, curundo)
+            let a:bvar.undotree[curundo]=copy(a:bvar.undotree[a:prevundo])
+        endif
     endif
     setlocal nomodifiable
 endfunction
 "▶1 unload
 function s:F.unload(bvar)
     let sbvar=get(a:bvar, 'sbvar', a:bvar)
+    let sbuf=get(a:bvar, 'sbuf', -1)
+    if sbuf isnot 0 && bufexists(sbuf)
+        unlet sbvar.bwfunc
+        execute 'bwipeout!' sbuf
+    endif
     for [o, val] in items(sbvar.savedopts)
         execute 'let &g:'.o.'=val'
     endfor
     else
         return
     endif
-    call map(copy(sbvar.backupfiles), 's:F.restorebackup(v:val, v:key)')
-    call map(copy(sbvar.newfiles),    's:F.restorebackup(v:val,   0  )')
+    let backupfiles=copy(sbvar.backupfiles)
+    let newfiles=copy(sbvar.newfiles)
+    call filter(backupfiles, 'filereadable(v:key)')
+    call filter(newfiles,    'filereadable(v:key)')
+    call map(backupfiles, 's:F.restorebackup(v:val, v:key)')
+    call map(newfiles,    's:F.restorebackup(v:key,   0  )')
     for [buf, savedopts] in items(filter(sbvar.oldbufs, 'bufexists(v:key)'))
         for [opt, optval] in items(savedopts)
             call setbufvar(buf, '&'.opt, optval)
         endfor
     endfor
+    augroup AuRecordVimLeave
+        autocmd!
+    augroup END
 endfunction
 "▶1 getwnrs
 function s:F.getwnrs()
         call s:_f.mapgroup.map('AuRecordLeft', bufnr('%'))
     endif
 endfunction
+let s:_augroups+=['AuRecordLeft']
+"▶1 srestore
+function s:F.srestore(bvar)
+    let sbuf=get(a:bvar, 'sbuf', -1)
+    if !bufexists(sbuf)
+        return s:F.unload(get(a:bvar, sbuf, 0))
+    endif
+    let sbvar=a:bvar.sbvar
+    execute 'silent botright sbuffer' sbuf
+    execute 'resize' sbvar.swheight
+    call winrestview(a:bvar.winview)
+    redraw!
+    let w:aurecid='AuRecordStatus'
+    setlocal bufhidden=wipe
+    return 1
+endfunction
+"▶1 restorefiles :: bvar, sline::UInt, eline::UInt → + FS
+function s:F.restorefiles(bvar, sline, eline)
+    for file in map(range(a:sline, a:eline), 'a:bvar.lines[v:val-1][2:]')
+        let fullpath=s:_r.os.path.join(a:bvar.repo.path, file)
+        let backupfile=a:bvar.filesbackup[fullpath]
+        call s:F.restorebackup(fullpath, backupfile)
+    endfor
+endfunction
+"▶1 undoup :: bvar → + bvar
+function s:F.undoup(bvar)
+    for line in range(1, line('$'))
+        let a:bvar.statuses[line-1]=stridx(s:statchars, getline(line)[0])
+    endfor
+    if a:bvar.fullundo
+        let curundo=s:F.curundo()
+        if !has_key(a:bvar.undotree, curundo)
+            call s:F.reset(a:bvar)
+            call s:_f.throw('noutree')
+        endif
+        for [file, contents] in items(a:bvar.undotree[curundo])
+            if contents is 0 && filereadable(file)
+                call delete(file)
+            else
+                call writefile(contents, file, 'b')
+            endif
+            unlet contents
+        endfor
+    endif
+endfunction
+"▶1 undoleafwrite :: bvar, lines::[String], path → + bvar, FS
+function s:F.undoleafwrite(bvar, lines, file)
+    if a:file is# a:bvar.file
+        let a:bvar.undoleaf[a:bvar.fullpath]=a:lines
+    endif
+    return a:bvar.ewrite(a:bvar, a:lines, a:file)
+endfunction
+"▶1 sactions
+let s:F.sactions={}
+"▶2 sactions.[v]add, sactions.[v]remove
+function s:F.sactions.add(action, bvar, buf)
+    if a:action[0] is# 'v'
+        let sline=line("'<")
+        let eline=line("'>")
+        if sline>eline
+            let [sline, eline]=[eline, sline]
+        endif
+    else
+        let sline=line('.')
+        let eline=line('.')+v:count1-1
+        if eline>line('$')
+            let eline=line('$')
+        endif
+    endif
+    let add=(a:action[-3:] is# 'add')
+    for line in range(sline, eline)
+        let status=a:bvar.statuses[line-1]
+        let oldstatus=status
+        if add
+            if status<2
+                let status+=2
+            elseif status==3
+                let status=2
+                call s:F.restorefiles(a:bvar, sline, eline)
+            endif
+        else
+            if status>1
+                let status-=2
+            elseif status==1
+                let status=0
+                call s:F.restorefiles(a:bvar, sline, eline)
+            endif
+        endif
+        if oldstatus==status
+            call s:_f.warn('no'.((add)?('ad'):('rm')), a:bvar.files[line-1])
+            continue
+        endif
+        let a:bvar.statuses[line-1]=status
+        call setline(line, s:statchars[status].a:bvar.lines[line-1])
+    endfor
+    return 1
+endfunction
+let s:F.sactions.vadd=s:F.sactions.add
+let s:F.sactions.remove=s:F.sactions.add
+let s:F.sactions.vremove=s:F.sactions.remove
+"▶2 sactions.discard
+function s:F.sactions.discard(action, bvar, buf)
+    call s:F.unload(a:bvar)
+    return 0
+endfunction
+"▶2 sactions.undo
+function s:F.sactions.undo(action, bvar, buf)
+    execute 'silent normal! '.v:count1.'u'
+    let curundo=s:F.curundo()
+    if curundo<a:bvar.startundo
+        while curundo<a:bvar.startundo
+            let pundo=curundo
+            silent redo
+            let curundo=s:F.curundo()
+            if curundo==pundo
+                call s:_f.throw('tooundo')
+                call s:F.reset(a:bvar)
+                setlocal nomodifiable
+                return 0
+            endif
+        endwhile
+    endif
+    return 1
+endfunction
+"▶2 sactions.redo, sactions.earlier, sactions.later
+let s:uactkey={
+            \'redo':    "\<C-r>",
+            \'earlier': "g+",
+            \'later':   "g-",
+        \}
+function s:F.sactions.redo(action, bvar, buf)
+    execute 'silent normal! '.v:count1.s:uactkey[a:action]
+    return 1
+endfunction
+let s:F.sactions.earlier=s:F.sactions.redo
+let s:F.sactions.later=s:F.sactions.earlier
+"▶2 sactions.edit
+function s:F.sactions.edit(action, bvar, buf)
+    let [lwnr, rwnr, swnr]=s:F.getwnrs()
+    let file=a:bvar.lines[line('.')-1][2:]
+    let type=a:bvar.types[line('.')-1]
+    let status=a:bvar.statuses[line('.')-1]
+    let modified=status%2
+    execute lwnr.'wincmd w'
+    diffoff!
+    let fullpath=s:_r.os.path.join(a:bvar.repo.path, file)
+    let ntype=get(s:ntypes, type, 0)
+    if !modified
+        if ntype is# 'm' || ntype is# 'a'
+            if has_key(a:bvar.filesbackup, fullpath)
+                let backupfile=a:bvar.filesbackup[fullpath]
+            else
+                let backupfile=fullpath.'.orig'
+                let i=0
+                while s:_r.os.path.exists(backupfile)
+                    let backupfile=fullpath.'.'.i.'.orig'
+                    let i+=1
+                endwhile
+            endif
+        elseif ntype is# 'r'
+            let a:bvar.newfiles[fullpath]=1
+        endif
+    endif
+    if ntype isnot 0
+        execute swnr.'wincmd w'
+        if !modified
+            let status=3
+            let a:bvar.statuses[line('.')-1]=status
+        endif
+        if a:bvar.fullundo
+            let prevundo=s:F.curundo()
+            let line=line('.')
+            call setline(line, s:statchars[status].a:bvar.lines[line-1])
+            call s:F.supdate(a:bvar, prevundo)
+            let curundo=s:F.curundo()
+        else
+            call s:F.reset(a:bvar)
+        endif
+        setlocal nomodifiable
+        execute lwnr.'wincmd w'
+        call s:F.edit(a:bvar, 'aurum://edit:'.fullpath, 0)
+        if a:bvar.fullundo
+            let ebvar=s:_r.bufvars[bufnr('%')]
+            let undoleaf=a:bvar.undotree[curundo]
+            let ebvar.undoleaf=undoleaf
+            let ebvar.fullpath=fullpath
+            let ebvar.ewrite=ebvar.write
+            let ebvar.write=s:F.undoleafwrite
+        endif
+        if ntype is# 'm' || (modified && ntype is# 'a')
+            if !modified
+                let fcontents=a:bvar.repo.functions.readfile(
+                            \     a:bvar.repo,
+                            \     a:bvar.repo.functions.getworkhex(a:bvar.repo),
+                            \     file)
+            endif
+            diffthis
+            execute rwnr.'wincmd w'
+            call s:F.edit(a:bvar, 'aurum://copy:'.
+                        \((modified)?(a:bvar.filesbackup[fullpath]):
+                        \            (fullpath)), 1)
+            diffthis
+            wincmd p
+        elseif modified
+            if ntype is# 'r'
+                diffthis
+                execute rwnr.'wincmd w'
+                call s:F.edit(a:bvar,
+                            \ ['file', a:bvar.repo,
+                            \  a:bvar.repo.functions.getworkhex(a:bvar.repo),
+                            \  file], 1)
+                diffthis
+                wincmd p
+            endif
+        else
+            if ntype is# 'a'
+                let fcontents=readfile(fullpath, 'b')
+            elseif ntype is# 'r'
+                let fcontents=a:bvar.repo.functions.readfile(
+                            \     a:bvar.repo,
+                            \     a:bvar.repo.functions.getworkhex(a:bvar.repo),
+                            \     file)
+            endif
+        endif
+        if !modified
+            if exists('backupfile')
+                let isexe=executable(fullpath)
+                if rename(fullpath, backupfile)
+                    call s:_f.warn('renfail', fullpath, backupfile)
+                    setlocal readonly nomodifiable
+                    execute swnr.'wincmd w'
+                    return 0
+                endif
+                let a:bvar.backupfiles[backupfile]=fullpath
+                let a:bvar.filesbackup[fullpath]=backupfile
+                if a:bvar.fullundo
+                    let undoleaf[fullpath]=0
+                    let undoleaf[backupfile]=readfile(backupfile, 'b')
+                    let oundoleafpart={fullpath   : copy(undoleaf[backupfile]),
+                                \      backupfile : 0}
+                    call map(a:bvar.undotree,
+                                \'extend(v:val, oundoleafpart, "keep")')
+                endif
+            else
+                let isexe=0
+            endif
+            let diff=&diff
+            if exists('fcontents')
+                silent %delete _
+                call s:_r.lineutils.setlines(fcontents, 0)
+                if diff
+                    diffupdate
+                endif
+            endif
+            silent write
+            if isexe && s:_r.os.name is# 'posix'
+                call system('chmod +x '.fnameescape(fullpath))
+            endif
+        endif
+        if !has_key(s:_r.bufvars, bufnr('%'))
+            let s:_r.bufvars[bufnr('%')]={}
+        endif
+        call extend(s:_r.bufvars[bufnr('%')], {'recfile': file,
+                    \                      'recmodified': modified,
+                    \                      'recfullpath': fullpath,
+                    \                       'recnewfile': 0,})
+        if exists('backupfile')
+            let s:_r.bufvars[bufnr('%')].recbackupfile=backupfile
+        else
+            let s:_r.bufvars[bufnr('%')].recnewfile=1
+        endif