#!/usr/bin/env python
#
# svn_summary
# Summarize the state of SVN repositories
#

import sys
import os
import commands
import re

def path_type(dirpath):
    tokens = dirpath.split("/")
    i = len(tokens)-1
    while i>=0:
      if (tokens[i] == "trunk") or (tokens[i] == "releases") or (tokens[i] == "stable") or (tokens[i] == "branches") or (tokens[i] == "tags"):
         break
      i = i-1
    if (tokens[i] == "trunk") or (tokens[i] == "releases") or (tokens[i] == "stable") or (tokens[i] == "branches") or (tokens[i] == "tags"):
       return tokens[i]
    return "unknown"


def print_alert(type,file,alert):
    if type == "Ignore":
       return
    if type == "OK":
       return
    print "<Alert>"
    print "  <Type>"+type+"</Type>"
    print "  <File>"+file+"</File>"
    print "  <Summary>"+alert+"</Summary>"
    print "</Alert>"

def find_externals(dir,prefix=""):
  res = []
  for name in os.listdir(dir):
    if os.path.isdir(dir+"/"+name):
       res = res + find_externals(dir+"/"+name, prefix+"/"+name)
    else:
       if name == "Externals": 
          res.append(prefix + "/" + name)
  return res 


def guess_projroot(external):
    tokens = external.split("/")
    if "" in tokens:
       ndx = tokens.index("")
       if ndx > 0 and (tokens[ndx-1] == "https:" or tokens[ndx-1] == "http:"):
       	  tokens[ndx-1] = tokens[ndx-1] + "/"
       tokens.remove("")
       while "" in tokens:
         ndx = tokens.index("")
         if ndx > 0 and (tokens[ndx-1] == "https:" or tokens[ndx-1] == "http:"):
       	    tokens[ndx-1] = tokens[ndx-1] + "/"
         tokens.remove("")
    num = len(tokens)
    i = num-1
    while i>=0:
      if (tokens[i] == "trunk") or (tokens[i] == "releases") or (tokens[i] == "stable") or (tokens[i] == "branches") or (tokens[i] == "tags"):
         break
      i = i-1
    if i == -1:
       ans = "/".join(tokens)
    else:
       ans = "/".join(tokens[0:i])
    if ans[-1] == "/":
       return ans[0:-1]
    return ans


def guess_release(repos,name,subdir):
    output = commands.getoutput("svn ls " + repos+name+"/"+subdir)
    if output=="":
       return (None,None)
    dirs=output.split("\n")
    ans_path=""
    ans_rev=-1
    ans_dir=""
    for release_dir in dirs:
      #print release_dir
      curr=""
      dir=""
      info = commands.getoutput("svn info " + repos+name+"/"+subdir + "/" + release_dir)
      lines=info.split("\n")
      for line in lines:
        token = re.split('[ \t]+',line.strip())
        if token[0] == "Path:":
           curr=token[1]
        elif token[0] == "URL:":
           dir = token[1]
        elif token[0] == "Last" and token[1] == "Changed" and token[2] == "Rev:":
           tmp = eval(token[3])
	   if tmp > ans_rev:
	      ans_path=curr
	      ans_rev=tmp
	      ans_dir=dir
    #print repos+name+"/"+subdir,ans_path,ans_dir
    return (ans_path,ans_dir)


