#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 2002-2003 Mark Ferrell <xrxgrok@yahoo.com>
#
# -----------------------------------------------------------------------
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   1. Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#
#   2. Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
# -----------------------------------------------------------------------

synonyms = ['ca']
summary = """Manipulate cache catalog"""
usage = """Usage: cscvs cache [-ubrSTAB] [-f file]
	-u              Update an existing cache
	-b              Build a new cache, overwritting if exists
	-f file         Read log output from file for building cache
	-t fuzztime	Number of hours into the past to look for new
			revisions (if doing an update)
	-h		Retrieve the headers even for non-updated files
			(if doing an update on a cvs server that doesn't
			support -S)
        -S              Display cache statistics
	-T              Display all tags in the catalog
	-A              Display all authors in the catalog
	-B              Display all branches in the catalog"""

MODE_UNSET  = 0
MODE_UPDATE = 1
MODE_BUILD  = 2

def findSomeRandomFileInRepository(config):
	"""Given that we're inside the base directory of a CVS repository, find the working name of a file"""
	import os, StorageLayer
	CVS = StorageLayer.convertingImport('CVS')

	pipe = os.popen("cvs -q log -h")
	while True:
		line = pipe.readline().strip()
		match = CVS.Parser.Parser._re_working_file.match(line)
		if match is not None: break
	retval = match.group(1)
	pipe.close()
	return retval

def getReposLocFromFile(config, filename):
	"""Determine the base repository location, given the name of a file within the repository"""

	import re, os, StorageLayer
	CVS = StorageLayer.convertingImport('CVS')

	remoteName = os.popen("cvs -q rlog -R '%s'" % os.path.join(config.prefix, filename)).read()
	match = CVS.Parser.Parser._re_rcs_file.match(remoteName)
	if not match:
		raise RuntimeError,  'Unable to parse remote RCS file path: %s' % remoteName
	rcsName = os.path.join(match.group(1), match.group(3))
	prefixMatch = re.match('^(.*)(%s)$' % filename, rcsName)
	if prefixMatch is None:
		raise RuntimeError, 'Unable to determine RCS prefix'
	return os.path.normpath(prefixMatch.group(1))

def update_cache(config, pipe):
	config.readOnly = False
	
	import StorageLayer
	SCM = StorageLayer.convertingImport('SCM')
	CVS = StorageLayer.convertingImport('CVS')

	catalog = SCM.Catalog.Catalog(config)

	if config.reposloc in ('', None):
		config.reposloc = getReposLocFromFile(config, findSomeRandomFileInRepository(config))
		print 'Set remote repository location to %s' % config.reposloc

	parser = CVS.Parser.Parser(config, pipe)

	# plow through all the revisions adding each to a changeset
	revisions = parser.revisions()
	del(parser)
	for rev in revisions:
		if catalog.alreadyHasRevision(rev):
			continue

		branch = catalog.getBranch(rev.branch)

		try:
			# Try appending to the last changeset by this author
			changeset = branch.lastChangesetByAuthor(rev.author)
			if changeset is not None:
				if rev.log == changeset.log:
					changeset.append(rev)
					if rev.type == SCM.Revision.Revision.CHANGE:
						print "U %s/%s: %s +%d-%d\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.addcount, rev.delcount, rev.revision)
					elif rev.type == SCM.Revision.Revision.REMOVE:
						print "R %s/%s: %s\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.revision)
					elif rev.type == SCM.Revision.Revision.ADD:
						print "A %s/%s: %s\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.revision)
					elif rev.type == SCM.Revision.Revision.PLACEHOLDER:
						print "_ %s/%s: %s\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.revision)
					else:
						print "? %s/%s: %s\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.revision)
					continue
				else: raise SCM.ChangeSet.MemberError, 'commit message differs'
			else: raise SCM.ChangeSet.MemberError, 'no previous commit by author %r' % rev.author
		except SCM.ChangeSet.MemberError, e:
			print 'New changeset: %s' % e
			changeset = branch.createChangesetFor(rev)

			if rev.type == SCM.Revision.Revision.CHANGE:
				print "U %s/%s: %s +%d-%d\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.addcount, rev.delcount, rev.revision)
			elif rev.type == SCM.Revision.Revision.REMOVE:
				print "R %s/%s: %s\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.revision)
			elif rev.type == SCM.Revision.Revision.ADD:
				print "A %s/%s: %s\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.revision)
			elif rev.type == SCM.Revision.Revision.PLACEHOLDER:
				print "_ %s/%s: %s\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.revision)
			else:
				print "? %s/%s: %s\t[%s]" % (rev.branch, changeset.index, rev.filename, rev.revision)

		# We don't need the this rev in the revisions list anymore, so lets
		# attempt to get it out of memory
		del(rev)

