#! /usr/bin/python
#  -*- Python -*-

"""Kernel synchronization utility

This script allows synchronization between ALSA CVS repository and 
standard kernel sources stored in local BK repository.

This tool is intended mainly for internal use of Jaroslav Kysela.

Usage:
	%(PROGRAM)s [options] command

Where options is:

	-h
	--help
		Print this help

	-C <path>
	--cvsroot=<path>
		Set root of ALSA CVS repository

	-B <path>
	--bkroot=<path>
		Set root of Linux kernel BitKeeper repository

Where command is:

	diffall
		Diff between ALSA and Linux kernel repositories
	diffall -r
		Reverse diff between ALSA and Linux kernel repositories

"""

import os
import sys
import string
import time
import dircache
import getopt
import re

# define for documentation
PROGRAM = sys.argv[0]

# define working directories
CVSROOT = '~/alsa'
BKROOT = '~/linux/official'
BKROOT1 = ''
PATCH_UPDATE = '~/alsa/alsa-kernel/scripts/kchanges/update'

# exclude some files or directories
ALSA_EXCLUDE = ['/include/version.h']
ALSA_EXCLUDE_DIR = ['/scripts', '/oss', '/kbuild']
KERNEL_EXCLUDE = ['/COPYING', '/CREDITS',
		  '/MAINTAINERS','/Makefile',
		  '/README','/REPORTING-BUGS',
		  '/include/sound/version.h']
KERNEL_EXCLUDE_DIR = ['/BitKeeper',
		      '/Documentation',
		      '/arch', '/drivers', '/crypto', '/fs',
		      '/include', '/init', '/ipc',
		      '/kernel', '/lib', '/mm', '/usr',
		      '/net', '/scripts', '/security',
		      '/sound/oss']
# force to process
ALSA_FORCE = []
ALSA_FORCE_DIR = ['/']
KERNEL_FORCE = []
KERNEL_FORCE_DIR = ['/Documentation/sound/alsa', '/include/sound']

# Directory mapping
ALSA_MAP = {'/':'/sound',
	    '/Documentation':'/Documentation/sound/alsa',
	    '/include':'/include/sound'}
KERNEL_MAP = {}		# Initialized from ALSA_MAP

