All pastes #274782 Raw Edit

Anonymous

public text v1 · immutable
·published 2006-12-10 23:23 UTC
rendered paste body
#!/usr/bin/python

import sys, os, output, commands, types, re, time, shutil

from os import *
from os.path import *
from commands import *
from popen2 import *
from select import *
from re import match
from optparse import OptionParser

err_closed = re.compile(".*Connection closed unexpectedly.*")
err_timeout = re.compile(".*Connection timed out.*")
err_malformed = re.compile(".*Malformed.*")
err_validate = re.compile("$Error validating server certificate for.*")
err_locked = re.compile(".*Working copy.*locked.*")
err_nonexistent = re.compile("(.*Not a versioned resource.*)|(.*Not a valid URL.*)|(.*File not found.*)|(.*is not a working copy.*)")
sleep_time=30

def einfo(message):
	sys.stdout.write(output.green(" * ") + message + "\n")

def ewarn(message):
	sys.stderr.write(output.yellow(" * ") + message + "\n")

def eerror(message):
	sys.stderr.write(output.red(" * ") + message + "\n")

def svninfo(message):
		sys.stdout.write(output.teal("   SVN: ") + message + "\n")

def svnwarn(message):
		sys.stderr.write(output.yellow("   SVN: ") + message + "\n")

def svnerr(message):
		sys.stderr.write(output.red("   SVN: ") + message + "\n")


## Class item_info encapsulates information from the svn info command regarding a module
class item_info:

	def __init__(self, handler, item):
		self.handler = handler
		self.item = item.strip("/ ")
		self.wc = self.handler.base + "/" + self.item
		self.tags = {}
		self.update(False)
		if not self.handler.offline:
			self.update(True)
	
	def info(self, item, local=False):
		tags = {}
			
		def on_output(line, tags=tags):
			line=line.strip()
			if err_nonexistent.match(line):
				tags["Node Kind"] = "nonexistent"
			elif line != "":
				(tag, value) = line.split(":", 1)
				tags[tag] = value.strip()

		def on_error(line, tags=tags):
			if err_nonexistent.match(line):
				tags["Node Kind"] = "nonexistent"
		
		if local and not exists(item):
			tags["Node Kind"] = "nonexistent"
		else:
			command="svn info " + item
			self.handler.perform(command, on_output, on_error)

		return tags

	def modifiedP(self):
		return (not self.existsP(False)) or (self.existsP(True) and self.revision(False) != self.revision(True))

	# Determine type of module (file, directory or missing)
	def type(self, remote=None):
		if remote == None:
			remote = not self.handler.offline
		return self.tags[remote]["Node Kind"]
		
	# Determine if module is a file
	def fileP(self, remote=None):
		if remote == None:
			remote = not self.handler.offline
		return self.type(remote) == "file"

	# Determine if module is a directory
	def directoryP(self, remote=None):
		if remote == None:
			remote = not self.handler.offline
		return self.type(remote) == "directory"

	# Determine if module exists
	def existsP(self, remote=None):
		if remote == None:
			remote = not self.handler.offline
		return not self.type(remote) == "nonexistent"

	def revision(self, remote=None):
		if remote == None:
			remote = not self.handler.offline
		for tag in "Last Changed Rev", "Revision":
			if tag in self.tags[remote]:
				return self.tags[remote][tag]
		else:
			eerror("QA notice: revision could not be determined")

	def mode(self, oldmode):
		modepath = self.wc + "/.svn.eclass.mode"
		if isfile(modepath):
			modefile = file(modepath, "r")
			mode = {"deep":True, "shallow":False}[modefile.readline().strip()]
			modefile.close()
			return mode
		elif self.directoryP(False):
			return True
		else:
			return oldmode

	def setmode(self, mode):
		modepath = self.wc + "/.svn.eclass.mode"
		modefile = file(modepath, "w")
		modefile.write({True:"deep", False:"shallow"}[mode])
		modefile.close()

	def remove(self):
		ewarn("Deleting " + self.wc)
		shutil.rmtree(self.wc)
		self.update(False)

	def update(self, remote=False):
		if not remote:
			self.tags[False] = self.info(self.wc, True)
		else:
			if self.existsP(False):
				self.repository = self.tags[False]["URL"].strip("/ ")
			else:
				self.repository = self.handler.repository + "/" + self.item
			self.tags[True] = self.info(self.repository)

	def log(self, revision):
		einfo("Differences between revision " + revision + " and latest revision " + self.revision())
		def on_output(line):
			if line!="":
				svninfo(line)
		command="svn log --revision " + str(int(revision)+1) + ":" + self.revision() + " " + self.repository
		self.handler.perform(command, on_output)

