Search code examples
svnsvnsync

Can I create a shallow repository copy with svnsync?


There's a repo online that has 13k commits, but I'm only interested in last 1000 of them. Is it possible to copy them with svnsync?

I want to be able to checkout within the last 1000 commits from this local repository, and it should be faster than from the server

Revision numbers must be preserved.
svnsync must be able to sync the future revisions normally into the local repository.


Solution

  • In the following example I save last 45 commits of a repo. The helper python script is below.

    # svnsync 1.6.11
    # rhel 6 x86_64
    
    # params
    svn_baserepourl="http://svn.igniterealtime.org/svn/repos"
    svn_subdir="spark/trunk"
    reponame=spark
    skipuntil=13774
    
    
    # well-known svnsync preparation
    repopath=`pwd`/$reponame.svn
    svnadmin create "$repopath"
    
    echo '#!/bin/sh' >"$repopath"/hooks/pre-revprop-change
    chmod +x "$repopath"/hooks/pre-revprop-change
    
    svnsync init file://"$repopath" "$svn_baserepourl/$svn_subdir"
    
    expectedfmt="4
    layout sharded 1000"
    
    if [ x"$expectedfmt" != x"$(cat "$repopath/db/format")" ]; then
        echo unknown db format
    fi
    
    # create database folder structure
    ( cd "$repopath/db" && echo $(for (( i=0; i<=$((skipuntil / 1000)) ; i++ )) ; do mkdir -p revprops/$i revs/$i; done ) )
    
    # don't create last skipped revision
    skipuntil=$((skipuntil - 1))
    
    # create dummy revision files
    ( cd "$repopath/db" && for (( i=0; i<=$skipuntil; i++ )) ; do echo revprops/$((i/1000))/$i; echo revs/$((i/1000))/$i; done | xargs touch )
    
    # update revision counter
    echo $skipuntil >"$repopath/db/current"
    
    # we need a valid revision file
    cp "$repopath/db/revs/0/0" "$repopath/db/revs/$((skipuntil/1000))/$skipuntil"
    
    # last skipped revision number
    skipuntil=$((skipuntil + 1))
    
    if false; then
        # TODO: if svnrdump available
        # mkdir in local repo
        # svnrdump last skipped revision and subdir
        # svnadmin load --parent-dir subdir
        :
    else
        # checkout last skipped revision
        rm -rf temptree
        svn co -r $skipuntil "$svn_baserepourl/$svn_subdir" "temptree/$svn_subdir"
        cd temptree
        pushd "$svn_subdir"
    
        # save properties
        svn proplist --xml -v -R  | ~/svnprops.py >/tmp/propset.sh
    
        # remove .svn folders
        find . -name .svn -print0 | xargs -0 rm -rf
    
        popd
    
        # checkout our copy
        svn co "file://$repopath" .
    
        # add the files
        svn add --no-ignore --force .
    
        # delete automatic props
        for p in $(svn proplist -q -R | sort -u); do svn propdel -q -R $p; done
    
        # restore original props
        pushd "$svn_subdir"
        sh /tmp/propset.sh
        popd
    
        # commit the last skipped revision
        svn commit -m"shallow root"
    
        cd ..
        rm -rf temptree
    fi
    
    # update svnsync counters
    svn propset --revprop -r 0 svn:sync-last-merged-rev $skipuntil  "file://$repopath"
    svn propset --revprop -r 0 svn:sync-currently-copying $((skipuntil+1))  "file://$repopath"
    
    # sync!
    svnsync sync "file://$repopath"
    

    This is some old svnprops.py to copy svn properties between repos:

    #!/usr/bin/env python
    
    import sys
    import xml.sax
    import xml.sax.handler
    
    """
    <?xml version="1.0"?>
    <properties>
    <target
       path="file:///.snapshots/persist/builds/cyg-apt/cyg-apt.svn/trunk">
    <property
       name="svn:ignore">build
    </property>
    </target>
    </properties>
    """
    
    class Subhandler:
      def __init__(self, mainHandler, parenthandler):
        self.mainHandler = mainHandler
        self.parenthandler = parenthandler
        self.subhandler = None
        self.lvl = 0
      def startElement(self, name, attributes):
        self.lvl = self.lvl + 1
      def characters(self, data):
        pass
      def endElement(self, name):
        self.lvl = self.lvl - 1
    
    class Subhandler_properties(Subhandler):
      def startElement(self, name, attributes):
        pass
      def characters(self, data):
        pass
      def endElement(self, name):
        pass
    
    class P1ropertiesDocHandler(xml.sax.handler.ContentHandler):
      def __init__(self):
        self.subhandler = None
    
      def startElement(self, name, attributes):
        if subhandler != None:
          subhandler.startElement(name, attributes)
        elif name == "properties":
          Subhandler_properties(self)
    
      def endElement(self, name):
        if subhandler != None:
          subhandler.endElement(name)
    
      def characters(self, data):
        if subhandler != None:
          subhandler.characters(data)
    
    
    class PropertiesDocHandler(xml.sax.handler.ContentHandler):
      def __init__(self):
        self.target = None
        self.property = None
    
      def startElement(self, name, attributes):
        if name == "target":
          self.target = attributes["path"]
        elif name == "property":
          self.property = attributes["name"]
          self.propval = ""
    
      def endElement(self, name):
        if name == "target":
          self.target = None
        elif name == "property":
          print("svn ps \"" + self.property.replace("\"","\\\"") + "\" \"" + self.propval.replace("\"","\\\"") + "\" \"" + self.target.replace("\"","\\\"") + "\"")
          self.property = None
          self.propval = None
    
      def characters(self, data):
        if self.property != None:
          self.propval += data
    
    parser = xml.sax.make_parser()
    handler = PropertiesDocHandler()
    parser.setContentHandler(handler)
    #print("aa\"ass".replace("\"","\\\""))
    parser.parse(sys.stdin)