# Comment mapping
COMMENT_MAP = [
		('/drivers/mpu401'	,'MPU401 UART'),
	 	('/include/asound_fm.h' ,'Raw OPL FM'),
		('/drivers/opl3'	,'OPL3'),
		('/drivers/opl4'	,'OPL4'),
		('/drivers/vx'		,'Digigram VX core'),
		('/drivers'		,'Generic drivers'),
		('/arm/Kconfig'		,'ARM'),
		('/include/uda1341.h'	,'UDA1341'),
		('/arm/sa11xx-uda1341.c','SA11xx UDA1341 driver'),
		('/arm'			,'ERROR'),
		('/isa/Kconfig'		,'ISA'),
		('/isa/ad1816a'		,'AD1816A driver'),
		('/include/ad1848.h'	,'AD1848 driver'),
		('/isa/ad1848'		,'AD1848 driver'),
		('/include/cs4231.h'	,'CS4231 driver'),
		('/isa/cs423x/cs4231.*'	,'CS4231 driver'),
		('/isa/cs423x/cs4236.*'	,'CS4236+ driver'),
		('/isa/cs423x/.*pc98.*'	,'PC98(CS423x) driver'),
		('/isa/cs423x'		,'CS423x drivers'),
		('/isa/es1688'		,'ES1688 driver'),
		('/isa/gus/gus_.*'	,'GUS Library'),
		('/isa/gus/gusclassic.c','GUS Classic driver'),
		('/isa/gus/gusextreme.c','GUS Extreme driver'),
		('/isa/gus/gusmax.c'	,'GUS MAX driver'),
		('/isa/gus/interwave.*'	,'AMD InterWave driver'),
		('/isa/gus'		,'GUS drivers'),
		('/include/sb.h'	,'SB drivers'),
		('/isa/sb/es968.c'	,'ES968 driver'),
		('/include/sfnt_info.h' ,'SoundFont'),
		('/include/soundfont.h' ,'SoundFont'),
		('/include/emu8000.h'	,'EMU8000 driver'),
		('/isa/sb/emu8000.*'	,'EMU8000 driver'),
		('/isa/sb/sb16.*'	,'SB16/AWE driver'),
		('/isa/sb/sb8.*'	,'SB8 driver'),
		('/isa/sb'		,'SB drivers'),
		('/isa/opti9xx'		,'Opti9xx drivers'),
		('/isa/wavefront'	,'Wavefront drivers'),
		('/isa/als100.c' 	,'ALS100 driver'),
		('/isa/azt2320.c'	,'AZT2320 driver'),
		('/isa/cmi8330.c'	,'CMI8330 driver'),
		('/isa/dt019x.c' 	,'DT019x driver'),
		('/isa/es18xx.c' 	,'ES18xx driver'),
		('/isa/opl3sa2.c'	,'OPL3SA2 driver'),
		('/isa/sgalaxy.c'	,'Sound Galaxy driver'),
		('/include/sscape_ioctl.h','Sound Scape driver'),
		('/isa/sscape.c'	,'Sound Scape driver'),
		('/isa'			,'ERROR'),
		('/pci/ac97/ak4531_codec.c','AK4531 codec'),
		('/include/ac97_codec.h','AC97 Codec Core'),
		('/pci/ac97'		,'AC97 Codec Core'),
		('/pci/ali5451'		,'ALI5451 driver'),
		('/include/emu10k1.h'	,'EMU10K1/EMU10K2 driver'),
		('/pci/emu10k1'		,'EMU10K1/EMU10K2 driver'),
		('/pci/au88x0'		,'au88x0 driver'),
		('/pci/ice1712/envy24ht.h','ICE1724 driver'),
		('/pci/ice1712/revo.(c|h)','ICE1724 driver'),
		('/pci/ice1712/amp.(c|h)','ICE1724 driver'),
		('/pci/ice1712/ice1724.c','ICE1724 driver'),
		('/pci/ice1712'		,'ICE1712 driver'),
		('/pci/korg1212'	,'KORG1212 driver'),
		('/pci/mixart'		,'MIXART driver'),
		('/pci/nm256'		,'NM256 driver'),
		('/include/hdsp.h'	,'RME HDSP driver'),
		('/pci/rme9652/hdsp.c'	,'RME HDSP driver'),
		('/pci/rme9652'		,'RME9652 driver'),
		('/include/ymfpci.h'	,'YMFPCI driver'),
		('/pci/ymfpci'		,'YMFPCI driver'),
		('/include/trident.h'	,'Trident driver'),
		('/pci/trident'		,'Trident driver'),
		('/pci/vx222'		,'Digigram VX222 driver'),
		('/include/cs46xx.h'	,'CS46xx driver'),
		('/pci/cs46xx'		,'CS46xx driver'),
		('/pci/als4000.c'	,'ALS4000 driver'),
		('/pci/azt3328.c'	,'AZT3328 driver'),
		('/pci/cmipci.c' 	,'CMIPCI driver'),
		('/pci/cs4281.c' 	,'CS4281 driver'),
		('/pci/ens1370.c'	,'ENS1370/1+ driver'),
		('/pci/ens1371.c'	,'ENS371+ driver'),
		('/pci/es1938.c' 	,'ES1938 driver'),
		('/pci/es1968.c' 	,'ES1968 driver'),
		('/pci/fm801.c'  	,'FM801 driver'),
		('/pci/intel8x0.c'	,'Intel8x0 driver'),
		('/pci/maestro3.c'	,'Maestro3 driver'),
		('/pci/rme32.c'		,'RME32 driver'),
		('/pci/rme96.c'		,'RME96 driver'),
		('/pci/sonicvibes.c'	,'SonicVibes driver'),
		('/pci/via82xx.c'	,'VIA82xx driver'),
		('/pci/bt87x.c'		,'BT87x driver'),
		('/pci/atiixp.c'	,'ATIIXP driver'),
		('/pci/intel8x0m.c'	,'Intel8x0-modem driver'),
		('/pci/Kconfig'		,'PCI drivers'),
		('/pci/Makefile'	,'PCI drivers'),
		('/pci'			,'ERROR'),
		('/ppc/Kconfig'		,'PPC'),
		('/ppc/awacs.(ch)'	,'PPC AWACS driver'),
		('/ppc/burgundy.(ch)'	,'PPC Burgundy driver'),
		('/ppc/daca.c'		,'PPC DACA driver'),
		('/ppc/keywest.c'	,'PPC Keywest driver'),
		('/ppc/pmac.(c|h)'	,'PPC PMAC driver'),
		('/ppc/powermac.c'	,'PPC PowerMac driver'),
		('/ppc/tumbler.(c|h)'	,'PPC Tumbler driver'),
		('/ppc'			,'ERROR'),
		('/i2c/l3'		,'L3 drivers'),
		('/include/tea575x-tuner.h','TEA575x tuner'),
		('/i2c/other/tea575x-tuner.c','TEA575x tuner'),
		('/include/ak4117.h'	,'AK4117 receiver'),
		('/i2c/other/ak4117.c'  ,'AK4117 receiver'),
		('/i2c/other'		,'Serial BUS drivers'),
		('/include/i2c.h'	,'I2C lib core'),
		('/i2c/i2c.c'		,'I2C lib core'),
		('/include/cs8427.h'	,'I2C cs8427'),
		('/i2c/cs8427.c'	,'I2C cs8427'),
		('/i2c'			,'ERROR'),
		('/parisc/harmony.c'	,'PARISC Harmony driver'),
		('/parisc/Kconfig'	,'PARISC'),
		('/parisc'		,'ERROR'),
		('/sparc/amd7930.c'	,'SPARC AMD7930 driver'),
		('/sparc/cs4231.c'	,'SPARC cs4231 driver'),
		('/sparc/Kconfig'	,'SPARC'),
		('/sparc'		,'ERROR'),
		('/include/emux_synth.h','Common EMU synth'),
		('/synth/emux'		,'Common EMU synth'),
		('/synth/Makefile'	,'Synth'),
		('/pcmcia/vx'		,'Digigram VX Pocket driver'),
		('/pcmcia/pdaudiocf'	,'Sound Core PDAudioCF driver'),
		('/pcmcia/Kconfig'	,'PCMCIA Kconfig'),
		('/pcmcia/Makefile'	,'PCMCIA'),
		('/pcmcia'		,'ERROR'),
		('/usb/Kconfig'		,'USB'),
		('/usb/usbaudio.(c|h)'	,'USB generic driver'),
		('/usb/usbmidi.(c|h)'	,'USB generic driver'),
		('/usb/usbmixer.(c|h)'	,'USB generic driver'),
		('/usb/usbquirks.(c|h)' ,'USB generic driver'),
		('/usb/usbmixer_maps.c' ,'USB generic driver'),
		('/usb'			,'ERROR'),
		('/core/ioctl32'	,'IOCTL32 emulation'),
		('/include/pcm_oss.h'	,'ALSA<-OSS emulation'),
		('/core/oss'		,'ALSA<-OSS emulation'),
		('/core/seq/oss'	,'ALSA<-OSS sequencer'),
		('/core/seq/instr'	,'Instrument layer'),
		('/include/seq_kernel.h','ALSA sequencer'),
		('/include/asequencer.h','ALSA sequencer'),
		('/core/seq'		,'ALSA sequencer'),
		('/core/Kconfig'	,'ALSA Core'),
		('/core/memalloc.*'	,'Memalloc module'),
		('/core/sgbuf.*'	,'Memalloc module'),
		('/core/rtctimer.*'	,'RTC timer driver'),
		('/include/timer.h'	,'Timer Midlevel'),
		('/core/timer.*'	,'Timer Midlevel'),
		('/include/rawmidi.*'	,'RawMidi Midlevel'),
		('/core/rawmidi.*'	,'RawMidi Midlevel'),
		('/include/pcm.*'	,'PCM Midlevel'),
		('/core/pcm.*'		,'PCM Midlevel'),
		('/include/hwdep.*'	,'HWDEP Midlevel'),
		('/core/hwdep.*'	,'HWDEP Midlevel'),
		('/include/control.*'	,'Control Midlevel'),
		('/core/control.*'	,'Control Midlevel'),
		('/include/asound.h'	,'ALSA Core'),
		('/include/version.h'	,'ALSA Version'),
		('/include/initval.h'	,'ALSA Core'),
		('/include/minors.h'	,'ALSA Minor Numbers'),
		('/include/sndmagic.h'	,'ALSA Core'),
		('/include/info.h'	,'ALSA Core'),
		('/include/core.h'	,'ALSA Core'),
		('/include/memalloc.h'  ,'ALSA Core'),
		('/core/info.*'		,'ALSA Core'),
		('/core/control.*'	,'ALSA Core'),
		('/core/init.*'		,'ALSA Core'),
		('/core/sound.*'	,'ALSA Core'),
		('/core/memory.*'	,'ALSA Core'),
		('/core/misc.*'		,'ALSA Core'),
		('/core/Makefile'	,'ALSA Core'),
		('/core'		,'ERROR'),
		('/Documentation'	,'Documentation'),
		('/include'		,'ERROR'),
		('/sound_core.c'	,'OSS device core'),
		('/scripts'		,'IGNORE'),
		('/'			,'ERROR')
	       ]