# Construct complete list of improper ancestors of item

class subversion_handler:

	def __init__(self, repository, path, offline):
		self.repository = repository
		self.path = path
		self.offline = offline
		self.infos = {}
		self.base = repository[repository.rfind("/")+1:]
		chdir(path)

	def handle_error(self, error, command, on_output, on_error):

		if err_closed.match(error) or err_timeout.match(error) or err_malformed.match(error): 
			ewarn("Subversion has reported the following network error:")
			svnwarn(error.split(":")[1].strip())
			einfo("Retrying in " + str(sleep_time) + " seconds")
			time.sleep(sleep_time)
			self.perform(command, on_output, on_error)
		elif err_locked.match(error):
			ewarn("Subversion reports a lock, another ebuild might be active.")
			ewarn("If no other ebuilds are active, interrupt, and execute svn cleanup on the reported directory:")
			svnwarn(error.split(":")[1].strip())
			einfo("Retrying in " + str(sleep_time) + " seconds")
			time.sleep(sleep_time)
			self.perform(command, on_output, on_error)
		else:
			on_error(error)
		return True
		
	def perform(self, command, on_output=svninfo, on_error=svnerr):
		process=Popen3(command, True)
		(stdin, stdout, stderr) = (process.tochild, process.fromchild, process.childerr)
		error = False
		while not error:
			(readables, writables, errors) = select([stdout, stderr], [], [stdin, stdout, stderr])
			for stream in readables:
				for line in stream:
					if stream == stdout:
						on_output(line.strip())
					elif stream == stderr:
						error = self.handle_error(line.strip(), command, on_output, on_error)
						if error:
							break
			if stderr.readline() == "" and stdout.readline() == "": break
		else:
			stdin.close and stdout.close and stderr.close

	def checkout(self, module="", recurse=False, ignore_externals=False):
		url=(self.repository + module).rstrip("/ ")
		einfo("Performing initial " + {True:"recursive "}.get(recurse, "") + "checkout of " + url)
		command="svn checkout --ignore-externals " + {False:"-N "}.get(recurse, "") + url
		self.perform(command)

	def update(self, module="", recurse=False, ignore_externals=False):
		if not self.offline:
			url=(self.base + "/" + module).rstrip("/ ")
			einfo("Updating working copy of " + url + {True:" recursively"}.get(recurse, ""))
			command="svn update --ignore-externals " + {False:"-N "}.get(recurse, "") + url
			self.perform(command)
	  
	def info(self, item=""):
		if item in self.infos:
			return self.infos[item]
		else:
			return item_info(self, item)

def ancestors(item, recurse, descendents=[]):
	if item == "":
		return descendents[0]
	else:
		return ancestors(dirname(item), False, [[[[item, recurse]] + descendents]])

# Create tree of ancestries
def ancestree(ancestries, ancestors):
	for ancestry in ancestries:
		if ancestry[0][0] == ancestors[0][0][0]:
			if ancestors[0][0][1] or ancestry[0][1]:
				ancestry[0][1] = True
				if len(ancestry) >= 1:
					del ancestry[1:]
			elif len(ancestry) == 1:
				ancestry.append(ancestors[0][1])
			elif len(ancestors[0]) > 1:
				ancestree(ancestry[1], ancestors[0][1])
			break
	else:
		ancestries += ancestors

def print_ancestries(ancestries, indent=0):
	for ancestry in ancestries:
		if type(ancestry[0][0]) == types.StringType:
			tabspace = ""
			for i in range(indent):
				tabspace += "  "
			print tabspace + {True:output.red, False:output.blue}[ancestry[0][1]](ancestry[0][0])
			if len(ancestry) > 1:
				print_ancestries(ancestry[1], indent+1)
		else:
			print_ancestries(ancestry[1:], indent+1)

