#!/usr/bin/env python
#
#   Copyright (C) 2005 Daniel Burrows
#
#   This program is free software; you can redistribute it and/or
#   modify it under the terms of the GNU General Public License as
#   published by the Free Software Foundation; either version 2 of the
#   License, or (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#   General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; see the file COPYING.  If not, write to
#   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#   Boston, MA 02111-1307, USA.
#
# A simple script to auto-generate Closes emails for things in a
# commit that look like Closes messages.  Only numbers that appear in
# newly added lines and not in deleted lines are parsed.
#
# Usage: svntag.py REPOS REV [path-to-config-file]

from itertools import *
import pwd
import os
from sets import Set
import sre
import sys

import ConfigParser

if len(sys.argv) < 3:
    sys.stderr.write('Need at least two arguments!\n')
    sys.exit(-1)


cp = ConfigParser.ConfigParser({'username' : pwd.getpwuid(os.getuid()).pw_name,
                                'repodir'  : sys.argv[1]})

cp.add_section('mail')
cp.set('mail', 'control_addr', 'control@bugs.debian.org')
# Default to the revision's author.
cp.set('mail', 'from_addr', '%(username)s')
cp.set('mail', 'sendmail', '/usr/sbin/sendmail')
       
cp.add_section('debbugs')
cp.set('debbugs', 'tag_user', '')
cp.set('debbugs', 'tag_name', 'fixed-in-svn')
cp.set('debbugs', 'bugnumre', '(?i)(?:(?:closes:? *#?)|#)([0-9]{5,6})')
cp.set('debbugs', 'package', '')

cp.add_section('svn')
cp.set('svn', 'repo_dir', '%(repodir)s')
# Any file containing this regular expression will be searched for bug
# closures:
cp.set('svn', 'extractfromre', '(?i)changelog')
cp.set('svn', 'svnlook', '/usr/bin/svnlook')

cp.read(sys.argv[2:])

# Ok...
repodir = cp.get('svn', 'repo_dir')
rev = sys.argv[2]
bugnumre = sre.compile(cp.get('debbugs', 'bugnumre'))
extractfromre = sre.compile(cp.get('svn' ,'extractfromre'))

difftext = os.popen('%s diff %s -r %s'%(cp.get('svn', 'svnlook'),
                                        repodir, rev)).read()

diffheader = sre.compile(r'(?:^|\n)---\s(\S*)[^\n]*\n\+\+\+\s(\S*)[^\n]*(?=\n)')
diffhunk = sre.compile(r'(?:\n|^)@@\s[^@]*\s@@(?=\n)((?:\n[^@][^\n]*(?=\n))*)')
diffsubline = sre.compile(r'(?:\n|^)(-[^\n]*)(?=\n)')
diffaddline = sre.compile(r'(?:\n|^)(\+[^\n]*)(?=\n)')
diffdiffline = sre.compile(r'(?:\n|^)([-+][^\n]*)(?=\n)')

def safe_find_iter(pattern, s, start, end):
    match = pattern.search(s, start, end)
    while match <> None:
        start = match.end()
        yield match
        match = pattern.search(s, start, end)

def parsediff(difftext):
    """Split the given diff into a series of file differences.
    Returns (match1, match2, [hunktexts]) where [hunktexts] is
    a list of the text of the various diff hunks."""

    matches = [x for x in diffheader.finditer(difftext)]

    for match, next in izip(matches, chain(islice(matches, 1, None), [None])):
        start = match.start()
        if next == None:
            end = len(difftext)
        else:
            end = next.start()

        hunks = [x.group(1) for x in safe_find_iter(diffhunk, difftext, start, end)]

        yield match.group(1), match.group(2), hunks

# text should be either diffaddline or diffsubline
def bugsbypattern(pat, text):
    lines = safe_find_iter(pat, text, 0, len(text))
    return Set([x.group(1) for line in lines for x in safe_find_iter(bugnumre, line.group(1), 1, len(line.group(1)))])

bugs = Set()

for f1, f2, hunktexts in parsediff(difftext):
    if extractfromre.search(f1) or extractfromre.search(f2):
        for hunk in hunktexts:
            sublines = bugsbypattern(diffsubline, hunk)
            addlines = bugsbypattern(diffaddline, hunk)

            bugs |= addlines - sublines



svnlook = cp.get('svn', 'svnlook')
from_addr = cp.get('mail', 'from_addr')
if len(from_addr) == 0:
    from_addr = pw.getpwuid(os.getuid()).pw_name
control_addr = cp.get('mail', 'control_addr')

# Great, let's do it!
mail = """From: %s
To: %s
Subject: Automatic tagging by svntag.py for revision %s
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8

"""%(from_addr, ', '.join(control_addr.split()), rev)

package = cp.get('debbugs', 'package')
if len(package) > 0:
    mail += 'package %s\n'%package

tag_user = cp.get('debbugs', 'tag_user')
if len(tag_user) > 0:
    mail += 'user %s\n'%tag_user

for bug in bugs:
    mail += 'usertag %s + %s\n'%(bug, cp.get('debbugs', 'tag_name'))

mail += 'thanks\n'

mail += '\nAutomatic bug modification triggered by the following commit:\n\n'

mail += 'Author: %s\n'%os.popen('%s author %s %s'%(svnlook, repodir, rev)).read().strip()
mail += 'Date: %s\n'%os.popen('%s date %s %s'%(svnlook, repodir, rev)).read().strip()
mail += 'New Revision: %s\n'%rev

mail += '\n'

mail += 'Diff:\n'
mail += difftext
mail += '\n'

# Send it.
sendmail = cp.get('mail', 'sendmail')
os.popen('%s -f %s %s'%(sendmail, from_addr, control_addr), 'w').write(mail)