# Global variables
LAST_CVS_TIME = 0
FATAL_LSYNC_ERROR = 0

# Translation tables for CVS user IDs
CVS_USER = {'perex':'Jaroslav Kysela <perex@suse.cz>',
	    'uid53661':'Jaroslav Kysela <perex@suse.cz>',	# seems SF CVS malfunction
	    'tiwai':'Takashi Iwai <tiwai@suse.de>',
	    'abramo':'Abramo Bagnara <abramo@alsa-project.org>',
	    'cladisch':'Clemens Ladisch <clemens@ladisch.de>'}

def usage(code, msg=''):
	print __doc__ % globals()
	if msg:
		print msg
	sys.exit(code)

def get_cvs_root():
	return os.path.expanduser(CVSROOT + '/alsa-kernel')

def get_bk_root(root=''):
	if root == '':
		root = BKROOT
	return os.path.expanduser(root)

def my_popen(cmd):
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	return lines

def print_file(fp, lines):
	for line in lines:
		fp.write(line)

def print_file_comment(fp, lines, space=''):
	for line in lines:
		fp.write('# %s' % space)
		fp.write(line)

def modify_version_file():
	filename = get_cvs_root() + '/include/version.h'
	fp = open(filename)
	lines = fp.readlines()
	fp.close()
	fp = open(filename + '.new', 'w')
	for line in lines:
		str = '#define CONFIG_SND_DATE'
		if line[0:len(str)] == str:
			t = time.gmtime(LAST_CVS_TIME)
			ts = time.strftime('%a %b %d %H:%M:%S %Y', t)
			str = str + ' " (%s UTC)"\n' % ts
			fp.write(str)
		else:
			fp.write(line)
	fp.close()
	os.rename(filename + '.new', filename)

def update_cvs_file(file, destfile = '', params = ''):
	path, file = os.path.split(file)
	try:
		os.chdir(get_cvs_root() + '/' + path)
	except OSError, msg:
		os.mkdir(get_cvs_root() + '/' + path)
		pass
	if destfile == '':
		cmd = 'cvs -z3 update %s' % file
	else:
		cmd = 'cvs -z3 update -p %s %s' % (params, file)
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	if destfile == '':
		print_file(sys.stderr, lines)
	else:
		fp = open(destfile, 'w')
		print_file(fp, lines)
		fp.close()

def update_bk_file(file, root=''):
	path, file = os.path.split(file)
	if os.path.isdir(path):
		os.chdir(path)
	else:
		xpath, xfile = os.path.split(path)
		os.chdir(xpath)
		cmd = 'bk mkdir %s' % xfile
		print cmd
		fp = os.popen(cmd)
		lines = fp.readlines()
		fp.close()
		print_file(sys.stderr, lines)
		os.chdir(path)
	cmd = 'bk get %s' % file
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	print_file(sys.stderr, lines)

def change_diff_line(line, file):
	if line[:6] == 'Index:':
		return 'Index: ' + file + '\n'
	if line[:7] == 'diff -u':
		return 'diff -u ' + file + '.old ' + file + '\n'
	if line[:4] == '--- ':
		file = file + '.old'
	if line[:4] == '--- ' or line[:4] == '+++ ':
		idx = string.find(line[4:], '\t')
		nline = line[:4] + file + line[4+idx:]
		return nline
	return line

def change_diff_lines(lines, file):
	if len(lines) > 1:
		lines[0] = change_diff_line(lines[0], file)
		lines[1] = change_diff_line(lines[1], file)
	return lines

def update_last_cvs_time(stime):
	global LAST_CVS_TIME
	# mktime returns local time, and we need to convert
	# the result to GMT (UTC) time, so substract the difference
	# in seconds between local time and GMT (time.timezone)	
	gm_time = time.mktime(time.strptime(stime)) - time.timezone
	if (LAST_CVS_TIME < gm_time):
		LAST_CVS_TIME = gm_time

