1. ZyX_I
  2. aurum

Commits

ZyX_I  committed 744408e

@aurum/drivers/subversion: Added cs.parents and cs.children support
Better file handling in different places
Added diff caching

  • Participants
  • Parent commits 9eab8e3
  • Branches svnsupport

Comments (0)

Files changed (3)

File doc/aurum.txt

View file
  • Ignore whitespace
                          If third argument is empty, then searches working 
                          directory versions of files present in current 
                          changeset. Uses |aurum-rf-getcsprop| (with “allfiles” 
-                         argument), |aurum-rf-revrange| and 
+                         argument), |aurum-rf-revrange|, |aurum-rf-getcs| and 
                          |aurum-rf-readfile|.
                          Slow!
   |aurum-repo.iterfuncs|   "changeset": Uses |aurum-rf-getchangesets|
 |aurum-cs.time| is always 0 if “date” programm from coreutils is missing.
 |aurum-cs.hex| contains the stringified value of |aurum-cs.rev| and thus is 
     not fixed-length.
-|auurm-repo.cslist|: repo.cslist[cs.rev] is any revision with .rev≥cs.rev, not 
+|aurum-repo.cslist|: repo.cslist[cs.rev] is any revision with .rev≥cs.rev, not 
     necessary cs itself.
 |aurum-rf-diff|: Options "git", "iblanks", "dates", "alltext" are not 
     supported, option "reverse" is supported only if two revisions are given.
 |aurum-rf-status|: if at least one of first two optional arguments is 
     non-zero, then then unknown and deleted files won’t be shown (if any).
+    Note: this function may output directories in a list.
 |aurum-rf-annotate| neither tracks renames/copies nor is able to output line 
     numbers.
+|aurum-rf-grep| uses internal grep implementation based on 
+    |aurum-rf-readfile|. Thus it is slow, but supports vim |regexp|.
+    See more in |aurum-f-regdriver|.
+|aurum-rf-ignoreglob| uses “svn propset svn:ignore” and thus is limited to 
+    non-recursive globs applying to exactly one directory: ignoring 
+    “subdir*/ignored*” will ignore all files with names starting with 
+    “ignored” in a directory “subdir*”, which is probably not what you 
+    initially wanted.
 
 ==============================================================================
 11. Changelog                                                *aurum-changelog*

File plugin/aurum.vim

View file
  • Ignore whitespace
         call s:_r.cmdutils.checkrepo(repo)
         let file=s:F.urlescape(file)
         if rev is 0
-            if has_key(opts, 'line') &&
-                        \index(repo.functions.status(repo).clean, file)==-1
+            if has_key(opts, 'line') && repo.functions.dirty(repo, file)
                 call remove(opts, 'line')
             endif
             let cs=repo.functions.getwork(repo)

File plugin/aurum/drivers/subversion.vim

View file
  • Ignore whitespace
 function s:F.decodeentities(s)
     return substitute(a:s, '\v\&([lg]t|amp)\;', '\=s:entities[submatch(1)]','g')
 endfunction
