#!/usr/bin/env python2
#-------------------------------------------------------------------------------
#    Copyright  The Computer Guys (PTY) Ltd 2002
#    Original file by: Ed Pascoe <ed@pascoe.co.za>
#-------------------------------------------------------------------------------

import os, sys,os.path

import optik #For the command line processing
import dircache #For walking the directory tree

#All the XML stuff you would think they could simplify this a little
import xml.dom.ext.reader.PyExpat
import xml.xpath
import xml.sax.saxutils
import xml.dom.minidom  #Used for the Node entries

class SVNWalk:
    """Makes global changes to subversion working directories"""
    oldbase="The old svn server url"
    newbase="The new svn server url"
    def __init__(self,topdirectory):
        self.td=topdirectory
        if not os.path.exists(os.path.join(self.td,".svn/entries")):
            raise Exception,self.td + " is not a subversion working directory"

        self.oldbase=self.__getoldurl= SVNEntry(os.path.join(os.path.join(self.td,".svn/entries"))).baseurl()
    def change(self,newurlbase="",oldurlbase=""):
        """Walk through the tree changing the svn url base"""
        if newurlbase: self.newbase=newurlbase
        if oldurlbase: self.oldbase=oldurlbase
        self.__dirwalk(self.td)
    def __dirwalk(self,directory):
        """Go away, private. Use change instead"""
        for f in dircache.listdir(directory):
            fname=os.path.join(directory,f)
            if f==".svn": self.__basechange(os.path.join(fname,"entries"))
            elif os.path.isdir(fname): self.__dirwalk(fname)
    def __basechange(self,fname):
        """Go away, private. Use change instead"""
        try: 
            s=SVNEntry(fname)
            s.basechange(self.oldbase,self.newbase)
            f=open(fname,"w")
            f.write(str(s))
            f.close()
            print fname
        except xml.parsers.expat.ExpatError:
            print "Something fishy in ",fname

class SVNEntry:
    """Makes changes to the .svn/entries url"""
    svnentryconf="/home/elp/work/svnentry/svnentryusers.xml"
    dom="The xml data tree"
    includeXmlHeader=1
        
    def __init__(self,conffile):
        """Sets the location of the .svn/entries xml file
        """
        if conffile: self.svnentryconf=conffile
        reader = xml.dom.ext.reader.PyExpat.Reader()
        f=open(self.svnentryconf,"r")
        self.dom = reader.fromStream(f)
        f.close()
    def baseurl(self):
        for node in xml.xpath.Evaluate(r"/wc-entries/entry[@name='']", self.dom.documentElement): 
            #The for loop is just incase we get more than one url entry
            return node.getAttributeNS(xml.dom.EMPTY_NAMESPACE, u'url')
    def basechange(self,oldbase,newbase):
        """Return a list of urls """
        sitelist=[]
        for node in xml.xpath.Evaluate(r"/wc-entries/entry[@name='']", self.dom.documentElement):
            url=self.baseurl()
            if url.find(oldbase)==0: #Only change if this is definitly the right base
                url=newbase+url[len(oldbase):] #Remove the old base  and add the new base
                node.setAttributeNS(xml.dom.EMPTY_NAMESPACE, u'url',url)
            else:
                if url.find(newbase)==0: continue #Means that it has just been converted already
                print "Could not find "+oldbase+" in " + url+ " in file "+self.svnentryconf
                #raise Exception,"Could not find "+oldbase+" in " + url
    def __atrText(self,node,indent,namelen=0):
        """Private, used by __toText to create the attribute strings"""
        atr=[]
        for a in node.attributes: atr.append(a.name+"="+xml.sax.saxutils.quoteattr(a.value)) #all attributes in array
        if (len(" ".join(atr))+namelen) < 70: #We could put everything all on one line
            return " " + " ".join(atr)
        
        out="\n"
        for l in atr: out=out+indent+"  " + l+"\n"
        out=out+indent
        return out
    def __toText(self,obj,indent):
        """Private, move along, nothing to see, used by __str__"""
        childcount=0
        out=""
        for node in obj.childNodes:
            childcount=childcount+1
            if node.nodeType == xml.dom.Node.ELEMENT_NODE:            #Create <nodename 
                nname=str(node.nodeName)
                out=out+"\n"+indent+"<"+node.nodeName  
                out=out+self.__atrText(node,indent,len(node.nodeName)) #Add attributes

            elif node.nodeType == xml.dom.Node.TEXT_NODE: 
                if len(node.nodeValue.strip()): out=out+node.nodeValue.strip() #If node contains text
            txt,count= self.__toText(node,indent+"   ")
            if count:
                if ((node.nodeType == xml.dom.Node.ELEMENT_NODE) and (len(indent+nname+nname+txt) < 75)):
                    out=out+">"+txt+"</"+node.nodeName+">"
                else:
                    out=out+">"
                    out=out+txt+"\n"
                    out=out+indent+"</"+node.nodeName+">"
            elif node.nodeType == xml.dom.Node.ELEMENT_NODE:
                out=out+"/>"
        return (out,childcount)
    def __str__(self):
        """Warning if trailing and leading white space is important to you this may not be what you want"""
        if self.includeXmlHeader: out="""<?xml version="1.0" encoding="utf-8"?>\n"""
        else: out=""
        txt,count=self.__toText(self.dom,"")
        return out+txt

if __name__ == '__main__':
    usage="""Usage: %prog [options] [<directories to process>]"
    Converts an svn repository to use a new url as its sourece.
    eg: to change from:
       http://elp@backup.afrihost.com/svn/live/afribin 
    to
       svn+ssh://elp@backup.afrihost.com/svn/live/afribin 
    go to the top of the svn directory and execute:
        %prog --new="svn+ssh://elp@backup.afrihost.com/svn/live/afribin"

    If directory tree to process is not specified the current directory and below is used
    """
    parser=optik.OptionParser(usage)
    parser.add_option("-o", "--old", dest="oldbase", default="",
                        help="Old svn base url, optional defaults to the top level svn url")
    parser.add_option("-n", "--new", dest="newbase",
                        help="New svn base url")
    (options,args) = parser.parse_args()
    if not args: args=["."]

    for i in args:
        print i
        s=SVNWalk(i)
        print  "Old base:",s.oldbase
        s.change(options.newbase,options.oldbase)