def get_cvs_files(base, dir):
	dlist = []
	flist = []

	# Read all entries
	fp = open(base + dir + 'CVS/Entries')
	entries = fp.readlines()
	fp.close()

	# Process files entries
	for e in entries:
		try:
			flags, name, rev, time, unk1, unk2 = string.split(e, '/')
			if string.count(flags, 'D') <= 0:
				update_last_cvs_time(time)
				flist.append(dir + name);
		except ValueError, msg:
			pass

	# Process directory entries
	for e in entries:
		try:
			flags, name, rev, time, unk1, unk2 = string.split(e, '/')
			if string.count(flags, 'D') > 0 and not ALSA_EXCLUDE_DIR.count(dir + name):
				dlist1, flist1 = get_cvs_files(base, dir + name + '/');
				dlist.append(dir + name);
				dlist = dlist + dlist1
				flist = flist + flist1
		except ValueError, msg:
			pass

	return dlist, flist

def get_bk_files(base, dir):
	dlist = []
	flist = []

	# merge files
	sccs_dir = base + dir + 'SCCS/'
	l = dircache.listdir(sccs_dir)
	for f in l:
		if f == 's.ChangeSet':
			dummy = 1
		elif f[0:2] == 's.':
			if not os.path.isdir(sccs_dir + f):
				flist.append(dir + f[2:])
	del l

	# merge directories
	l = dircache.listdir(base + dir)
	for f in l:
		if f == 'SCCS':
			dummy = 1	# Nothing
		elif os.path.isdir(base + dir + f):
			if os.path.isdir(base + dir + f + '/SCCS/') and not KERNEL_EXCLUDE_DIR.count(dir + f):
				dlist1, flist1 = get_bk_files(base, dir + f + '/')
				dlist.append(dir + f)
				dlist = dlist + dlist1
				flist = flist + flist1
			else:
				if not KERNEL_EXCLUDE_DIR.count(dir + f):
					print 'Ignoring BK directory %s' % base + dir + f
	del l

	return dlist, flist

def remap_engine(dir, dict):
	comp = string.split(dir, '/')
	add = ''
	while len(comp) > 0:
		tmp = string.join(comp, '/');
		if tmp == '':
			tmp = '/'
		if dict.has_key(tmp):
			if add != '' and dict[tmp] != '/':
				add = '/' + add
			return dict[tmp] + add;
		if add == '':
			add = comp.pop();
		else:
			add = comp.pop() + '/' + add;
	return dir

def remap_alsa(dir):
	return remap_engine(dir, ALSA_MAP);

def remap_kernel(dir):
	return remap_engine(dir, KERNEL_MAP);

def remap_alsa_file(file):
	comp = string.split(file, '/')
	path = string.join(comp[:len(comp)-1],'/')
	if path == '':
		path = '/'
	path = remap_alsa(path)
	if path[-1] != '/':
		path = path + '/'
	return path + comp[-1]

def remap_kernel_file(file):
	comp = string.split(file, '/')
	path = string.join(comp[:len(comp)-1],'/')
	if path == '':
		path = '/'
	path = remap_kernel(path)
	if path[-1] != '/':
		path = path + '/'
	return path + comp[-1]

def get_cvs_date_param(from_cvs_time, to_cvs_time):
	if from_cvs_time:
		xtime1 = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(from_cvs_time))
	if to_cvs_time:
		xtime2 = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(to_cvs_time))
	if from_cvs_time and not to_cvs_time:
		return "-d'%s<'" % xtime1
	elif not from_cvs_time and to_cvs_time:
		return "-d'%s>'" % xtime2
	else:
		return "-d'%s<%s'" % (xtime1, xtime2)

def get_cvs_date_param1(to_cvs_time):
	if to_cvs_time:
		xtime1 = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(to_cvs_time))
		return "-D'%s'" % xtime1
	else:
		return ''

def collect_cvs_logs(file, from_cvs_time, to_cvs_time):
	path, file = os.path.split(file)
	os.chdir(get_cvs_root() + '/' + path)
	cmd = "cvs -z3 log %s %s" % (get_cvs_date_param(from_cvs_time, to_cvs_time), file)
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	i = 0
	fdead = 1
	dead = 0
	next_is_comment = 0
	result = ''
	while i < len(lines):
		if lines[i][0:11] == 'revision ':
			revision = line[i][11:]
			i = i + 1
		elif lines[i][0:6] == 'date: ':
			date, author, state, xlines = string.split(lines[i], ';')
			date = date[6:]
			author = string.lstrip(author)[8:]
			state = string.lstrip(state)[7:]
			if state == 'dead' and fdead:
				dead = 1
			i = i + 1
			next_is_comment = 1
			fdead = 0
		elif next_is_comment:
			next_is_comment = 0
			comment = ''
			while i < len(lines) and lines[i][0:6] != '------' and lines[i][0:6] != '======':
				if lines[i][0:9] == 'branches:' or lines[i][0:11] == '*BK_IGNORE*':
					while i < len(lines) and lines[i][0:6] != '------' and lines[i][0:6] != '======':
						i = i + 1
				else:
					comment = comment + '  ' + lines[i]
					i = i + 1
			result = result + '%s, %s :\n' % (CVS_USER[author], date) + comment
		else:
			i = i + 1
	return result, dead

def write_to_patch_update_file(lines):
	global PATCH_UPDATE
	fp = open(os.path.expanduser(PATCH_UPDATE), 'a+')
	print_file(fp, lines)
	fp.close()

def do_alsa_kernel_diff(alsa, kernel, from_cvs_time, to_cvs_time, ofile=sys.stdout):
	global FATAL_LSYNC_ERROR
	ncvsflag = 0
	nbkflag = 0
	lsyncflag = 0
	if from_cvs_time or to_cvs_time:
		lsyncflag = 1