-"▶1 parsecs :: csdata, lstart::UInt → (cs, line::UInt)
+"▶1 parsecs :: repo, csdata, lstart::UInt → (cs, line::UInt)
 let s:logseparator=repeat('-', 72)
 let s:csinit={
             \'branch': 'default',
 let s:hasdateexe=executable('date')
 " TODO HEAD, ... in cs.tags
 " TODO use merge information if available
-function s:F.parsecs(csdata, line)
+function s:F.parsecs(repo, csdata, line)
     let cs=deepcopy(s:csinit)
     let lcsdata=len(a:csdata)
     "▶2 Check for logentry start
         call s:_f.throw('perr', '<logentry', a:csdata[line])
     endif
     let line+=1
-    "▶2 rev and hex
+    "▶2 rev and hex, parents
     let rev=matchstr(a:csdata[line], '\v(\srevision\=\")@<=(\d+)\"@=')
     if empty(rev)
         call s:_f.throw('perr', 'revision="N"', a:csdata[line])
     endif
     let cs.rev=str2nr(rev)
     let cs.hex=rev
+    let cs.parents=((cs.rev>1)?([''.(cs.rev-1)]):([]))
     let line+=1
     "▶2 author
     let author=matchstr(a:csdata[line], '\v(\<author\>)@<=.*(\<\/author\>)@=')
     "▶2 paths
     if a:csdata[line] is# '<paths>'
         let line+=1
+        let svnplidx=len(a:repo.svnprefix)-1
         while line<lcsdata && a:csdata[line] isnot# '</paths>'
             if a:csdata[line][:4] isnot# '<path' "▶3
                 call s:_f.throw('perr', '<path', a:csdata[line])
             endif           "▲3
             let [action, file]=match
             "▶3 Add file to list(s)
-            if has_key(s:logstatchars, action)
-                let cs.status[s:logstatchars[action]]+=[file]
-            else
-                call s:_f.throw('perr', 'action="A/M/D"', action)
+            if file[:(svnplidx)] is# a:repo.svnprefix
+                if has_key(s:logstatchars, action)
+                    let cs.status[s:logstatchars[action]]+=[file]
+                else
+                    call s:_f.throw('perr', 'action="A/M/D"', action)
+                endif
             endif
             "▲3
             let line+=1
 endfunction
 "▶1 getchangesets :: repo → [cs]
 function s:F.getchangesets(repo, ...)
-    let args=[]
+    let args=['--', a:repo.svnroot]
     let kwargs={'xml': 1, 'verbose': 1}
     if a:0==1
         if type(a:1)==type(0)
     let llog=len(log)
     let line=0
     while line<llog
-        let [cs, line]=s:F.parsecs(log, line)
+        let [cs, line]=s:F.parsecs(a:repo, log, line)
         let a:repo.changesets[cs.hex]=cs
         call insert(cslist, cs)
     endwhile
 function s:svn.getchangesets(repo)
     if empty(a:repo.cslist)
         let cslist=s:F.getchangesets(a:repo)
-        call map(cslist, 'extend(v:val, {"parents": '.
-                    \'(v:key ? [cslist[v:key-1].hex] : [])})')
-        " TODO children
+        let cslist[-1].children=[]
+        call map(cslist[:-2], 'extend(v:val, {"children": ["".(v:val.rev-1)]})')
         let a:repo.cslist+=cslist
     else
         call a:repo.functions.updatechangesets(a:repo)
         call remove(a:repo.cslist, +tiphex, -1)
     elseif tiphex>oldtiphex
         let cslist=s:F.getchangesets(a:repo, oldtiphex, tiphex)
-        call map(cslist, 'extend(v:val, {"parents": '.
-                    \'(v:key ? [cslist[v:key-1]] : [oldtiphex])})')
-        " TODO children
-        let a:repo.cslist+=cslist
+        if !empty(cslist)
+            let a:repo.cslist[-1].children=''.(a:repo.cslist[-1].rev+1)
+            call map(cslist[:-2], 'extend(v:val, {"children": '.
+                        \                                '["".(v:val.rev-1)]})')
+            let a:repo.cslist+=cslist
+        endif
     endif
+    let a:repo.cslist[-1].children=[]
 endfunction
 "▶1 svn.revrange :: repo, rev1, rev2 → [cs]
 function s:svn.revrange(repo, rev1, rev2)
         endif
     endif
     "▲2
-    let cs=s:F.parsecs(s:F.svn(a:repo, 'log', [],
+    let cs=s:F.parsecs(a:repo,
+                \      s:F.svn(a:repo, 'log', ['--', a:repo.svnroot],
                 \              {'revision': rev, 'limit': '1', 'xml': 1,
                 \               'verbose': 1},
                 \              0, 'csf', a:rev), 2)[0]
-    " TODO Populate cs.parents
-    let cs.parents=[]
     let a:repo.changesets[cs.hex]=cs
     return a:repo.changesets[cs.hex]
 endfunction
         let r=s:F.svn(a:repo, 'list', ['.'], {'recursive': 1,
                     \                         'revision': a:cs.hex}, 0,
                     \ 'listf', a:cs.hex)[:-2]
+        let dirs=[]
         " FIXME Investigate whether s:_r.os.pathsep should be used here instead
-        call filter(r, 'v:val[-1:] isnot# "/"')
+        call filter(r, 'v:val[-1:] is# "/" ? [0, add(dirs, v:val)][0] : 1')
+        let a:cs.alldirs=dirs
     elseif a:prop is# 'children'
-        " TODO
-    elseif  a:prop is# 'changes' || a:prop is# 'files' || a:prop is# 'removes'
-        let diff=a:repo.functions.diff(a:repo, a:cs.hex, 0, [], {})
-        if empty(a:cs.parents)
-            let ofs=[]
-        else
-            let ofs=a:repo.functions.getcsprop(a:repo, a:cs.parents[0],
-                        \                     'allfiles')
+        let lastrev=str2nr(a:repo.functions.gettiphex(a:repo))
+        if a:cs.rev<lastrev
+            let r=[''.(a:cs.rev+1)]
         endif
-        let fs=a:repo.functions.getcsprop(a:repo, a:cs, 'allfiles')
-        let status=s:F.statfromdiff(a:repo, diff, ofs, fs, [])[0]
-        let a:cs.files=status.modified+status.added
-        let a:cs.removes=status.removed
-        let a:cs.changes=a:cs.files+a:cs.removes
-        return a:cs[a:prop]
     endif
     let a:cs[a:prop]=r
     return r
             \'iwsamount': 'ignore-space-change',
             \ 'showfunc': 'show-c-function',
         \}
-" TODO Cache diff?
 function s:svn.diff(repo, rev1, rev2, files, opts)
+    let cancache=1
+    "▶2 Get --extensions arguments
     let diffopts=s:_r.utils.diffopts(a:opts, a:repo.diffopts, s:difftrans)
     if has_key(diffopts, 'unified') && diffopts.unified && diffopts.unified!=3
         call s:_f.throw('u3', diffopts.unified)
             let args+=['--extensions', '--'.k]
         endif
     endfor
+    "▶2 Get revision arguments
     if empty(a:rev2)
         if !empty(a:rev1)
             if reverse
         endif
     else
         let kwargs.revision=''.a:rev2
-        if !empty(a:rev1)
+        if empty(a:rev1)
+            if reverse
+                call s:_f.throw('rnimp')
+            endif
+            let cancache=0
+        else
             if reverse
                 let kwargs.revision=a:rev1.':'.kwargs.revision
             else
                 let kwargs.revision.=':'.a:rev1
             endif
-        elseif reverse
-            call s:_f.throw('rnimp')
         endif
     endif
+    "▶2 Get files arguments
     if !empty(a:files)
+        " FIXME Work with case when local repository root is not remote 
+        " repository root
         let args+=['--']+a:files
     endif
+    "▶2 Process cache
+    if cancache
+        let cachekey=string(args).string(sort(items(kwargs)))
+        if has_key(a:repo.diffcache, cachekey)
+            let cache=a:repo.diffcache[cachekey]
+            let cache.acccount+=1
+            return copy(cache.diff)
+        endif
+    endif
+    "▲2
     let r=s:F.svn(a:repo, 'diff', args, kwargs, 1,
                 \ 'difff', a:rev1, a:rev2, join(a:files, ', '))
+    "▶2 Remove cache entries if needed
+    if cancache
+        let cache={'diff': copy(r), 'acccount': 1}
+        if len(keys(a:repo.diffcache))==50
+            let dcitems=items(a:repo.diffcache)
+            let [minacccountkey, ccache]=remove(dcitems, 0)
+            let minacccount=ccache.acccount
+            for [key, ccache] in dcitems
+                if ccache.acccount<minacccount
+                    let minacccountkey=key
+                    let minacccount=ccache.acccount
+                endif
+            endfor
+            call remove(a:repo.diffcache, minacccountkey)
+        endif
+        let a:repo.diffcache[cachekey]=cache
+    endif
+    "▲2
     return r
 endfunction
 "▶1 svn.diffre :: _, opts → Regex
     let revstatus={}
     for line in slines
         let status=line[:6]
-        let file=line[8:]
+        let file=(a:repo.svnprefix).line[8:]
         let col=0
         for colschars in s:statchars
             if has_key(colschars, status[col])
         elseif hasfiles && index(a:files, filematch[0])==-1
             continue
         else
-            let file=filematch[0]
+            let file=(a:repo.svnprefix).filematch[0]
         endif
     endwhile
     "▲2
     "▶2 Simple case: we can use “svn status”
     if !a:0 || (a:1 is 0 && !(a:0>1 && a:2 isnot 0))
         let [r, revstatus]=s:F.status(a:repo, get(a:000, 2, []))
-        let allfiles=a:repo.functions.getcsprop(a:repo, 'HEAD', 'allfiles')
+        let allfiles=a:repo.functions.getcsprop(a:repo, 'BASE', 'allfiles')
         if a:0>2 && !empty(a:3)
             let allfiles=filter(copy(allfiles), 'index(a:3, v:val)!=-1')
         endif
 endfunction
 "▶1 svn.ignoreglob :: repo, glob → + FS
 function s:svn.ignoreglob(repo, glob)
-    " XXX Check ignoring in subdirectories
-    return s:F.svnm(a:repo, 'propset', ['svn:ignore', a:glob, '.'], {}, 0,
+    let dir=s:_r.os.path.dirname(a:glob)
+    let glob=s:_r.os.path.basename(a:glob)
+    return s:F.svnm(a:repo, 'propset', ['svn:ignore', glob, dir], {}, 0,
                 \   'ignf', a:glob)
 endfunction
 "▶1 svn.getrepoprop :: repo, propname → a
 function s:svn.getrepoprop(repo, prop)
     if a:prop is# 'url'
-        return s:F.getfrominfo(a:repo, 'URL')
+        return a:repo.svnurl
     elseif a:prop is# 'tagslist'
         return ['HEAD', 'BASE', 'COMMITTED', 'PREV']
     elseif a:prop[-4:] is# 'list'
                 \              'branch', 'time', 'user', 'description',
                 \              'renames', 'copies', 'files', 'changes',
                 \              'removes'],
-                \'has_merges': 0,
+                \'has_merges': 0, 'diffcache': {},
                 \}
+    "▶2 Get svnprefix
+    let str1='URL: '
+    let str1lidx=len(str1)-1
+    let str2='Repository Root: '
+    let str2lidx=len(str2)-1
+    for line in s:F.svn(repo, 'info', [], {}, 0, 'infof')
+        if line[:(str1lidx)] is# str1
+            let repo.svnurl=line[str1lidx+1:]
+        elseif line[:(str2lidx)] is# str2
+            let repo.svnroot=line[str2lidx+1:]
+            " XXX Assuming repository root always goes after URL
+            break
+        endif
+    endfor
+    let repo.svnprefix=repo.svnurl[(len(repo.svnroot)+1):]
+    if !empty(repo.svnprefix)
+        let repo.svnprefix.='/'
+    endif
+    "▲2
     return repo
 endfunction
 "▶1 svn.checkdir :: dir → Bool
     endfor
     return a:dir
 endfunction
+"▶1 svn.reltorepo :: repo, path → rpath
+function s:svn.reltorepo(repo, path)
+    return a:repo.svnprefix.
+                \join(s:_r.os.path.split(s:_r.os.path.relpath(a:path,
+                \                                             a:repo.path))[1:],
+                \     '/')
+endfunction
 "▶1 Register driver
 call s:_f.regdriver('Subversion', s:svn)
 "▶1