#! /usr/local/bin/python

"""Remote CVS -- command line interface"""

from cvslib import CVS, File
import md5
import os
import string
import sys
from cmdfw import CommandFrameWork


class MyFile(File):

	def action(self):
		"""Return a code indicating the update status of this file.

		The possible return values are:
		
		'=' -- everything's fine
		'0' -- file doesn't exist anywhere
		'?' -- exists locally only
		'A' -- new locally
		'R' -- deleted locally
		'U' -- changed remotely, no changes locally
		       (includes new remotely or deleted remotely)
		'M' -- changed locally, no changes remotely
		'C' -- conflict: changed locally as well as remotely
		       (includes cases where the file has been added
		       or removed locally and remotely)
		'D' -- deleted remotely
		'N' -- new remotely
		'r' -- get rid of entry
		'c' -- create entry
		'u' -- update entry

		(and probably others :-)
		"""
		if not self.lseen:
			self.getlocal()
		if not self.rseen:
			self.getremote()
		if not self.eseen:
			if not self.lsum:
				if not self.rsum: return '0' # Never heard of
				else:
					return 'N' # New remotely
			else: # self.lsum
				if not self.rsum: return '?' # Local only
				# Local and remote, but no entry
				if self.lsum == self.rsum:
					return 'c' # Restore entry only
				else: return 'C' # Real conflict
		else: # self.eseen
			if not self.lsum:
				if self.edeleted:
					if self.rsum: return 'R' # Removed
					else: return 'r' # Get rid of entry
				else: # not self.edeleted
					if self.rsum:
						print "warning:",
						print self.file,
						print "was lost"
						return 'U'
					else: return 'r' # Get rid of entry
			else: # self.lsum
				if not self.rsum:
					if self.enew: return 'A' # New locally
					else: return 'D' # Deleted remotely
				else: # self.rsum
					if self.enew:
						if self.lsum == self.rsum:
							return 'u'
						else:
							return 'C'
					if self.lsum == self.esum:
						if self.esum == self.rsum:
							return '='
						else:
							return 'U'
					elif self.esum == self.rsum:
						return 'M'
					elif self.lsum == self.rsum:
						return 'u'
					else:
						return 'C'

	def update(self):
		code = self.action()
		if code == '=': return
		print code, self.file
		if code in ('U', 'N'):
			self.get()
		elif code == 'C':
			print "%s: conflict resolution not yet implemented" % \
			      self.file
		elif code == 'D':
			remove(self.file)
			self.eseen = 0
		elif code == 'r':
			self.eseen = 0
		elif code in ('c', 'u'):
			self.eseen = 1
			self.erev = self.rrev
			self.enew = 0
			self.edeleted = 0
			self.esum = self.rsum
			self.emtime, self.ectime = os.stat(self.file)[-2:]
			self.extra = ''

	def commit(self, message = ""):
		code = self.action()
		if code in ('A', 'M'):
			self.put(message)
		elif code == 'R':
			print "%s: committing removes not yet implemented" % \
			      self.file
		elif code == 'C':
			print "%s: conflict resolution not yet implemented" % \
			      self.file

	def diff(self, opts = []):
		self.action()		# To update lseen, rseen
		if self.lsum == self.rsum:
			return
		import tempfile
		flags = ''
		for o, a in opts:
			flags = flags + ' ' + o + a
		flags = flags[1:]
		fn = self.file
		data = self.proxy.get(fn)
		tfn = tempfile.mktemp()
		try:
			tf = open(tfn, 'w')
			tf.write(data)
			tf.close()
			print 'diff %s -r%s %s' % (flags, self.rrev, fn)
			sts = os.system('diff %s %s %s' % (flags, tfn, fn))
			if sts:
				print '='*70
		finally:
			remove(tfn)

	def commitcheck(self):
		return self.action() != 'C'

	def put(self, message = ""):
		print "Checking in", self.file, "..."
		data = open(self.file).read()
		messages = self.proxy.put(self.file, data, message)
		if messages:
			print messages
		self.setentry(self.proxy.head(self.file), self.lsum)
	
	def get(self):
		data = self.proxy.get(self.file)
		f = open(self.file, 'w')
		f.write(data)
		f.close()
		self.setentry(self.rrev, self.rsum)

	def setentry(self, erev, esum):
		self.eseen = 0		# While we're hacking...
		self.esum = esum
		self.emtime, self.ectime = os.stat(self.file)[-2:]
		self.erev = erev
		self.enew = 0
		self.edeleted = 0
		self.eseen = 1		# Done


class RCVS(CVS):

	FileClass = MyFile

	def __init__(self):
		CVS.__init__(self)

	def update(self, files):
		for e in self.whichentries(files, 1):
			e.update()

	def commit(self, files, message = ""):
		list = self.whichentries(files)
		ok = 1
		for e in list:
			if not e.commitcheck():
				ok = 0
		if not ok:
			print "correct above errors first"
			return
		if not message:
			message = raw_input("One-liner: ")
		for e in list:
			e.commit(message)

	def report(self, files):
		for e in self.whichentries(files):
			e.report()

	def diff(self, files, opts):
		for e in self.whichentries(files):
			e.diff(opts)

	def whichentries(self, files, localfilestoo = 0):
		if files:
			list = []
			for file in files:
				if self.entries.has_key(file):
					e = self.entries[file]
				else:
					e = self.FileClass(file)
					self.entries[file] = e
				list.append(e)
		else:
			list = self.entries.values()
			for file in self.proxy.listfiles():
				if self.entries.has_key(file):
					continue
				e = self.FileClass(file)
				self.entries[file] = e
				list.append(e)
			if localfilestoo:
				for file in os.listdir(os.curdir):
					if not self.entries.has_key(file) \
					   and not self.ignored(file):
						e = self.FileClass(file)
						self.entries[file] = e
						list.append(e)
			list.sort()
		if self.proxy:
			for e in list:
				if e.proxy is None:
					e.proxy = self.proxy
		return list


class rcvs(CommandFrameWork):

	GlobalFlags = 'd:h:p:qv'
	UsageMessage = \
"usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"
	PostUsageMessage = \
		"If no subcommand is given, the status of all files is listed"

	def __init__(self):
		"""Constructor."""
		CommandFrameWork.__init__(self)
		self.proxy = None
		self.cvs = RCVS()

	def options(self, opts):
		self.opts = opts

	def ready(self):
		import rcsclient
		self.proxy = rcsclient.openrcsclient(self.opts)
		self.cvs.setproxy(self.proxy)
		self.cvs.getentries()

	def default(self):
		self.cvs.report([])

	def do_update(self, opts, files):
		"""update [file] ..."""
		self.cvs.update(files)
		self.cvs.putentries()
	do_up = do_update

	def do_commit(self, opts, files):
		"""commit [-m message] [file] ..."""
		message = ""
		for o, a in opts:
			if o == '-m': message = a
		self.cvs.commit(files, message)
		self.cvs.putentries()
	do_com = do_commit
	flags_commit = 'm:'

	def do_diff(self, opts, files):
		"""diff [difflags] [file] ..."""
		self.cvs.diff(files, opts)
	do_dif = do_diff
	flags_diff = 'cbitwcefhnlrsD:S:'



def remove(fn):
	try:
		os.unlink(fn)
	except os.error:
		pass


def main():
	rcvs().run()


if __name__ == "__main__":
	main()