#		ncvsflag = 1
#		afile = '/tmp/ksync_kernel_diff'
#		params = get_cvs_date_param1(to_cvs_time)
#		update_cvs_file(alsa, afile, params)
#	else:
	afile = get_cvs_root() + alsa;
	if not os.path.exists(afile):
		update_cvs_file(alsa)
		if not os.path.exists(afile):
			ncvsflag = 1
			os.system('touch %s' % afile)
	kfile = get_bk_root() + kernel;
	if not os.path.exists(kfile):
		update_bk_file(kfile)
		if not os.path.exists(kfile):
			nbkflag = 1
			os.system('touch %s' % kfile)
	if lsyncflag > 0:
		cmd = 'diff -uN %s %s' % (afile, kfile)
	else:
		cmd = 'diff -uN %s %s' % (kfile, afile)
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	lines = change_diff_lines(lines, "linux" + kernel)
	if len(lines) > 1:
		if lsyncflag > 0:
			collected_logs, dead = collect_cvs_logs(alsa, from_cvs_time, to_cvs_time)
			os.chdir(get_bk_root())
			print 'Updating file %s' % kfile
			if not nbkflag and not dead:
				if os.system('bk edit %s' % kfile):
					sys.exit(1)
			if nbkflag:
				if os.system('cp -avf %s %s' % (afile, kfile)):
					sys.exit(1)
				if os.system('bk new %s' % kfile):
					sys.exit(1)
				nbkflag = 0
			elif dead:
				if os.system('bk rm %s' % kfile):
					sys.exit(1)
			else:
				if os.system('cp -avf %s %s' % (afile, kfile)):
					sys.exit(1)
				if os.system('bk ci -y"ksync" %s' % kfile):
					sys.exit(1)
			tmpfile = '/tmp/ksyncABCD1234'
			fp = open(tmpfile, 'w+')
			if alsa == '/include/version.h':
				collected_logs = 'Updated date/time/version from the ALSA CVS tree'
			fp.write(collected_logs)
			fp.close()
			if os.system('bk comments -Y%s %s' % (tmpfile, kfile)):
				write_to_patch_update_file(lines)
				FATAL_LSYNC_ERROR = 1
			os.remove(tmpfile)
		else:
			print_file(ofile, lines)
	if ncvsflag:
		os.remove(afile)
	if nbkflag:
		os.remove(kfile)

def do_kernel_alsa_diff(kernel, alsa):
	afile = get_cvs_root() + alsa;
	kfile = get_bk_root() + kernel;
	if not os.path.exists(afile):
		update_cvs_file(alsa)
	if not os.path.exists(kfile):
		update_bk_file(kfile)
	cmd = 'diff -uN %s %s' % (afile, kfile)
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	lines = change_diff_lines(lines, alsa)
	print_file(sys.stdout, lines)

def show_last_changeset():
	os.chdir(get_bk_root())
	cmd = 'bk prs -r+ ChangeSet'
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
        print '# This is an automatically generated patch'
        print '# Project: Advanced Linux Sound Architecture (ALSA)'
        print '# Tool: ksync (path in CVS repository: alsa-kernel/scripts/ksync)'
        print '# Generated: %s' % time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(time.time()))
        print '# Source: ALSA CVS (cvs.alsa-project.org:/cvsroot/alsa)'
        print '# Kernel ChangeSet:'
	print_file_comment(sys.stdout, lines, '  ')
	print '#'

def diffall(reverse, from_cvs_time = 0, to_cvs_time = 0):
	lsyncflag = 0
	if from_cvs_time or to_cvs_time:
		lsyncflag = 1
	cdirs, cfiles = get_cvs_files(get_cvs_root(), '/')
	bdirs, bfiles = get_bk_files(get_bk_root(), '/')
	print 'BKROOT = %s' % BKROOT
	show_last_changeset()
	for d in ALSA_FORCE_DIR:
		if len(d) == 0:
			continue
		if cdirs.count(d) == 0:
			cdirs.append(d)
			if d[-1] != '/':
				d = d + '/'
			if d != '/':
				cdirs1, cfiles1 = get_cvs_files(get_cvs_root(), d)
				cdirs = cdirs + cdirs1
				cfiles = cfiles + cfiles1
	for d in KERNEL_FORCE_DIR:
		if len(d) == 0:
			continue
		if bdirs.count(d) == 0:
			bdirs.append(d)
			if d[-1] != '/':
				d = d + '/'
			if d != '/':
				bdirs1, bfiles1 = get_bk_files(get_bk_root(), d)
				bdirs = bdirs + bdirs1
				bfiles = bfiles + bfiles1
	cfiles = cfiles + ALSA_FORCE
	bfiles = bfiles + KERNEL_FORCE
	for f in ALSA_EXCLUDE:
		cfiles.remove(f)
	for f in KERNEL_EXCLUDE:
		bfiles.remove(f)
	first = 1
	for d in cdirs:
		dm = d
		d = remap_alsa(dm)
		if not bdirs.count(d):
			if first:
				print '# Added directories to Kernel Tree:'
				first = 0
			print '#   %s (%s)' % (dm, d)
	first = 1
	for d in bdirs:
		dm = d
		d = remap_kernel(dm)
		if not cdirs.count(d):
			if first:
				print '# Removed directories from Kernel Tree:'
				first = 0
			print '#   %s (%s)' % (dm, d)
	first = 1
	for f in cfiles:
		df = f
		f = remap_alsa_file(df)
		if not bfiles.count(f):
			if first:
				print '# Added files to Kernel Tree:'
				first = 0
			print '#   %s (%s)' % (df, f)
	first = 1
	for f in bfiles:
		df = f
		f = remap_kernel_file(df)
		if not cfiles.count(f):
			if first:
				print '# Removed files from Kernel Tree:'
				first = 0
			print '#   %s (%s)' % (df, f)
	print '\n'
	for f in cfiles:
		df = f
		f = remap_alsa_file(df)
		if not reverse:
			do_alsa_kernel_diff(df, f, from_cvs_time, to_cvs_time)
		else:
			do_kernel_alsa_diff(f, df)
		if bfiles.count(f):
			bfiles.remove(f)
	for f in bfiles:
		df = f
		f = remap_kernel_file(df)
		if not reverse:
			do_alsa_kernel_diff(f, df, from_cvs_time, to_cvs_time)
		else:
			do_kernel_alsa_diff(df, f)
		if cfiles.count(f):
			cfiles.remove(f)
	update_cvs_file('include/version.h')
	modify_version_file()
	if not reverse:
		do_alsa_kernel_diff('/include/version.h', '/include/sound/version.h', from_cvs_time, to_cvs_time)
	else:
		do_kernel_alsa_diff('/include/sound/version.h', '/include/version.h')
	os.remove(get_cvs_root() + '/include/version.h')
	update_cvs_file('include/version.h')

