#!/usr/bin/env python

# new version, 030428
# we now check add/delete differently from the previous version.
# We start with a list of modified files.  "Add" is collected
# by taking any files that are not currently in CVS repository.
# "Delete" is collected by looking at the patch file and find files
# whose deleted lines are equal the numbers of lines in existing files.

# NOTE:
# 	We decide whether a directory is in CVS based on whether it has
#	a CVS sub-directory.  This is not super-safe and accurate, but
#	should be fine in all normal cases.

# TODO:
# 	Need to support '-R" option

import sys;
import os;
import os.path;
import commands;
import string;
import re;
import time;

def usage():
        print
        print "cvspatch [options for 'patch'] <patchfile>";
        print
        print " Execute this under CVS tree, it will apply patch and do ";
        print " corresponding CVS add/rm properly. ";
        print 
        print " --dry-run option will list files that will be added or deleted";
	print " without actually adding or deleting them."
        print
	print " --verbose will cause extra (time consuming) consistency checking.";
        print


def parse_options(argv):
	global strip_level, dry_run, verbose;
	for i in range(1, len(argv)-1):
		if argv[i] == "--dry-run":
			dry_run = 1;
		if argv[i] == "--verbose":
			verbose=1;
		if argv[i][0:2] == "-p":
			if len(argv[i]) > 2 and argv[i][2] >= '0' and argv[i][2] <= '9':
				strip_level = int(argv[i][2]);
			else :
				i = i+1;
				strip_level = int(argv[i]);

def cvs_file_exist(f):
	global verbose;
	output= commands.getoutput("cvs status " + f);
	temp=string.split(commands.getoutput("cvs status " + f));
	for i in range(0, len(temp)):
		if temp[i] == "Status:":
			if temp[i+1] == "Unknown":
				return 0;
			elif temp[i+1] != "Up-to-date" and verbose != 0:
				print "Warning : unexpected cvs status for " + f + " - " + temp[i+1];
			return 1;
	return 0;			# if we can't find "Status" at all

def check_parent_for_add(f):
	global adddirs, verbose;
	if not re.search(r'/', f):
		return;
	p = re.sub(r'/[^/]*$', '', f);
	if p in adddirs: 		# skip if it is already added
		return;
	check_parent_for_add(p);
	if not os.path.isdir(p + "/CVS") :
		adddirs.append(p);

def strip_file(fname, level):
	for j in range(0, level):
		fname = re.sub(r'^[^/]*/', '', fname);
	return fname;
	
def parse_patch_file(pfile):
	global files, addfiles, deletefiles, strip_level;
	f=open(pfile, "r");
	fname1="";
	fname2="";
	while (1==1): 
		line=f.readline();
		if not line: 
			break;
		if re.match(r'\-\-\- ', line) :
			assert(fname2=="");
			fname1=string.split(line)[1];
			continue;
		if re.match(r'\+\+\+ ', line) :
			assert(fname1 != "");
			fname2=string.split(line)[1];
		else:
			continue;

		if (fname2 != "/dev/null"):
			fname = fname2;
		else:
			assert(fname1 != "/dev/null");
			fname = fname1;

		fname1="";
		fname2="";
		fname=strip_file(fname, strip_level);
		files.append(fname);

		nextline = f.readline();
		if re.search(r'-0,0', nextline):
			check_parent_for_add(fname);
			addfiles.append(fname);
		if re.search(r'\+0,0', nextline):
			deletefiles.append(fname);
	f.close();

def verbose_check_files():
	global files, addfiles, deletefiles;
	for i in files:
		time.sleep(1);
		# os.system("usleep 250");
		print "checking " + i + " ...";
		ret=cvs_file_exist(i);
		if i in addfiles:
			if ret :
				print "\tto-be-added file already exists in CVS - " + i;
		else :
			if not ret :
				print "\tto-be-modifed file does not exist in CVS - " + i;

def print_list(prompt, list):
	print prompt;
	for i in list:
		print i;

def do_report():
	global files, addfiles, deletefiles, adddirs;
	print "New directories";
	for i in adddirs:
		print "  ", i;
	print "Modified files (+: newly added; -: removed) ";
	for i in files:
		if i in addfiles:
			print "+ ",
		elif i in deletefiles:
			print "- ",
		else :
			print "  ",
		print i;

def get_prompt(prompt):
	print prompt;
	sys.stdout.flush();
	sys.stdin.readline();

# ========================== MAIN =========================

# figure out options
if len(sys.argv)==1 :
        usage()
        sys.exit()

strip_level=0;
dry_run=0;
verbose=0;
parse_options(sys.argv);
args = string.join(sys.argv[1:-1]);

# --verbose for patch is actually boring
args = re.sub(r'--verbose', '', args);

# obtain a list of 
patch_file=sys.argv[len(sys.argv)-1];
#files=string.split(commands.getoutput("lsdiff " + patch_file));

# experiment
files=[];
addfiles=[];
deletefiles=[];
adddirs=[];
parse_patch_file(patch_file);

# verbose checking
if verbose:
	verbose_check_files();

# report actions to be taken
do_report();

if not dry_run:
	get_prompt("Press <RETURN> to start patching the tree ... ");
else:
	print;
	print "Start patching the tree ...";

# do the patching
os.system("patch " + args + " < " + patch_file);
sys.stdout.flush();

# add/remove files
if not dry_run:
	# another chance
	get_prompt("Press <RETURN> to start CVS adding/deleting ... ");

	for i in adddirs:
		print "adding directory " + i + " ... ";
		os.system("cvs add " + i);
		sys.stdout.flush();
	for i in addfiles:
		print "adding file " + i + " ... ";
		os.system("cvs add " + i);
		sys.stdout.flush();
	for i in deletefiles:
		print "deleting file " + i + " ... ";
		if os.path.exists(i):
			print "\tWarning : deleted file still exists! Patch failure?!";
		os.system("cvs rm -f " + i);
		sys.stdout.flush();

