#!/usr/bin/env python

# This script searches added or deleted patterns from text files
# stored in a Subversion repository.

# With the -a option, it searches when (at which revision) the
# pattern was Added in the file

# With the -l option, it searches when (at which revision) the pattern
# was deLeted from the file.

# Examples:

## When did we start to use getaddrinfo()?
#   python grephistory -a getaddrinfo foobar.c

## When did we remove the function do_something?
#   python grephistory -l do_something foobar.c

# Author: Stephane Bortzmeyer <bortzmeyer@nic.fr>
# Licence: use as you want

# http://pysvn.tigris.org/
import pysvn

# Standard library
import getopt, sys, re, time

# Options by default
added = None
deleted = None
debug = 0
case_insensitive = False
short = False

def usage(message):
    sys.stderr.write("""
    Searches when a pattern was added or deleted from a file stored in a
       Subversion repository.
Usage:
    %s [-i -s] -a added_pattern filename
  OR
    %s [-i -s] -l deleted_pattern filename
You can also use long options like --deleted, --added,
   --case-insensitive, --short...
%s
""" % (sys.argv[0], sys.argv[0], message))

def fatal(message):
    usage(message)
    sys.exit(1)
                     
try:
    optlist, args = getopt.getopt (sys.argv[1:], "hsl:a:di",
                                   ["deleted=", "added=", "help",
                                    "debug",
                                    "short", "case-insensitive"])
    for option, value in optlist:
        if option == "--added" or option == "-a":
            added = value
        elif option == "--deleted" or option == "-l":
            deleted = value
        elif option == "--help" or option == "-h":
            usage("")
            sys.exit(0)
        elif option == "--short" or option == "-s":
            short = True
        elif option == "--case-insensitive" or option == "-i":
            case_insensitive = True
        elif option == "--debug" or option == "-d":
            debug = debug + 1
        else:
            raise Exception ("Unknown option " + str(option))
except getopt.error, reason:
    fatal(str(reason))
if added is None and deleted is None:
    fatal("-a <string> OR -l <string> is mandatory")
if added is not None and deleted is not None:
    fatal("-a <string> OR -l <string> but not both")
if len(args) != 1:
    fatal("a file name as argument is mandatory")
filename = args[0]

client = pysvn.Client()
info = client.info(filename)
if info is None:
    fatal("File \"%s\" does not exist" % filename)
log = client.log(filename, discover_changed_paths=True, strict_node_history=False)
revision_num = None
previous_revision_num = None
got_it = False
for changeset in log:
    if revision_num is not None:
        previous_revision_num = revision_num
        previous_revision_date = revision_date
    revision_num = changeset['revision'].number
    revision_date = changeset['date']
    if debug >= 2:
        print "Testing revision %i" % revision_num
    content = client.cat(filename, 
                         revision=changeset['revision'],
                         peg_revision=pysvn.Revision(pysvn.opt_revision_kind.unspecified))
    default_options = re.MULTILINE
    if case_insensitive:
        options = default_options | re.IGNORECASE
    else:
        options = default_options
    if added is not None:
        added_found = False
        match = re.search("(%s)" % added, content, options)
        if not match:
            if previous_revision_num is not None:
                got_it = True
            else:
                # The pattern was never there
                if not short:
                    print "\"%s\" (added pattern) not currently found in %s" % (added, filename)
                break
            if got_it:
                if short:
                    print previous_revision_num
                else:
                    print "\"%s\" was added (as \"%s\") in %s at revision %i (%s)" % \
                          (added, result, filename,
                           previous_revision_num,
                           time.strftime("%Y-%m-%d %H:%M", time.localtime(previous_revision_date)))
                break
        else:
            result = match.group(1)
    else: # The pattern is a deleted string, not an added one
        deleted_found = False
        match = re.search("(%s)" % deleted, content, options)
        if match:
            result = match.group(1)
            if previous_revision_num is not None:
                got_it = True
            else:
                # The pattern was not deleted
                if not short:
                    print "\"%s\" (deleted pattern) is still in %s" % \
                          (deleted, filename)
                break
            if got_it:
                if short:
                    print previous_revision_num
                else:
                    print "\"%s\" was deleted (as \"%s\") from %s at revision %i (%s)" % \
                          (deleted, result, filename,
                           previous_revision_num,
                           time.strftime("%Y-%m-%d %H:%M", time.localtime(previous_revision_date)))
                break
if not got_it and previous_revision_num is not None and not short:
    if added is not None:
        print "\"%s\" was always in %s" % (added, filename)
    else:
        print "\"%s\" was never in %s" % (deleted, filename)
        