class SVNProject:
  def __init__(self,repos,name):
	self.has_trunk=False
	self.has_releases=False
	self.has_tags=False
	self.has_branches=False
	self.has_stable=False

	self.stable_name = None
	self.stable_diffs = None
	self.stable_dir = None

	self.release_name = None
	self.release_diffs = None
	self.release_dir = None

	self.tag_name = None
	self.tag_diffs = None
	self.tag_dir = None

	self.repos=repos
	self.name=name
	if self.name is None:
	   self.name_str = ""
	else:
	   self.name_str = "/" + name 
	self.proj_root = repos + self.name_str
	subdir = commands.getoutput("svn ls " + repos+self.name_str)
	self.dirs = subdir.split("\n")
	self.init(self.dirs)

  def init(self,dlist):
	#
	# Get layout
	#
	self.has_trunk = "trunk/" in dlist
	self.has_releases = "releases/" in dlist
	self.has_tags = "tags/" in dlist
	self.has_branches = "branches/" in dlist
	self.has_stable = "stable/" in dlist
	#
	# Guess release and stable version
	#
	if self.has_releases:
           (self.release_name,self.release_dir) = guess_release(self.repos,self.name_str,"releases")
	   if self.release_dir is None:
	      self.has_releases=False
	if self.has_tags:
           (self.tag_name,self.tag_dir) = guess_release(self.repos,self.name_str,"tags")
	   if self.tag_dir is None:
	      self.has_tags=False
	if self.has_stable:
           (self.stable_name,self.stable_dir) = guess_release(self.repos,self.name_str,"stable")
	   if self.stable_dir is None:
	      self.has_stable=False
	#
	# If have a trunk and stable directory, then summarize the number
	# of differences.
	#
	if self.has_trunk and self.stable_name is not None:
	   output = commands.getoutput("svn diff --summarize " + self.repos+self.name_str+"/trunk " + self.stable_dir)
	   if output == "":
	      self.stable_diffs = 0
	   else:
	      diffs = output.split("\n")
	      self.stable_diffs = len(diffs)
	#
	# If have a trunk and a release directory, then summarize the number
	# of differences.
	#
	if self.has_trunk and self.release_name is not None:
	   output = commands.getoutput("svn diff --summarize " + self.repos+self.name_str+"/trunk " + self.release_dir)
	   if output == "":
	      self.release_diffs = 0
	   else:
	      diffs = output.split("\n")
	      self.release_diffs = len(diffs)
	#
	# If have a trunk and a tag directory, then summarize the number
	# of differences.
	#
	if self.has_trunk and self.tag_name is not None:
	   output = commands.getoutput("svn diff --summarize " + self.repos+self.name_str+"/trunk " + self.tag_dir)
	   if output == "":
	      self.tag_diffs = 0
	   else:
	      diffs = output.split("\n")
	      self.tag_diffs = len(diffs)
	   

  def write(self,format=""):
	if format=="text":
	   print ""
	   print "  Project         ",self.name
	   print "  Project Root    ",self.proj_root
	   print "  Trunk Branch    ",self.has_trunk
	   print "  Normal Branches ",self.has_branches

	   print "  Tag Branches    ",self.has_tags
	   print "    Latest Tag    ",self.tag_name
	   print "    Tag Diffs     ",self.tag_diffs
	   print "    Tag Path      ",self.tag_dir

	   print "  Stable Branches ",self.has_stable
	   print "    Latest Stable   ",self.stable_name
	   print "    Stable Diffs    ",self.stable_diffs
	   print "    Stable Path     ",self.stable_dir

	   print "  Release Branches",self.has_releases
	   print "    Latest Release  ",self.release_name
	   print "    Release Diffs   ",self.release_diffs
	   print "    Release Path    ",self.release_dir
	elif format=="xml" or format=="alerts":
	   print "  <Project>"
	   if self.name is None:
	      print "    <Name>" + "" + "</Name>"
	   else:
	      print "    <Name>" + self.name + "</Name>"
	   print "    <Root>" + self.proj_root + "</Root>"
	   print "    <TrunkBranch>"+`self.has_trunk`+"</TrunkBranch>"
	   print "    <NormalBranches>"+`self.has_branches`+"</NormalBranches>"

	   print "    <TagBranches>"+`self.has_tags`+"</TagBranches>"
	   print "    <LatestTag>"+str(self.tag_name)+"</LatestTag>"
	   print "    <TagDiffs>"+`self.tag_diffs`+"</TagDiffs>"
	   print "    <TagPath>"+str(self.tag_dir)+"</TagPath>"

	   print "    <StableBranches>"+`self.has_stable`+"</StableBranches>"
	   print "    <LatestStable>"+str(self.stable_name)+"</LatestStable>"
	   print "    <StableDiffs>"+`self.stable_diffs`+"</StableDiffs>"
	   print "    <StablePath>"+str(self.stable_dir)+"</StablePath>"

	   print "    <ReleaseBranches>"+`self.has_releases`+"</ReleaseBranches>"
	   print "    <LatestRelease>"+str(self.release_name)+"</LatestRelease>"
	   print "    <ReleaseDiffs>"+`self.release_diffs`+"</ReleaseDiffs>"
	   print "    <ReleasePath>"+str(self.release_dir)+"</ReleasePath>"

	   print "  </Project>"


##
## MAIN
##
if len(sys.argv) == 1:
   print ""
   print "A tool to summarize the state of SVN repositories"
   print ""
   print "svn_summary text <svn-repository> [...]"
   print "  Provide a plain-text summary"
   print ""
   print "svn_summary xml <svn-repository> [...]"
   print "  Provide an XML textual summary"
   print ""
   print "svn_summary [--aux=<repos>,...,<repos>] alerts <svn-repository> [...]"
   print "  Provide alterts for svn externals that appear missing or out of date"
   print ""
   sys.exit(1)

offset=0
auxsvn=[]
if sys.argv[1][:6] == "--aux=":
   offset=1
   tmp=(sys.argv[1])[6:]
   auxsvn = tmp.split(",")
start=2+offset
format=sys.argv[1+offset]

