#! /usr/bin/env python
"""Helper script for large collections of subversion commits."""

USAGE = """
Usage:

    %(program)s [file]

Perform a set of subversion commits using a listing of filenames and
checkin comments specified in 'file'.  If 'file' is not given or is
'-', standard input is used.

The input is a series of commit descriptions.  Each description is a
set of status lines as printed by the 'svn status' command, consisting
of 'A', 'M', and 'R' lines.  After each group of status lines, the
corresponding checkin comment should be written, separated from status
lines by blank lines.

The normal usage pattern is to generate an input file using the 'svn
status' command:

    $ svn status >changes

and then editing the generated file to include checkin comments.  A
short example might look like this:

    M src/pkg/foo.py
    R src/pkg/subpkg/utils.py

    Replace the foobar helper with an inline implementation; it wasn't
    used anywhere else.

    A src/helpers/fastimpl.c
    M setup.py

    Add an optimized frobnicator.

    Checkin comments can have multiple paragraphs.

    M src/app/myalgorithm.py

    Use the new optimized frobnicator to get an order of magnitude
    more speed.

This file can then be fed into %(program)s to generate three separate
commits, one for each different comment.

    $ %(program)s changes
"""

import getopt
import os
import re
import sys
import tempfile

paragraph_sep = re.compile(r"(\n\s*)+\n")
ops = re.compile(r"^([+ARM] [^\n]+\n)+$").match
qops = re.compile(r"^([+ARM?] [^\n]+\n)+$").match

def main(argv):

    try:
        opts, args = getopt.getopt(sys.argv[1:], "h?", ["help"])
    except getopt.GetoptError, e:
        print >>sys.stderr, e
        usage(sys.stderr, 2)

    if opts:
        print __doc__.strip()
        usage(sys.stdout, 0)

    if len(args) > 1:
        print >>sys.stderr, "too many arguments"
        usage(sys.stderr, 2)
    elif args:
        # read from stdin by default
        f = args[0]
    else:
        f = '-'

    if f == '-':
        f = sys.stdin
    else:
        f = open(f)

    data = f.read().rstrip()
    paragraphs = [(p.strip()+'\n') for p in paragraph_sep.split(data)]

    docfilename = tempfile.mktemp()

    p = paragraphs.pop(0)
    if not ops(p):
        if qops(p):
            raise "? found in operations!"
        raise "Should start with ops", p
    doc = []
    checkins = [(p, doc)]
    for p in paragraphs:
        if ops(p):
            if not doc:
                raise "No doc for", checkins[-1][0]
            doc = []
            checkins.append((p, doc))
        else:
            if qops(p):
                raise "? found in operations!"
            doc.append(p)

    for (p, doc) in checkins:
        for l in p.strip().split('\n'):
            if l[0] == '+':
                command = 'svn add ' + l[2:]
                print command
                os.system(command)

        files_to_commit = ' '.join([l[2:] for l in p.strip().split('\n')])

        docfile = open(docfilename, 'w')
        docfile.write(''.join(doc))
        docfile.close()

        command = 'svn commit -F %s %s' % (docfilename, files_to_commit)
        print command
        os.system(command)

    os.remove(docfilename)


def usage(f, rc):
    program = os.path.basename(sys.argv[0])
    message = USAGE % {"program": program}
    print >>f, message
    sys.exit(rc)


if __name__ == '__main__':
    main(sys.argv)
