#! /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

	-G <path>
	--gitroot=<path>
		Set root of Linux kernel GIT 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'
GITROOT = '~/git/repos/linux-2.6'
PATCH_UPDATE = '~/alsa/alsa-kernel/scripts/kchanges/update'
GIT_COMMITTER_NAME = 'Jaroslav Kysela'
GIT_COMMITTER_EMAIL = 'perex@suse.cz'

# exclude some files or directories
ALSA_EXCLUDE = ['/include/version.h']
ALSA_EXCLUDE_DIR = ['/scripts', '/oss', '/kbuild']
KERNEL_EXCLUDE = ['/COPYING', '/CREDITS',
		  '/MAINTAINERS','/Makefile','/Kbuild',
		  '/README','/REPORTING-BUGS',
		  '/include/sound/version.h',
		  '/.gitignore']
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_DRIVER = [
		('/include/mpu401.h'	,'MPU401 UART'),
		('/drivers/mpu401'	,'MPU401 UART'),
	 	('/include/asound_fm.h' ,'Raw OPL FM'),
		('/include/opl3.h'	,'OPL3'),
		('/drivers/opl3'	,'OPL3'),
		('/drivers/opl4'	,'OPL4'),
		('/include/vx_core.h'	,'Digigram VX core'),
		('/drivers/vx'		,'Digigram VX core'),
		('/drivers'		,'Generic drivers'),
		('/include/uda1341.h'	,'UDA1341'),
		('/arm/Kconfig'		,'ARM'),
		('/arm/devdma.[ch]'	,'ARM DMA routines'),
		('/arm/aaci.[ch]'	,'ARM AACI PL041 driver'),
		('/arm/sa11xx-uda1341.c','SA11xx UDA1341 driver'),
		('/arm/pxa2xx.*'	,'ARM PXA2XX driver'),
		('/arm/s3c24xx-iis.[ch]','ARM S3C24XX IIS driver'),
		('/arm'			,'ERROR'),
		('/isa/Kconfig'		,'ISA'),
		('/include/ad1816a.h'	,'AD1816A driver'),
		('/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'),
		('/include/es1688.h'	,'ES1688 driver'),
		('/isa/es1688'		,'ES1688 driver'),
		('/include/gus.h'	,'GUS Library'),
		('/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/msnd'		,'MSND driver'),
		('/isa/opti9xx'		,'Opti9xx drivers'),
		('/include/yss225.h'	,'Wavefront drivers'),
		('/include/snd_wavefront.h', 'Wavefront 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'),
		('/include/ak4531_codec.h','AK4531 codec'),
		('/pci/ac97/ak4531_codec.c','AK4531 codec'),
		('/include/ac97_codec.h','AC97 Codec'),
		('/pci/ac97'		,'AC97 Codec'),
		('/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.[ch]'	,'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/atiixp_modem.c'	,'ATIIXP-modem driver'),
		('/pci/intel8x0m.c'	,'Intel8x0-modem driver'),
		('/pci/via82xx_modem.c'	,'VIA82xx-modem driver'),
		('/pci/Kconfig'		,'PCI drivers'),
		('/pci/Makefile'	,'PCI drivers'),
		('/pci/ca0106/.*'	,'CA0106 driver'),
		('/pci/hda/hda_codec.[ch]', 'HDA Codec driver'),
		('/pci/hda/hda_patch.[ch]', 'HDA Codec driver'),
		('/pci/hda/patch_.*'	,'HDA Codec driver'),
		('/pci/hda/hda_intel.c'	,'HDA Intel driver'),
		('/pci/hda/.*'		,'HDA generic driver'),
		('/pci/ad1889.*'	,'AD1889 driver'),
		('/include/hdspm.h'	,'HDSPM driver'),
		('/pci'			,'ERROR'),
		('/ppc/Makefile'	,'PPC'),
		('/ppc/Kconfig'		,'PPC'),
		('/ppc/awacs.(c|h)'	,'PPC AWACS driver'),
		('/ppc/burgundy.(c|h)'	,'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/beep.c'		,'PPC Beep'),
		('/ppc/toonie.c'	,'PPC Toonie'),
		('/ppc'			,'ERROR'),
		('/i2c/l3'		,'L3 drivers'),
		('/include/tea575x-tuner.h','TEA575x tuner'),
		('/i2c/other/tea575x-tuner.c','TEA575x tuner'),
		('/include/ak4114.h'	,'AK4114 receiver'),
		('/i2c/other/ak4114.c'  ,'AK4114 receiver'),
		('/include/ak4117.h'	,'AK4117 receiver'),
		('/i2c/other/ak4117.c'  ,'AK4117 receiver'),
		('/include/ak4xxx-adda.h','AK4XXX AD/DA converters'),
		('/i2c/other/ak4xxx-adda.c','AK4XXX AD/DA converters'),
		('/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/tea6330t.c'	,'I2C tea6330t'),
		('/i2c'			,'ERROR'),
		('/parisc/harmony.[ch]'	,'PARISC Harmony driver'),
		('/parisc/Kconfig'	,'PARISC'),
		('/parisc'		,'ERROR'),
		('/sparc/amd7930.c'	,'SPARC AMD7930 driver'),
		('/sparc/cs4231.c'	,'SPARC cs4231 driver'),
		('/sparc/dbri.c'	,'SPARC DBRI driver'),
		('/sparc/Kconfig'	,'SPARC'),
		('/sparc'		,'ERROR'),
		('/mips/au1x00.[ch]'	,'MIPS AU1x00 driver'),
		('/mips/Kconfig'	,'MIPS'),
		('/mips'		,'ERROR'),
		('/include/emux_synth.h','Common EMU synth'),
		('/synth/emux'		,'Common EMU synth'),
		('/include/soundmem.h'  ,'Synth'),
		('/synth/Makefile'	,'Synth'),
		('/synth/util_mem.c'	,'Synth'),
		('/pcmcia/vx'		,'Digigram VX Pocket driver'),
		('/pcmcia/pdaudiocf'	,'PDAudioCF driver'),
		('/pcmcia/Kconfig'	,'PCMCIA Kconfig'),
		('/pcmcia/Makefile'	,'PCMCIA'),
		('/pcmcia'		,'ERROR'),
		('/usb/usx2y/.*'	,'USB USX2Y'),
		('/usb/Kconfig'		,'USB'),
		('/usb/Makefile'	,'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'),
		('/include/mixer_oss.h'	,'ALSA<-OSS emulation'),
		('/core/oss'		,'ALSA<-OSS emulation'),
		('/core/seq/oss'	,'ALSA<-OSS sequencer'),
		('/include/ainstr.*'	,'Instrument layer'),
		('/core/seq/instr'	,'Instrument layer'),
		('/include/seq_kernel.h','ALSA sequencer'),
		('/include/seq_midi_emul.h','ALSA sequencer'),
		('/include/seq_midi_event.h','ALSA sequencer'),
		('/include/asequencer.h','ALSA sequencer'),
		('/include/seq_virmidi.h','Virtual Midi'),
		('/core/seq'		,'ALSA sequencer'),
		('/core/Kconfig'	,'ALSA Core'),
		('/core/memalloc.*'	,'Memalloc module'),
		('/core/sgbuf.*'	,'Memalloc module'),
		('/core/rtctimer.*'	,'RTC timer driver'),
		('/core/hpetimer.*'	,'HPE 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/adriver.h'	,'ALSA Core'),
		('/include/asound.h'	,'ALSA Core'),
		('/include/asoundef.h'	,'ALSA Core'),
		('/include/driver.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'),
		('/include/config.h.in' ,'ALSA Core'),
		('/include/firmware_compat.h','ALSA Core'),
		('/include/compat_22.h'	,'ALSA Core'),
		('/include/linux/pci_ids.h'	,'ALSA Core'),
		('/include/autoconf.*'	,'ALSA Core'),
		('/core/info.*'		,'ALSA Core'),
		('/core/control.*'	,'ALSA Core'),
		('/core/init.*'		,'ALSA Core'),
		('/core/sound.*'	,'ALSA Core'),
		('/core/device.*'	,'ALSA Core'),
		('/core/memory.*'	,'ALSA Core'),
		('/core/misc.*'		,'ALSA Core'),
		('/core/wrappers.*'	,'ALSA Core'),
		('/core/pci_compat_22.c','ALSA Core'),
		('/core/Makefile'	,'ALSA Core'),
		('/core'		,'ERROR'),
		('/Documentation/.*'	,'Documentation'),
		('/doc/.*'		,'Documentation'),
		('/include'		,'ERROR'),
		('/sound_core.c'	,'OSS device core'),
		('/scripts'		,'IGNORE'),
		('/Kconfig'		,'Sound Core'),
		('/Makefile'		,'Sound Core'),
		('/INSTALL'		,'Sound Core'),
		('/Rules.make'		,'Sound Core'),
		('/configure.in'	,'Sound Core'),
		('/cvscompile'		,'cvscompile script'),
		('/snddevices.in'	,'snddevices script'),
		('/'			,'ERROR')
	       ]