def do_bktree_diff(oldroot, newroot, file):
	ofile = get_bk_root(oldroot) + file;
	nfile = get_bk_root(newroot) + file;
	if not os.path.exists(ofile):
		update_bk_file(ofile, root=oldroot)
	if not os.path.exists(nfile):
		update_bk_file(nfile, root=newroot)
	cmd = 'diff -uN %s %s' % (ofile, nfile)
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	lines = change_diff_lines(lines, file)
	print_file(sys.stdout, lines)

def bkdiffall(reverse):
	lsyncflag = 0
	bdirs, bfiles = get_bk_files(get_bk_root(BKROOT), '/')
	cdirs, cfiles = get_bk_files(get_bk_root(BKROOT1), '/')
	print 'BKROOT = %s' % BKROOT
	print 'BKROOT1 = %s' % BKROOT1
	for d in KERNEL_FORCE_DIR:
		if len(d) == 0:
			continue
		if bdirs.count(d) == 0:
			bdirs.append(d)
			if d[-1] != '/':
				d = d + '/'
			if d != '/':
				bdirs1, bfiles1 = get_bk_files(get_bk_root(BKROOT), d)
				bdirs = bdirs + bdirs1
				bfiles = bfiles + bfiles1
	for d in KERNEL_FORCE_DIR:
		if len(d) == 0:
			continue
		if cdirs.count(d) == 0:
			cdirs.append(d)
			if d[-1] != '/':
				d = d + '/'
			if d != '/':
				bdirs1, bfiles1 = get_bk_files(get_bk_root(BKROOT1), d)
				cdirs = cdirs + bdirs1
				cfiles = cfiles + bfiles1
	bfiles = bfiles + KERNEL_FORCE
	cfiles = cfiles + KERNEL_FORCE
	for f in KERNEL_EXCLUDE:
		bfiles.remove(f)
	for f in KERNEL_EXCLUDE:
		cfiles.remove(f)
	first = 1
	for d in cdirs:
		if not bdirs.count(d):
			if first:
				print '# Removed directories to Kernel Tree:'
				first = 0
			print '#   %s' % d
	first = 1
	for d in bdirs:
		if not cdirs.count(d):
			if first:
				print '# Added directories from Kernel Tree:'
				first = 0
			print '#   %s' % d
	first = 1
	for f in cfiles:
		if not bfiles.count(f):
			if first:
				print '# Removed files to Kernel Tree:'
				first = 0
			print '#   %s' % f
	first = 1
	for f in bfiles:
		if not cfiles.count(f):
			if first:
				print '# Added files from Kernel Tree:'
				first = 0
			print '#   %s' % f
	print '\n'
	for f in cfiles:
		if not reverse:
			do_bktree_diff(BKROOT1, BKROOT, f)
		else:
			do_bktree_diff(BKROOT, BKROOT1, f)
		if bfiles.count(f):
			bfiles.remove(f)
	for f in bfiles:
		if not reverse:
			do_bktree_diff(BKROOT1, BKROOT, f)
		else:
			do_bktree_diff(BKROOT, BKROOT1, f)
		if cfiles.count(f):
			cfiles.remove(f)

def get_last_lsync_time():
	os.chdir(get_bk_root())
	os.system('bk get -q include/sound/version.h')
	fp = open('include/sound/version.h', 'r')
	lines = fp.readlines()
	fp.close()
	if lines[2][0:24] != '#define CONFIG_SND_DATE ':
		print 'Error, unexpected include/sound/version.h in BK directory'
		print lines[2]
		sys.exit(1)
	str = lines[2][27:-7]
	t = time.mktime(time.strptime(str, '%a %b %d %H:%M:%S %Y'))
	return t + time.timezone

def get_last_changeset():
	os.chdir(get_bk_root())
	os.system('bk get -q Makefile')
	fp = open('Makefile', 'r')
	lines = fp.readlines()
	fp.close()
	kernel1 = '?'
	kernel2 = '?'
	kernel3 = '?'
	kernel4 = ''
	for line in lines:
		if line[0:10] == 'VERSION = ':
			kernel1 = line[10:-1]
		elif line[0:13] == 'PATCHLEVEL = ':
			kernel2 = line[13:-1]
		elif line[0:11] == 'SUBLEVEL = ':
			kernel3 = line[11:-1]
		elif line[0:15] == 'EXTRAVERSION = ':
			kernel4 = line[15:-1]
	kernel = kernel1 + '.' + kernel2 + '.' + kernel3 + kernel4
	cmd = 'bk prs -r+ ChangeSet'
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	cs = '0.0'
	for line in lines:
		line = line[:-1]
		words = string.split(line, ' ')
		if words[0] == 'D':
			cs = words[1]
		elif words[0] == 'S':
			kernel = words[1][1:]
	return cs, kernel

def filename(ver = 1):
	cs, kernel = get_last_changeset()
        print 'alsa-%s-%s-linux-%s-cs%s.patch' % (time.strftime("%Y-%m-%d", time.gmtime(time.time())), ver, kernel, cs)	

def kdiffs(cs = '1.403.57.48'):
	os.chdir(get_bk_root())
	cmd = 'sh -c \'bk rset -hr%s.. | grep -E "^(sound/|include/sound/)" | grep -E -v "^sound/oss/" | bk gnupatch -du\'' % cs
	fp = os.popen(cmd)
	lines = fp.readlines()
	fp.close()
	print_file(sys.stdout, lines)

