'''
Copyright (c) 2007, Slide, Inc.

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

	* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
	* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
	* Neither the name of the Slide, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


For questions and patches, contact <tyler@slide.com>

'''
import os
import sys
import string
from optparse import OptionParser

class MergeMaster(object):
	'''
		MergeMaster handles a lot of the specific merge operations that are 
		needed for merging file-by-file from one branch to another
	'''	
	def __init__(self, src, dst, src_rev, dst_rev):
		self.src = src
		self.dst = dst
		self.src_rev = src_rev
		self.dst_rev = dst_rev
		self.dry_run = False
		self.diff_cmd = 'svn diff %(dst)s@%(dst_rev)s %(src)s@%(dst_rev)s'
		self.merge_cmd = 'svn merge -r %(src_rev)s:%(dst_rev)s %(src)s/%(file)s ./%(file)s'
		self.copy_cmd = 'svn copy -r %(dst_rev)s %(src)s/%(file)s ./%(file)s'
	
	def __del__(self):
		pass

	
	def branch_diff(self):
		cmd = self.diff_cmd % {'src_rev' : self.src_rev, 'dst_rev' : self.dst_rev, 'src' : self.src,
							'dst' : self.dst}
		print 'Figuring out which files have changed between the two branches at r%s' % (self.dst_rev)
		files = os.popen('%s | grep "Index: " | sed \'s/Index: //\'' % cmd)
		files = files.readlines()

		for index, file in enumerate(files):
			files[index] = string.strip(file)
	
		return files

	def merge_files(self, files):
		appendage = ''
		new_files = []
		if self.dry_run:
			appendage = ' --dry-run'	
		for file in files:
			cmd = self.merge_cmd % {'src_rev' : self.src_rev, 'dst_rev' : self.dst_rev, 'src' : self.src,
								'dst' : self.dst, 'file' : file}
			cmd += appendage					
			cont = True
			
			if self.interactive:
				print 'Would you like to merge %(file)s?' % {'file' : file}
				sys.stdout.write('[y/n]: ')
				while True:
					val = raw_input()
					if val == 'y':
						break
					elif val ==	'n':
						cont = False
						break
					else:
						sys.stdout.write('Please enter \'y\' or \'n\': ')
						continue

			if not cont:
				continue

			print '==> %s' % cmd
			#	Using popen3 to make sure we capture stderr
			stdin, out, err = os.popen3(cmd)
			if out:
				out = out.readlines()
				out = string.join(out, ' ')
				print string.strip(out)
			if err:
				err = err.readlines()
				err = string.join(err, ' ')

				if string.find(err, 'is not under version control') >= 0:
					print 'Looks like "%s" is a new file, we\'ll add it later' % (file)
					print
					new_files.append(file)

		if not self.dry_run:
			for file in new_files:
				cmd = self.copy_cmd % {'file' : file, 'src' : self.src, 'dst_rev' : self.dst_rev}
				print '==> %s' % cmd
				os.system(cmd)


def main():
	options = OptionParser()
	options.usage = '''

The merge-safe script should help you, the lowly startup employee
more effectively merge one branch to another by examining which files 
have changed, and merge/copy those to the destination branch.
	
Examples:
	Do a dry-run of merging from $SRC to $DST where r1002 is the starting branch of
	$SRC and r1050 is the last revision to merge from $SRC
	  %> python some/dir/merge-safe.py -s $SRC -d $DST -r 1002:1050 --dry-run
		
	Do an interactive merge from $SRC to $DST 
	  %> python some/dir/merge-safe.py -s $SRC -d $DST -r 1002:1050 -i

	Usage: $prog [options]
			'''	
	options.add_option('-s' , '--source', dest='source', help='The source branch to merge from')
	options.add_option('-d' , '--dest', dest='dest', help='The destination branch to merge to')
	options.add_option('-i', '--interactive', action='store_true', dest='interactive', help='Enable merging interactively on each file')
	options.add_option('--dry-run', action='store_true', dest='dry_run', help='Run with --dry-run enabled')
	options.add_option('-r', '--revision', dest='revision', help='Specify the revisions separated by a colon (i.e. -r 100:104)')
	opts, args = options.parse_args()

	if not opts.source or not opts.dest:
		print 'Please enter a valid source and destination branch!'
		return
	if not opts.revision:
		print 'Please enter a valid set of revisions'
		return

	revision = string.split(opts.revision, ':')
	source_revision = revision[0]
	dest_revision = revision[1]
	if not source_revision or not dest_revision:
		print 'Please enter a valid set of revisions'
		return

	master = MergeMaster(opts.source, opts.dest, source_revision, dest_revision)
	master.dry_run = opts.dry_run
	master.interactive = opts.interactive
	diff_files = master.branch_diff()
	
	if len(diff_files) > 0:
		master.merge_files(diff_files)
	

if __name__ == '__main__':
	main()