COMMENT_MAP_LIB = [
		('/include/sound/.*'	,'Kernel Headers'),
		('/include/asound.*'	,'Core'),
		('/include/local.*'	,'Core'),
		('/include/global.*'	,'Core'),
		('/include/conv.h'	,'Core'),
		('/include/error.h'	,'Core'),
		('/include/conf.h'	,'Config API'),
		('/include/control.h'	,'Control API'),
		('/include/control_external.h','External Control Plugin SDK'),
		('/include/hwdep.h'	,'HWDEP API'),
		('/include/(input|output).h' ,'I/O API'),
		('/include/instr.h'	,'Instrument API'),
		('/include/mixer.h'	,'Mixer API'),
		('/include/mixer_abst.h','Mixer Abstraction API'),
		('/include/mixer_ordinary.h' ,'Mixer Ordinary API'),
		('/include/rawmidi.h'	,'Rawmidi API'),
		('/include/timer.h'	,'Timer API'),
		('/include/seq.*'	,'Sequencer API'),
		('/include/pcm.h'	,'PCM API'),
		('/include/pcm_old.h'	,'PCM API'),
		('/include/pcm_ordinary.h' ,'PCM Ordinary API'),
		('/include/pcm_plugin.h' ,'PCM Plugin API'),
		('/include/pcm_external.h' ,'External PCM Plugin SDK'),
		('/include/pcm_ioplug.h' ,'External PCM I/O Plugin SDK'),
		('/include/pcm_extplug.h','External PCM Filter Plugin SDK'),
		('/include/alsa-symbols.h','Core'),
		('/src/control/.*'	,'Control API'),
		('/src/mixer/.*'	,'Mixer API'),
		('/include/pcm.h'	,'PCM API'),
		('/src/alisp/.*'	,'ALSA Lisp'),
		('/src/pcm/.*'		,'PCM API'),
		('/src/rawmidi/.*'	,'RawMidi API'),
		('/src/timer/.*'	,'Timer API'),
		('/src/seq/.*'		,'Sequencer API'),
		('/src/instr/.*'	,'Instrument API'),
		('/src/hwdep/.*'	,'HWDEP API'),
		('/src/ordinary_mixer/.*' ,'Mixer Ordinary API'),
		('/src/ordinary_pcm/.*' ,'PCM Ordinary API'),
		('/src/input.*'		,'I/O subsystem'),
		('/src/output.*'	,'I/O subsystem'),
		('/src/conf.*'		,'Configuration'),
		('/src/async.*'		,'Async helpers'),
		('/src/error.*'		,'Error handler'),
		('/src/socket.*'	,'Socket helpers'),
		('/src/userfile.*'	,'Filename helpers'),
		('/src/dlmisc.*'	,'Dynamic Loader helpers'),
		('/src/names.*'		,'Device Name API'),
		('/src/shmarea.*'	,'SHM helpers'),
		('/src/Versions'	,'Core'),
		('/modules/mixer/simple/.*', 'Simple Abstraction Mixer Modules'),
		('/doc/.*'		,'Documentation'),
		('/utils/.*'		,'Utils'),
		('/test/.*'		,'Test/Example code'),
		('/configure.in'	,'Core'),
		('/INSTALL'		,'Documentation'),
		('/NOTES'		,'Documentation'),
		('/'			,'ERROR')
	       ]