def update_modules(ancestries, svnrevs):
	for ancestry in ancestries:
		module=ancestry[0][0]
		info = subversion.info(module)
		if type(module == types.StringType):
			recurse=ancestry[0][1]
			if info.directoryP():
				if info.mode(recurse):
					recurse = True
				elif recurse:
					info.remove()
				#if info.modifiedP():
				# Unfortunately there is no way to determine if files have been erased
				# So update always
				subversion.update(module, recurse)
				info.update()
				info.setmode(recurse)
				if svnrevs:
					svnrevs.write(module + ":" + info.revision(False) + "\n")
			if len(ancestry) > 1:
				update_modules(ancestry[1], svnrevs)
		else:
			update_modules(ancestry[1:], svnrevs)

if __name__ == "__main__":

	parser = OptionParser()
	parser.add_option("--work-base", action="store", type="string", dest="work_base", help="local storage for working copies")
	parser.add_option("--repository", action="store", type="string", dest="repository", help="repository to check out from")
	parser.add_option("--revdb-in", action="store", type="string", dest="revdb_in", help="path to file to read revision numbers from")
	parser.add_option("--revdb-out", action="store", type="string", dest="revdb_out", help="path to file to record revision numbers in")
	parser.add_option("--deep", action="append", type="string", dest="deep", default=[], help="item to be checked out recursively")
	parser.add_option("--shallow", action="append", type="string", dest="shallow", default=[], help="item to be checked out non-recursively")
	parser.add_option("--check", action="append", type="string", dest="check", default=[], help="item to be checked for revision changes")
	parser.add_option("--logonly", action="store_true", dest="logonly", help="")
	parser.add_option("--checkrevs", action="store_true", dest="checkrevs", help="")
	parser.add_option("--offline", action="store_true", dest="offline", help="specify if operation is to be in offline mode")
	(values, args) = parser.parse_args()

	work_base = values.work_base
	repository = values.repository
	revdb_in = values.revdb_in and file(values.revdb_in, "r")
	revdb_out = values.revdb_out and file(values.revdb_out, "w")
	deep = [ deep.strip("/ ") for deep in values.deep ]
	shallow = [ shallow.strip("/ ") for shallow in values.shallow ]
	check = [ check.strip("/ ") for check in values.check ]
	checkrevs = values.checkrevs
	logonly = values.logonly
	revisions = {}

	base_module=repository[repository.rfind("/")+1:]
	working_copy=work_base + "/" + base_module

	if not isdir(work_base):
		einfo("Creating local repository for working copies in " + work_base)
		mkdir(work_base)
	
	subversion = subversion_handler(repository, work_base, values.offline)

	# Extract revisions from input revision database of item:revision pairs
	if revdb_in:
		for line in revdb_in:
			(item, revision) = line.split(":")
			revisions[item] = revision.strip()
		revdb_in.close()

	if checkrevs and len(check) > 0:
		for item in check:
			einfo({True:"(offline) "}.get(values.offline, "") + "Comparing installed vs. latest revision of " + item)
			info = subversion.info(item)
			# If repository claims item as directory, compare its revision to revision provided
			if info.directoryP():
				if item not in revisions:
					break
				elif info.revision() != revisions[item]:
					if logonly:
						info.log(revisions[item])
					else:
						break
			# If repository claims item as file, compare its topdir's revision to revision provided for topdir
			elif info.fileP():
				topdir = dirname(item)
				einfo({True:"(offline) "}.get(values.offline, "") + "Comparing installed vs. latest revision of " + topdir)
				topdir_info = subversion.info(topdir)
				if topdir not in revisions:
					break
				elif topdir_info.revision() != revisions[topdir]:
					if logonly:
						topdir_info.log(revisions[topdir])
					else:
						break
			# If repository claims item does not exist, see if it is in provided revision
			elif item in revisions:
				if not logonly:
					break
		else:
			if logonly:
				sys.exit(17)
			else:
				einfo("Revisions have NOT changed")
				sys.exit(16)
		einfo("Revisions have changed")
	if logonly:
		sys.exit(17)
		
	if not values.offline:
		einfo("Synchronizing with repository at " + repository + " in " + work_base)
		if isdir(working_copy + "/.svn"):
			if subversion.info().modifiedP():
				subversion.update()
		else:
			subversion.checkout()

	ancestries=[]
	for item in deep:
		ancestree(ancestries, ancestors(item, True))
	for item in shallow:
		ancestree(ancestries, ancestors(item, False))

	#print_ancestries(ancestries)
	update_modules(ances