#
# Create a dictionary of Subversion projects and their corresponding release directories
#
svn_projects = {}
dirs = sys.argv[start:] + auxsvn
for repos in dirs:
  if repos[-1] == "/":
     repos=repos[0:-1]
  if format=="xml" or format=="alerts":
     print "<Repository>"
     print "  <Name>"+repos+"</Name>"
     tmp = repos in auxsvn
     print "  <Auxiliary>"+`tmp`+"</Auxiliary>"
  elif format=="text":
     print "REPOSITORY",repos
  dir = commands.getoutput("svn ls " + repos)
  dirs = dir.split("\n")
  has_trunk = "trunk/" in dirs
  if has_trunk:
     proj = SVNProject(repos,None)
     key = repos
     if proj.name is not None:
        key = key+"/"+proj.name
     svn_projects[ key ] = proj
     proj.write(format)
  else:
     for file in dir.split("\n"):
       file = file.strip()
       if file[-1] == "/":
          proj = SVNProject(repos,file[:-1])
          svn_projects[ proj.proj_root ] = proj
          proj.write(format)
  if format=="xml" or format=="alerts":
     print "</Repository>"
  elif format == "text":
     print ""

if format != "alerts":
   sys.exit(0)

#
# Scan for externals, and verify whether they are for the latest releases
#
referenced_projects = {}

for repos_dir in sys.argv[start:]:
  #print " Processing repository", repos_dir
  repos = repos_dir
  if repos[-1] != "/":
     repos = repos + "/"
  cmd="(rm -Rf /tmp/svn_repos; svn checkout -q --ignore-externals " + repos + " /tmp/svn_repos) > /tmp/svn_repos.out 2>&1"
  commands.getoutput(cmd)
  odir = "/tmp/svn_repos"
  externals = find_externals(odir)
  if len(externals) > 0:
     for external in externals:
       rlink = repos + external
       rtype = path_type(rlink)
       rprojroot = guess_projroot(rlink)
       if rprojroot not in svn_projects.keys():
       	  print "RPROJROOT",rprojroot
       	  print "SVN_PROJECTS",svn_projects.keys()
       	  sys.exit(0)
       rproj = svn_projects[rprojroot]

       etype = path_type(odir + "/" + external)
       FILE = open(odir + "/" + external,"r")
       #print " Examining file " + external
       for line in FILE:
	 if len(re.split('[ \t]+',line.strip())) < 2:
	    continue
         link = re.split('[ \t]+',line.strip())[1]
         #print "  LINK: " + link
         #print "  ROOT: " + guess_projroot(link)
         projroot = guess_projroot(link)
         if projroot not in svn_projects.keys():
            print_alert("Error",repos + external,"Project root not found: " + projroot + " for link " + link)
	    #print svn_projects.keys()
	    #print projroot
         else:
	    referenced_projects[projroot] = 1
	    proj = svn_projects[ projroot ]
	    ptype = path_type(link)
	    #
	    # Check if the link is missing, or if it is out of date
	    #
	    if ptype == "trunk":
	       if not proj.has_trunk:
                  print_alert("Error",repos + external,"External missing: " + link)
	       else:
                  print_alert("OK",repos + external,"External OK: " + link)
	    	  if etype == "stable" or etype == "tag" or etype == "release":
                     print_alert("Error",repos + external,"External to a trunk from a " + etype + " banch: " + link)

	    elif ptype == "branches":
	       if not proj.has_branches:
                  print_alert("Error",repos + external,"External missing: " + link)
	       else:
		  #
		  # Branch externals are not verified, but that shouldn't
		  # generate a warning that we need to investigate.
		  #
                  print_alert("OK",repos + external,"External not verified: " + link)

	    elif ptype == "tags":
	       if not proj.has_tags:
                  print_alert("Error",repos + external,"External missing: " + link)
	       elif not link.startswith(proj.tag_dir.strip()):
                  print_alert("LinkUpdate",repos + external,"Update for link " + link + " : " + proj.tag_dir)
	       else:
                  print_alert("OK",repos + external,"External OK: " + link)

	    elif ptype == "releases":
	       if not proj.has_releases:
                  print_alert("Error",repos + external,"External missing: " + link)
	       #
	       # NOTE: we ignore links in releases, except to check that they exist.
	       #
	       #elif not rlink.startswith(rproj.release_dir.strip()):
                  #print_alert("Ignore",rlink,"Old Release Dir: " + rlink)
	       #elif not link.startswith(proj.release_dir.strip()):
                  #print_alert("LinkUpdate",repos + external,"Update for link " + link + " : " + proj.release_dir)
	       else:
                  print_alert("OK",repos + external,"External OK: " + link)

	    elif ptype == "stable":
	       if not proj.has_stable:
                  print_alert("Error",repos + external,"External missing: " + link)
	       elif not rlink.startswith(rproj.stable_dir.strip()):
                  print_alert("Ignore",rlink,"Old Stable Dir: " + rlink)

	       else:
	          if not link.startswith(proj.stable_dir.strip()):
                     print_alert("LinkUpdate",repos + external,"Update for link " + link + " : " + proj.stable_dir)
	          else:
                     print_alert("OK",repos + external,"External OK: " + link)
	    	  if etype == "tag" or etype == "release":
                     print_alert("Error",repos + external,"External to a stable branch from a " + etype + " branch: " + link)
	    else:
               print_alert("NonStandard",repos + external,"External not verified: " + link)

#
# Summarized referenced Projects
#
for projroot in referenced_projects.keys():
  print "<Reference>" + projroot + "</Reference>"