COMMENT_MAP_UTILS = [
		('/amixer/.*'		,'amixer'),
		('/alsamixer/.*'	,'alsamixer'),
		('/aplay/.*'		,'aplay/arecord'),
		('/alsaconf/.*'		,'alsaconf'),
		('/speaker-test/.*'	,'Speaker Test'),
		('/alsactl/.*'		,'ALSA Control (alsactl)'),
		('/amidi/.*'		,'ALSA RawMidi Utility (amidi)'),
		('/seq/aplaymidi/.*'	,'aplaymidi/arecordmidi'),
		('/seq/aseqdump/.*'	,'aseqdump'),
		('/seq/aconnect/.*'	,'aconnect'),
		('/seq/aseqnet/.*'	,'aseqnet'),
		('/iecset/.*'		,'iecset'),
		('/configure.in'	,'Core'),
		('/Makefile.am'		,'Core'),
		('/Makefile.am'		,'Core'),
		('/INSTALL'		,'Core'),
		('/include/gettext.h'	,'IGNORE'),
		('/po/.*'		,'IGNORE'),
		('/m4/.*'		,'IGNORE'),
		('/cvscompile'		,'cvscompile'),
		('/ChangeLog'		,'IGNORE'),
		('/'			,'ERROR')
	       ]
COMMENT_MAP_TOOLS = [
		('/envy24control/.*'	,'Envy24 Control'),
		('/rmedigicontrol/.*'	,'RME Digi Control'),
		('/pcxhrloader/.*'	,'Digigram PCXHR Loader'),
		('/hdsploader/.*'	,'RME HDSP Loader'),
		('/echomixer/.*'	,'Digigram Echo Mixer'),
		('/as10k1/.*'		,'as10k1 (EMU10K1+ DSP Assembler)'),
		('/ld10k1/.*'		,'ld10k1 (EMU10K1+ DSP Code Loader)'),
		('/qlo10k1/.*'		,'qlo10k1'),
		('/hdspconf/.*'		,'hdspconf'),
		('/hdspmixer/.*'	,'hdspmixer'),
		('/Makefile'		,'Core'),
		('/'			,'ERROR')
	       ]