def cache(config):
	import sys, os, getopt
	import StorageLayer
	SCM = StorageLayer.convertingImport('SCM')
	CVS = StorageLayer.convertingImport('CVS')

	inputfile = None
	module = None
	mode = MODE_UNSET
	show_stats = 0
	show_tags = 0
	show_authors = 0
	show_branches = 0
	use_rlog = 1
	new_server_only_flags=' -S '

	opts, args = getopt.getopt(config.args, 'ubhSTABf:t:')

	for opt, val in opts:
		if opt == '-u':
			mode = MODE_UPDATE
		elif opt == '-b':
			mode = MODE_BUILD
		elif opt == '-h':
			new_server_only_flags = ''
		elif opt == '-t':
			config.fuzzhours = int(val)
		elif opt == '-S':
			show_stats = 1
		elif opt == '-T':
			show_tags = 1
		elif opt == '-A':
			show_authors = 1
		elif opt == '-B':
			show_branches = 1
		elif opt == '-f':
			inputfile = val

	if not use_rlog: new_server_only_flags = ''
	config.use_rlog = use_rlog

	# FIXME we need to make a CVS Repository module and a Log module for this
	catalog = None

	if mode == MODE_UPDATE:
		if not inputfile:
			catalog = SCM.Catalog.Catalog(config)
			if config.reposloc in ('', None):
				raise 'Repository base not available -- catalog created with an older cscvs? Rebuild'
			# Change to the top of the CVS tree
			os.chdir(config.topdir)
			pipe = os.popen("cvs -q rlog %s -d'%s<' '%s'" % (new_server_only_flags, CVS.time(catalog.time - config.fuzztime), config.prefix))
		else:
			sys.stderr.write("cscvs [cache aborted]: Can not update cache from file\n")
			sys.exit(1)
	# FIXME we need to make a CVS Repository module and a Log module for this
	elif mode == MODE_BUILD:
		if not inputfile:
			os.chdir(config.topdir)
			if os.path.exists(config.cat_path):
				os.unlink(config.cat_path)
			pipe = os.popen("cvs -q rlog '%s'" % config.prefix)
		elif os.path.exists(inputfile):
			pipe = open(inputfile)
		else:
			sys.stderr.write("cscvs [cache aborted]: %s No such file or directory\n" % inputfile)
			sys.exit(1)

	if catalog is None:
		catalog = SCM.Catalog.Catalog(config)

	if not mode == MODE_UNSET: update_cache(config, pipe)

	if show_branches:
		sys.stdout.write("Branches: %d\n" % catalog.getBranchCount())
		branches = catalog.branches
		branches.sort()
		for branch in branches:
			sys.stdout.write("\t%s revs on %s\n" % (len(catalog.getBranch(branch)), branch))
	if show_tags:
		tags = catalog.tags
		sys.stdout.write("Tags: %d\n" % len(tags))
		for tag in tags.keys():
			printedHeader = False
			for branch in tags[tag].keys():
				if not printedHeader:
					print '\t%s:\t%s@%s' % (tag, branch, tags[tag][branch])
					printedHeader = True
				else:
					print '\t\t%s@%s' % (branch, tags[tag][branch])
	if show_authors:
		authors = catalog.getAuthorBlame()
		sys.stdout.write("Authors: %d\n" % (len(authors.keys())))
		for author in authors.keys():
			sys.stdout.write("\t%s: %d changesets\n" % (author, authors[author]))
	# Display cache statistics
	if show_stats:
		print "=================================================="
		print "Catalog @ '%s'" % config.cat_path
		print "\tBranches: %d" % catalog.getBranchCount()
		print "\tAuthors:  %d" % catalog.getAuthorCount()
		print "\tEntries:  %d" % catalog.count
		print "\tDate on last entry: %s" % CVS.time(catalog.time)
	catalog.close()


if __name__ == '__main__':
	import os, sys
	sys.path.append('modules')
	import CVS

	config = CVS.Config(os.path.curdir())
	config.cmd = 'cache'
	config.args = sys.args[1:]
	config.cat_path = os.path.join(config.topdir, "CVS/Catalog.sqlite")
	try:	cache(config)
	except CVS.Usage:
		sys.stderr.write("%s\n" % usage)
		sys.exit(1)

# tag: Mark Ferrell Mon Jun  2 14:28:04 CDT 2003 (cmds/cache.py)
#