def parse_time(str):
	args = string.split(str, '/')
	if len(args) < 3:
		args.insert(0, time.strftime("%Y", time.gmtime(time.time())))
	if len(args) != 3:
		print 'Bad date syntax'
		sys.exit(0)
	str = args[0] + ' ' + args[1] + ' ' + args[2]
	t = time.mktime(time.strptime(str, '%Y %m %d'))
	return t - time.timezone

def cvsps(changeset):
	os.chdir(get_cvs_root())
	lines = my_popen("cvsps -u | grep PatchSet | tail -n 1")
	dummy, endchangeset = string.split(lines[0][0:-2], ' ')
	changeset = int(changeset)
	endchangeset = int(endchangeset)
	print 'Generating CVS changesets %s..%s' % (changeset, endchangeset)
	try:
		os.mkdir(get_cvs_root() + '/scripts/changesets')
	except OSError, msg:
		a = 0
	while changeset <= endchangeset:
		print 'ChangeSet %s' % changeset
		fp = open(get_cvs_root() + '/scripts/changesets/%s.patch' % changeset, 'w+')
		lines = my_popen('cvsps -g -s %s' % changeset)
		idx = 0
		bkfile = ''
		for i in lines:
			if i[0:6] == 'Index:':
				bkfile = remap_alsa_file(i[string.find(i, '/'):-1])
				fp.write(change_diff_line(i, bkfile))
				idx = 1
			elif i[0:13] == '--- /dev/null':
				prevline = i
				idx = 100
			elif idx > 0 and idx < 4:
				fp.write(change_diff_line(i, bkfile))
				idx += 1
			elif idx == 100:
				str = string.expandtabs(i[string.find(i, '/'):-1])
				str = str[0:string.find(str, ' ')]
				bkfile = remap_alsa_file(str)
				fp.write('Index: %s\n' % bkfile)
				fp.write('diff -u %s.old %s\n' % (bkfile, bkfile))
				fp.write(change_diff_line(prevline, bkfile))
				fp.write(change_diff_line(i, bkfile))
				idx = 3
			else:
				fp.write(i)
		fp.close()
		changeset += 1				

def cvsps_merge_members1(file):
	global COMMENT_MAP
	for i in COMMENT_MAP:
		if re.compile("^" + i[0]).search(file):
			return i[1]
	return ''

def cvsps_merge_members(members):
	changes = []
	result = []
	for member in members:
		file, other = string.split(member, ':')
		file = "/" + file
		while file != '':
			result1 = cvsps_merge_members1(file)
			if result1 == 'ERROR':
				print 'Cannot identify file "%s"' % file
				sys.exit(1)
			if result1 != '':
				file = ''
				changes.append(result1)
			else:
				i = string.rfind(file, '/')
				if i < 0:
					file = ''
				else:
					file = file[0:i]
	i = 0
	while i < len(changes):
		j = 0
		while j < len(changes):
			if i != j and changes[i] == changes[j]:
				del changes[j]
				i = -1
				break
			j += 1
		i += 1
	xresult = ''
	for i in changes:
		if len(i) + len(xresult) > 70:
			result.append(xresult)
			xresult = ''
		if xresult == '':
			xresult = i
		else :
			xresult = xresult + ',' + i
	if xresult != '':
		result.append(xresult)
	return result

def cvsps_merge1(f,t=0):
	if not t:
		print 'Trying merge patch ' + f
	ff = get_cvs_root() + '/scripts/changesets/' + f
	fp = open(ff, 'r')
	diffs = fp.readlines()
	fp.close()
	files = []
	for line in diffs:
		if line[0:3] == '+++':
			xx1 = string.split(line, ' ')
			xx1 = string.split(xx1[1], '\t')
			xx2 = string.split(xx1[0], '/')
			a = 0
			file = ''
			for i in xx2:
				a += 1
				if a < 2:
					continue
				if file != '':
					file = file + '/' + i
				else:
					file = i
			files.append(file)
	log = []
	members = []
	ok = 0
	idx = 0
	author = ''
	date = ''
	for line in diffs:
		if line[:-1] == 'Log:':
			ok = 1
		elif line[:-1] == 'Members: ':
			ok = 2
		elif line[:6] == 'Index:':
			ok = 0
		elif line[:5] == 'Date:':
			date = line[6:-1]
		elif line[:7] == 'Author:':
			author = CVS_USER[line[8:-1]]
		elif line[:8] == 'Members:':
			ok = 2
		elif ok == 1:
			log.append(line)
		elif ok == 2:
			if line != '\n':
				members.append(line[1:])
	while log[len(log)-1] == '\n':
		del log[len(log)-1]
	lines = 'ALSA CVS update\n'
	if author != '':
		nlines = 'ALSA CVS update' + ' - ' + author + '\n'
	else:
		nlines = lines
	if date != '':
		lines = lines + 'D:' + date + '\n'
	changes = cvsps_merge_members(members)
	for i in changes:
		lines = lines + 'C:' + i + '\n'
	if author != '':
		lines = lines + 'A:' + author + '\n'
	for i in changes:
		nlines = nlines + i + '\n'
	for i in members:
		lines = lines + 'F:' + i
	for i in log:
		lines = lines + 'L:' + i
		nlines = nlines + i
	if t:
		print lines
		return
	nlines = string.replace(nlines, '"', "'")
	lines = string.replace(lines, '"', "'")
	while os.system('bk import -tpatch -C -y"' + nlines + '" ' + ff + ' .'):
		print 'BK patch import of %s error, xterm? (any key to continue or Control-C to abort)' % ff
		sys.stdin.readline()
		os.system("xterm")
	if os.system('bk commit -y"' + nlines + '"'):
		print 'BK commit failed'
		sys.exit(1)
	for file in files:
		os.system('bk get %s' % file)
		if not os.path.exists(file):
			continue
		if os.system('bk comment -y"' + lines + '" %s' % file):
			print 'BK comment change failed'
			sys.exit(1)
	#sys.exit(1)
	