COMMENT_MAP_FIRMWARE = [
		('/vxloader'		,'Digigram Vx Firmware'),
		('/mixartloader'	,'Digigram MixArt Firmware'),
		('/pcxhrloader'		,'Digigram PCXHR Firmware'),
		('/echoaudio'		,'Digigram Echo Audio Firmware'),
		('/hdsploader'		,'RME HDSP Firmware'),
		('/usx2yloader'		,'Tescam USX2Y Firmware'),
		('/configure.in'	,'Core'),
		('/Makefile.am'		,'Core'),
		('/README'		,'Core'),
		('/'			,'ERROR')
	       ]
COMMENT_MAP_OSS = [
		('/alsa/pcm.c'		,'PCM Emulation'),
		('/alsa/mixer.c'	,'Mixer Emulation'),
		('/alsa/stdioemu.c'	,'Core'),
		('/alsa/alsa-oss.c'	,'Core'),
		('/alsa/Makefile.am'	,'Core'),
		('/alsa/aoss.1'		,'Manual Page'),
		('/alsa/aoss.in'	,'aoss script'),
		('/alsa/aoss.old.in'	,'aoss script'),
		('/configure.in'	,'Core'),
		('/'			,'ERROR')
	       ]
COMMENT_MAP = {
	'alsa-driver': COMMENT_MAP_DRIVER,
	'alsa-lib': COMMENT_MAP_LIB,
	'alsa-utils': COMMENT_MAP_UTILS,
	'alsa-tools': COMMENT_MAP_TOOLS,
	'alsa-firmware': COMMENT_MAP_FIRMWARE,
	'alsa-oss': COMMENT_MAP_OSS
}

# Global variables
LAST_CVS_TIME = 0
FATAL_LSYNC_ERROR = 0
GERRORS = 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>',
	    'jcdutton':'James Courtier-Dutton <James@superbug.co.uk>'}

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_cvs_root_top():
	return os.path.expanduser(CVSROOT)

def get_git_root(root=''):
	if root == '':
		root = GITROOT
	return os.path.expanduser(root)

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

def my_popen2(cmd):
	fp = os.popen('{ ' + cmd + '; } 2>&1', 'r')
	lines = fp.readlines()
	sts = fp.close()
	if sts == None:
		sts = 0
	return sts, 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 extract_name(full):
	x, y = string.split(full, '<')
	return x[:-1]

def extract_email(full):
	x, y = string.split(full, '<')
	return y[:-1]

def get_cvs_version():
	filename = get_cvs_root() + '/include/version.h'
	fp = open(filename)
	lines = fp.readlines()
	fp.close()
	version = "unknown"
	for line in lines:
		str = '#define CONFIG_SND_VERSION'
		if line[0:len(str)] == str:
			d = string.split(line, '"')
			version = d[1]
			return version
	return version

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_git_file(file, root=''):
	# empty
	root =''

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_git_files(base, dir):
	dlist = []
	flist = []

	# merge files and directories
	dir1 = base + dir
	l = dircache.listdir(dir1)
	for f in l:
		if f[:-1] == '~' or f == '.git':
			continue
		if os.path.isdir(dir1 + f):
			if not KERNEL_EXCLUDE_DIR.count(dir + f):
				dlist1, flist1 = get_git_files(base, dir + f + '/')
				dlist.append(dir + f)
				dlist = dlist + dlist1
				flist = flist + flist1
		else:
			flist.append(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):
        if dir == '/MAINTAINERS':
                return '/MAINTAINERS'
	return remap_engine(dir, ALSA_MAP);

def remap_kernel(dir):
        if dir == '/MAINTAINERS':
                return '/MAINTAINERS'
	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
	ngitflag = 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_git_root() + kernel;
	if not os.path.exists(kfile):
		update_git_file(kfile)
		if not os.path.exists(kfile):
			ngitflag = 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_git_root())
			print 'Updating file %s' % kfile
			if not ngitflag and not dead:
				if os.system('git edit %s' % kfile):
					sys.exit(1)
			if ngitflag:
				if os.system('cp -avf %s %s' % (afile, kfile)):
					sys.exit(1)
				if os.system('cg-add %s' % kfile):
					sys.exit(1)
				ngitflag = 0
			elif dead:
				if os.system('cg-rm %s' % kfile):
					sys.exit(1)
			else:
				if os.system('cp -avf %s %s' % (afile, 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()
			os.remove(tmpfile)
		else:
			print_file(ofile, lines)
	if ncvsflag:
		os.remove(afile)
	if ngitflag:
		os.remove(kfile)

def do_kernel_alsa_diff(kernel, alsa):
	afile = get_cvs_root() + alsa;
	kfile = get_git_root() + kernel;
	if not os.path.exists(afile):
		update_cvs_file(alsa)
	if not os.path.exists(kfile):
		update_git_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 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_git_files(get_git_root(), '/')
	print 'GITROOT = %s' % GITROOT
	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_git_files(get_git_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 get_last_lsync_time():
	os.chdir(get_git_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 git 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_git_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 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 -Z 3 -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 -Z 3 -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, module):
	global COMMENT_MAP
	map = COMMENT_MAP[module]
	if module == 'alsa-driver' and file[:6] == '/acore':
		file = '/' + file[2:]
	for i in map:
		if re.compile("^" + i[0]).search(file):
			if i[1] == 'ERROR':
				break
			return i[1]
	if file[-11:] == '/.cvsignore':
		return 'IGNORE'
	if file[-12:] == '/Makefile.am':
		return file
	if file[-9:] == '/Makefile':
		return file
	return 'ERROR'

def cvsps_merge_members(members, module='alsa-driver', nice=False):
	global GERRORS

	changes = []
	result = []
	for member in members:
		file, other = string.split(member, ':')
		file = "/" + file
		while file != '':
			result1 = cvsps_merge_members1(file, module)
			if result1 == 'ERROR':
				GERRORS += 1
				str = 'Cannot identify file "%s" from module %s' % (file, module)
				fp = open("/tmp/cvsps-changes.txt", "a+")
				fp.write(str + "\n")
				fp.close()
				print str, ' {see /tmp/cvsps-changes.txt file}'
				result1 = ''
			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 = ''
	maxc = 70
	if nice:
	        maxc = 61
	for i in changes:
		if len(i) + len(xresult) > maxc:
			result.append(xresult)
			xresult = ''
		if xresult == '':
			xresult = i
		else:
			xresult = xresult + ',' + i
	if xresult != '':
		result.append(xresult)
        if len(result) > 1 and nice:
                return []
        elif len(result) > 0:
              result[0] = "Modules: " + result[0]
              result.append('')
	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 = []
	pline = ''
	for line in diffs:
		if line[:14] == "+++ /dev/null\t":
			continue
		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)
		pline = line
	summary = ''
	signed = []
	log = []
	members = []
	ok = 0
	idx = 0
	author = ''
	date = ''
	level = 'Devel'
	xfrom = ''
	for line in diffs:
		if line[:-1] == 'Log:':
			ok = 1
		elif line[:-1] == 'Members: ':
			ok = 2
		elif line[:6] == 'Index:':
			ok = 0
		elif line[:3] == '---':
			ok = 0
		elif line[:5] == 'Date:':
			date = line[6:-1]
		elif line[:7] == 'Author:' and ok == 0:
			author = CVS_USER[line[8:-1]]
		elif line[:8] == 'Members:':
			ok = 2
		elif ok == 1:
			if line[:14] == "Signed-off-by:":
				signed.append(line)
			elif line[:14] == "Signed-Off-By:":
				signed.append(line)
			elif line[:9] == "Summary: ":
				summary = line[9:]
			elif line[:8] == "Summary:":
				summary = line[8:]
			elif line[:9] == "Subject: ":
				summary = line[9:]
			elif line[:8] == "Subject:":
				summary = line[8:]
			elif line[:13] == "Patch-level: ":
				level = line[13:-1]
			elif line[:13] == "Patch-Level: ":
				level = line[13:-1]
			elif line[:6] == "From: ":
				xfrom = line[6:-1]
			else:
				log.append(line)
		elif ok == 2:
			if line != '\n':
				members.append(line[1:])
	while len(log) > 1 and log[0] == '\n':
		del log[0]
	if level == 'Low':
		level = 'Devel'
	elif level == 'High':
		level = 'ASAP'
	if level == 'merged':
		level = 'Merged'
	while len(log) > 1 and log[len(log)-1] == '\n':
		del log[len(log)-1]
	if summary != '':
		lines = '[ALSA] ' + summary + '\n'
	else:
		lines = 'ALSA CVS update\n'
	nlines = lines
	if date != '':
		lines = lines + 'D:' + date + '\n'
	changes = cvsps_merge_members(members, nice=True)
	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
	signed_flag = 0
	if xfrom != '':
		real_author = xfrom
	else:
		real_author = author
	for i in signed:
		if not signed_flag:
			if xfrom == '':
				real_author = i[15:]
			nlines = nlines + '\n'
			signed_flag = 1
		lines = lines + i
		nlines = nlines + i
	if not signed_flag:
		nlines = nlines + '\n'
		signed_flag = 1
	lines = lines + 'Signed-off-by: %s\n' % author
	nlines = nlines + 'Signed-off-by: %s\n' % author
	if t:
		print lines
		if level == 'Merged':
			print '^^^^^^ Already merged to mainstream...'
			print
		return

	if level == 'Merged':
		print 'Already merged to mainstream...'
		return

	nlines = string.replace(nlines, '"', "'")
	nlines = string.replace(nlines, '`', "'")
	lines = string.replace(lines, '"', "'")
	lines = string.replace(lines, '`', "'")

	nfiles = []
	for file in files:
		if not os.path.exists(file):
			nfiles.append(file)
	print 'New files: %s' % nfiles

	os.system('find . -name "*.orig" -exec rm {} \;')
	os.system('find . -name "*.rej" -exec rm {} \;')
	os.system('find . -name "*~" -exec rm {} \;')

	while 1:
		sts, out = my_popen2('cat ' + ff + ' | cg-patch')
		for o in out:
			print o[:-1]
			if string.find(o, 'FAILED') > 0:
				sts = 1
		if sts == 0:
			break
		print 'Patch failed...'
		os.system('cg-reset')
		sys.exit(1)
	for file in nfiles:
		print 'Adding file %s...' % file
		os.system('cg-add %s' % file)
	pline = ''
	for line in diffs:
		if line[:14] == "+++ /dev/null\t" and pline != '':
			file = pline[4:string.find(pline, '\t')]
			file1 = remap_alsa_file(file[11:])[1:]
			print 'Removing file %s...' % file1
			os.system('cg-rm %s' % file1)
		pline = line

	print 'Commiting...'
	os.environ['GIT_AUTHOR_NAME'] = extract_name(real_author)
	os.environ['GIT_AUTHOR_EMAIL'] = extract_email(real_author)
	os.environ['GIT_AUTHOR_DATE'] = date
	os.environ['GIT_COMMITTER_NAME'] = GIT_COMMITTER_NAME
	os.environ['GIT_COMMITTER_EMAIL'] = GIT_COMMITTER_EMAIL
	cmd = 'echo -n "' + nlines + '" | cg-commit'
	while os.system(cmd):
		print 'cg-commit failed'
		os.system('cg-reset')
		sys.exit(1)
	if False:
		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(start):
	os.chdir(get_git_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':
				continue
			y = int(file)
			if y < start:
				continue
			cvsps_merge1(f, t=1)
	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':
				continue
			y = int(file)
			if y < start:
				continue
			cvsps_merge1(f, t=0)
		print 'Is this ok? Press Ctrl-C to abort...'
		sys.stdin.readline()
	del l

def cvsps_merges(start):
	fp = open(get_cvs_root() + '/scripts/changesets/asap.txt')
	csl = fp.readlines()
	fp.close()
	
	cs = []
	for l in csl:
		if l[:1] == "#":
			continue
		if int(l) < start:
			continue
		cs.append(int(l))
	cs.sort()
	del csl
	
	os.chdir(get_git_root())
	for l in cs:
		f = str(l) + '.patch'
		cvsps_merge1(f, t=1)
	print 'Is this ok? Press Ctrl-C to abort...'
	sys.stdin.readline()
	for l in cs:
		f = str(l) + '.patch'
		cvsps_merge1(f, t=0)
		print 'Is this ok? Press Ctrl-C to abort...'
		sys.stdin.readline()
	del cs

def git_version_patch():
	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')

def git_version():
	os.chdir(get_git_root())
	os.system(get_cvs_root() + '/scripts/ksync git-version-patch | cg-patch')
	log = '[ALSA] version %s' % get_cvs_version()
	print log
	os.environ['GIT_AUTHOR_NAME'] = GIT_COMMITTER_NAME
	os.environ['GIT_AUTHOR_EMAIL'] = GIT_COMMITTER_EMAIL
	os.environ['GIT_COMMITTER_NAME'] = GIT_COMMITTER_NAME
	os.environ['GIT_COMMITTER_EMAIL'] = GIT_COMMITTER_EMAIL
	os.system('echo "' + log + '" | cg-commit')

def cvsps_changes1(changes, lines, module):
	if module == 'alsa-kernel':
		module = 'alsa-driver'
	idx = 0
	delim = '---------------------\n'
	while idx < len(lines):
		while idx < len(lines) and lines[idx] != delim:
			idx += 1
		idx += 1
		patchset = 0
		date = ''
		author = ''
		log = []
		logflag = False
		memflag = False
		members = []
		while idx < len(lines) and lines[idx] != delim:
			line = lines[idx]
			# print line
			if line[:8] == 'Members:':
				logflag = False
				memflag = True
			elif logflag:
				if len(line) > 1:
					log.append(line[:-1])
			elif memflag:
				if len(line) > 2:
					members.append(line[1:-1])
			elif line[:9] == 'PatchSet ':
				patchset = long(string.replace(line[9:-1], " FNK_SHOW_ALL", ""))
				#print 'patchset: %s' % patchset
			elif line[:6] == 'Date: ':
				date = line[6:-1]
				#print 'date: %s' % date
			elif line[:8] == 'Author: ':
				author = line[8:-1]
				#print 'author: %s' % author
			elif line[:4] == 'Log:':
				logflag = True
			idx += 1
		a = {}
		a['patchset'] = patchset
		a['date'] = date
		a['author'] = author
		a['log'] = log
		a['members'] = members
		a['module'] = module
		already = False
		idx1 = 0
		for change in changes:
			if a['date'] == change['date'] and \
			   a['author'] == change['author'] and \
			   a['log'] == change['log']:
				# print 'SAME!!!'
				already = True
				break
			if a['date'] < change['date']:
				changes.insert(idx1, a)
				# print 'INSERTED!!!'
				already = True
				break
			idx1 += 1
		if not already:
			changes.append(a)
		del log
		del members

def cvsps_changes2(changes):
	global GERRORS

	res = {}
	try:
		os.remove("/tmp/cvsps-changes.txt")
	except OSError:
		pass
	for change in changes:
		module = change['module']
		if not res.has_key(module):
			res[module] = {}
		members = cvsps_merge_members(change['members'], module)
		if len(members) == 0:
			continue
		members = members[0]
		mems = string.split(members, ',')
		for mem in mems:
			if mem == 'IGNORE':
				continue
			if not res[module].has_key(mem):
				res[module][mem] = []
			res[module][mem].append(change)
	if GERRORS > 0:
		print 'Bailing out...'
		sys.exit(1);
	return res

def cvsps_changes3(allitems):
	items = []
	idx = 0
	for item in ['Sound Core', 'ALSA Core']:
		items.append([item])
		idx += 1
	core = idx
	items.append([])	# Core
	midlevel = idx + 1
	items.append([])	# Midlevel
	all = idx + 2
	items.append(allitems)
	items[all].sort()
	for item in items[all]:
		if string.find(item, 'Core') >= 0:
			items[core].append(item)
		if string.find(item, 'Midlevel') >= 0:
			items[midlevel].append(item)
		if string.find(item, 'API') >= 0:
			items[midlevel].append(item)
	idx1 = core
	while idx1 < all:
		for item in items[idx1]:
			items[all].remove(item)
		idx1 += 1
	for items1 in items[:idx]:
		for item in items1:
			idx1 = idx
			while idx1 < len(items):
				if item in items[idx1]:
					items[idx1].remove(item)
				idx1 += 1
	return items

def rev_to_dot(rev):
	major, minor, subminor = string.split(rev, '-')
	major = major[1:]
	return "%s.%s.%s" % (major, minor, subminor)

def print_underline(c, str):
	i = 0
	while i < len(str):
		sys.stdout.write(c)
		i += 1
	print

def cvsps_changes(rev1, rev2, update=False):
	changes = []
	os.chdir(get_cvs_root_top())
	fullset = ['alsa-driver', 'alsa-kernel', 'alsa-lib',
		   'alsa-utils', 'alsa-tools', 'alsa-firmware',
		   'alsa-oss']
	for module in fullset:
	#for module in ['alsa-oss']:
		os.chdir(get_cvs_root_top() + '/' + module)
		if update:
			os.system("cvsps -Z 3 -u > /dev/null 2> /dev/null")
		xrev = rev1
		while True:
			#print "%s: cvsps -Z 3 -r %s -r %s" % (module, xrev, rev2)
			lines = my_popen("cvsps -Z 3 -r %s -r %s" % (xrev, rev2))
			if len(lines) == 0:
				if ord(xrev[-1:]) > ord('a'):
					last = chr(ord(xrev[-1:]) - 1)
					xrev = xrev[:-1] + last
				elif xrev[-1:] == "a":
					xrev = xrev[:-1]
				else:
					break
			else:
				break
		#lines = my_popen("cvsps -Z 3 -u -r %s -r %s" % (rev1, rev2))
		cvsps_changes1(changes, lines, module)
	res = cvsps_changes2(changes)
	modules1 = res.keys()
	modules = []
	for module in fullset:
		if module in modules1:
			modules.append(module)
	print
	print
	str = 'Changelog between %s and %s releases' % (rev_to_dot(rev1), rev_to_dot(rev2))
	print str
	print_underline('*', str)
	print
	for module in modules:
		print '* %s' % module
		items = cvsps_changes3(res[module].keys())
		for items1 in items:
			for b in items1:
				if not res[module].has_key(b):
					continue
				print '  + %s' % b
				for a in res[module][b]:
					log = a['log'][0]
					if log[:9] == 'Summary: ':
						log = log[9:]
					elif log[:8] == 'Summary:':
						log = log[8:]
					print '    - %s' % log
	print
	print
	str = 'Detailed changelog between %s and %s releases' % (rev_to_dot(rev1), rev_to_dot(rev2))
	print str
	print_underline('*', str)
	print
	for module in modules:
		print '* %s' % module
		items = cvsps_changes3(res[module].keys())
		for items1 in items:
			for b in items1:
				if not res[module].has_key(b):
					continue
				print '  + %s' % b
				for a in res[module][b]:
					log = a['log']
					first = "-"
					for l in log:
						if l[:13] == "Patch-level: ":
							continue
						if l[:13] == "Patch-Level: ":
							continue
						print '    %s %s' % (first, l)
						first = " "

def main():
	global CVSROOT, GITROOT, 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 == '--gitroot':
			GITROOT = 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] == 'work':
		reverse=0
		if args[0] == 'work':
			GITROOT = '~/git/repos/work'
		if len(args) > 1:
			if args[1] == '-r':
				reverse=1
			else:
				print 'Ignoring extra arguments %s' % args[1:]
		diffall(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':
		GITROOT = '~/git/repos/work'
		if len(args) < 2:
			x = 0
		else:
			x = int(args[1])
		cvsps_merge(x)
	elif args[0] == 'cvsps-merges':
		GITROOT = '~/git/repos/stable'
		if len(args) < 2:
			x = 0
		else:
			x = int(args[1])
		cvsps_merges(x)
	elif args[0] == 'cvsps-changes':
		if len(args) < 3:
			print 'Please, give a start and end revision'
			print 'Example: ksync cvsps-changes v1-0-7 v1-0-8'
			return
		update = False
		if len(args) > 3:
			update = True
		cvsps_changes(args[1], args[2], update)
	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] == 'git-version-patch':
		GITROOT = '~/git/repos/work'
		git_version_patch()
	elif args[0] == 'git-version':
		GITROOT = '~/git/repos/work'
		git_version()
	elif args[0] == 'filename':
		ver = 1
		if len(args) > 1:
			ver = args[1]
		filename(ver)
	else:
		print 'Unknown command %s' % args[0]
		sys.exit(1)

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