def cvsps_merge():
	os.chdir(get_bk_root())
	l = dircache.listdir(get_cvs_root() + '/scripts/changesets')
	for f in l:
		if string.find(f, '.') > 0:
			file, suff = string.split(f, '.')
			if suff == 'patch':
				cvsps_merge1(f, t=1)
	print 'Is this ok? Press Ctrl-C to abort...'
	sys.stdin.readline()
	get_cvs_files(get_cvs_root(), '/')
	update_cvs_file('include/version.h')
	modify_version_file()
	do_alsa_kernel_diff('/include/version.h', '/include/sound/version.h', 0, 0)
	os.remove(get_cvs_root() + '/include/version.h')
	update_cvs_file('include/version.h')
	os.chdir(get_bk_root())
	print 'Is this ok? Press Ctrl-C to abort...'
	sys.stdin.readline()
	for f in l:
		if string.find(f, '.') > 0:
			file, suff = string.split(f, '.')
			if suff == 'patch':
				cvsps_merge1(f, t=0)
	del l


def main():
	global CVSROOT, BKROOT, BKROOT1, PATCH_UPDATE, ALSA_MAP, KERNEL_MAP
	try:
		opts, args = getopt.getopt(sys.argv[1:], 'hB:C:',
					   ['cvsroot=', 'bkroot=', 'help']);
	except getopt.error, msg:
		usage(1, msg)

	cwd = os.getcwd()

	# parse the options
	for opt, arg in opts:
		if opt in ('-h', '--help'):
			usage(0)
		elif opt == '--cvsroot':
			CVSROOT = arg
		elif opt == '--bkroot':
			BKROOT = arg

	for k in ALSA_MAP.keys():
		KERNEL_MAP[ALSA_MAP[k]] = k

	if not args:
		print 'Command not specified'
		sys.exit(1)
	if args[0] == 'diffall' or args[0] == 'linux-sound' or args[0] == 'work':
		reverse=0
		if args[0] == 'linux-sound':
			BKROOT = '~/bk/linux-sound/linux-sound'
		elif args[0] == 'work':
			BKROOT = '~/bk/linux-sound/work'
		if len(args) > 1:
			if args[1] == '-r':
				reverse=1
			else:
				print 'Ignoring extra arguments %s' % args[1:]
		diffall(reverse)
	elif args[0] == 'bkdiff':
		reverse=0
		BKROOT = '~/bk/linux-sound/linux-sound'
		BKROOT1 = '~/bk/linux-sound/work'
		if len(args) > 1:
			if args[1] == '-r':
				reverse=1
			else:
				print 'Ignoring extra arguments %s' % args[1:]
		bkdiffall(reverse)
	elif args[0] == 'cvsps':
		reverse=0
		if len(args) < 2:
			print 'Please, give a starting changeset'
			return
		cvsps(args[1])
	elif args[0] == 'cvsps-merge':
		BKROOT = '~/bk/linux-sound/work'
		cvsps_merge()
	elif args[0] == 'lsync':
		to_cvs_time = 0
		patch = ''
		if len(args) > 1:
			if args[1] == "last":
				BKROOT = '~/bk/linux-sound/linux-sound'
				xtime = get_last_lsync_time()
				print time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(xtime))
				return
			to_cvs_time = parse_time(args[1])
		if len(args) > 2:
			patch = os.path.abspath(os.path.expanduser(args[2]))
		else:
			npatch = os.path.abspath('kchanges/%s' % time.strftime("%Y-%m-%d", time.gmtime(to_cvs_time)))
			if os.path.exists(npatch):
				print 'Using kernel patch %s' % npatch
				patch = npatch

		if to_cvs_time > 0:
			CVSROOT = '~/alsa.local'
			os.chdir(os.path.expanduser('~/'))
			os.system('rm -rf alsa.local')
			os.system('mkdir alsa.local')
			os.chdir(os.path.expanduser('~/alsa.local'))
			os.system('cvs -d/home/src/alsa co -P %s alsa-driver alsa-kernel' % get_cvs_date_param1(to_cvs_time))
			if os.path.exists(patch):
				os.chdir(os.path.expanduser('~/alsa.local/alsa-kernel'))
				os.system('patch -p2 < %s' % patch)
				print 'Is this ok? Press Ctrl-C to abort...'
				sys.stdin.readline()

		BKROOT = '~/bk/linux-sound/work'
		os.chdir(os.path.expanduser('~/bk/linux-sound'))
		os.system('rm -rf work')
		os.system('bk clone -l linux-sound work')

		if os.path.exists(os.path.expanduser(PATCH_UPDATE)):
			os.remove(os.path.expanduser(PATCH_UPDATE))

		from_cvs_time = get_last_lsync_time()
		if to_cvs_time > 0 and to_cvs_time <= from_cvs_time:
			print 'Time < last_lsync_time (%s)' % time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(from_cvs_time))
			sys.exit(1)
		diffall(0, from_cvs_time, to_cvs_time)
		if FATAL_LSYNC_ERROR > 0:
			print 'Error during merge, see %s file...' % PATCH_UPDATE
	elif args[0] == 'cvslocal':
		if len(args) <= 1:
			print 'Specify time, please'
			sys.exit(1)
		to_cvs_time = parse_time(args[1])
		CVSROOT = '~/alsa.local'
		os.chdir(os.path.expanduser('~/'))
		os.system('rm -rf alsa.local')
		os.system('mkdir alsa.local')
		os.chdir(os.path.expanduser('~/alsa.local'))
		os.system('cvs -d/home/src/alsa co -P %s alsa-driver alsa-kernel' % get_cvs_date_param1(to_cvs_time))
	elif args[0] == 'filename':
		ver = 1
		if len(args) > 1:
			ver = args[1]
		filename(ver)
	elif args[0] == 'kdiffs':
		if len(args) > 1:
			kdiffs(args[1])
		else:
			kdiffs()
	else:
		print 'Unknown command %s' % args[0]
		sys.exit(1)

if __name__ == '__main__':
	main()
	sys.exit(0)
