#!/usr/bin/python3
# vim: set et sw=4 :

import os
import re
import subprocess
import sys

allowed_paths = [ "/vendor/" ]
debug = False

def get_svninfo_value(svnlog, lookfor):
    for l in svnlog.splitlines():
        if l.startswith(lookfor):
            return l[len(lookfor):]

def get_common_part(f1, f2):
    l1 = f1.split('/')
    l2 = f2.split('/')
    for i in range(0, min(len(l1), len(l2))):
        if l1[i] != l2[i]:
            break;
    else:
        i = min(len(l1), len(l2)) + 1
    return '/'.join(l1[0:i])

def get_original_path(f):
    copied_path = f
    rest = ""
    wcroot = None
    while True:
        if copied_path == wcroot:
            return f
        svnlog = subprocess.check_output(["svn", "info", copied_path], universal_newlines=True)
        if wcroot is None:
            wcroot = get_svninfo_value(svnlog, "Working Copy Root Path: ") # Path to WC
        rel_path = get_svninfo_value(svnlog, "Relative URL: ^") # Relative URL
        root_url = get_svninfo_value(svnlog, "Repository Root: ") # Repository root URL
        copy_url = get_svninfo_value(svnlog, "Copied From URL: ") # Copy-from URL
        if (rel_path is None or root_url is None):
            raise ValueError
        if (copy_url is not None):
            if (not(copy_url.startswith(root_url + '/'))):
                print("Invalid copy URL")
                raise ValueError
            break
        last_slash = copied_path.rindex("/")
        rest = copied_path[last_slash:] + rest
        copied_path = copied_path[:last_slash]
    rel_path += rest
    copy_url += rest
    svnlog = subprocess.check_output(["svn", "info", wcroot], universal_newlines=True)
    rel_root_path = get_svninfo_value(svnlog, "Relative URL: ^") # Relative URL for root of WC
    if rel_root_path is None:
        print("No root path found")
        raise ValueError
    lookfor = copy_url[len(root_url):]
    if debug:
        print('wcroot %s' % wcroot)
        print("root rel path {%s}" % rel_root_path)
        print("look for {%s}" % lookfor)
    try:
        svnlog = subprocess.check_output(["svn", "log", "-qv", f], universal_newlines=True)
    except subprocess.CalledProcessError:
        return f # Ok, even though inside a copied path, this path does not seem to be copied
    logrevs = svnlog.split("------------------------------------------------------------------------\n")[1:-1]
    while True:
        if lookfor.startswith(rel_root_path):
            orig = wcroot + lookfor[len(rel_root_path):]
            if debug:
                print("found local copy source for `%s': `%s' (lookfor `%s')" % (f, orig, lookfor))
            return orig
        elif get_common_part(lookfor, rel_root_path):
            if debug:
                print("`%s' copied from `%s', but top of WC (`%s') does not include that path" % (f, lookfor, rel_root_path))
            return None # Change to plain addition
        while True:
            if len(logrevs) == 0:
                if debug:
                    print("----------------------------")
                return None # Suitable copy source not found. Change to addition
            r = logrevs.pop(0)
            loglines = r.splitlines()
            loglines.reverse()
            m = None
            while len(loglines) != 0:
                l = loglines.pop(0)
                m = re.match('^   [AR] (?P<dst>.*) \(from (?P<src>.*):(?P<rev>[0-9]+)\)$', l)
                if not m:
                    continue
                copy_src = m.group('src')
                copy_dst = m.group('dst')
                copy_rev = m.group('rev')
                dst_common = get_common_part(copy_dst, lookfor)
                if debug:
                    print("dst_common `%s' copy_src `%s' copy_dst `%s' [%s]" %
                            (dst_common, copy_src, copy_dst, l))
                if dst_common == copy_dst:
                    break # Includes path of interest
            else:
                continue # This revision did not touch the path
            break # Ok, determine where it came from and figure out what to look for now

        # Replace the copied path with the copy source path
        lookfor = copy_src + lookfor[len(copy_dst):]
        if debug:
            print('==')
            print(copy_src)
            print(copy_dst)
            print(dst_common)
            print("look for {%s}" % lookfor)

        # Check if it is coming from an explicitly allowed location
        for a in allowed_paths:
            if lookfor.startswith(a):
                src = '^' + lookfor + '@' + copy_rev
                if debug:
                    print("found explicitly allowed source for `%s': `%s'" % (f, src))
                return src
            continue # Does not include path of interest

def do_svn_copy(src, dst):
    proplist = subprocess.check_output(["svn", "pl", "-q", dst], universal_newlines=True).splitlines()
    props = {}
    for p in proplist:
        # Property list is indented 2 spaces
        p = p[2:]
        v = subprocess.check_output(["svn", "pg", p, dst], universal_newlines=True)
        if v[-1] == '\n':
            # 'svn pg' prints an extra new line at the end
            v = v[:-1]
        props[p] = v
    os.rename(dst, dst + ".preserve-modified")
    subprocess.check_call(["svn", "rm", "--force", dst])
    if src:
        subprocess.check_call(["svn", "cp", "-q", src, dst])
        os.rename(dst + ".preserve-modified", dst)
        # Clear copied properties in preparation for restoration belo
        proplist = subprocess.check_output(["svn", "pl", "-q", dst],
                universal_newlines=True).splitlines()
        for p in proplist:
            p = p[2:]
            subprocess.check_call(["svn", "pd", "-q", p, dst])
    else:
        os.rename(dst + ".preserve-modified", dst)
        subprocess.check_call(["svn", "add", "--no-auto-props", dst])
    for p in props:
        subprocess.check_call(["svn", "ps", "-q", p, props[p], dst])

def do_copy(src, dst):
    if src[0] == '^':
        # copy from SVN path
        do_svn_copy(src, dst)
    else:
        # copy from local file - obtain SVN path/revision and reduce to SVN copy
        svnlog = subprocess.check_output(["svn", "info", src], universal_newlines=True)
        url = get_svninfo_value(svnlog, "Relative URL: ")
        rev = get_svninfo_value(svnlog, "Revision: ")
        do_svn_copy(url + '@' + rev, dst)

def handle_path(f):
    af = os.path.abspath(f)
    orig = get_original_path(af)
    if orig is None or os.environ.get('BREAK_HISTORY'):
        if os.environ.get('PRESERVE_HISTORY'):
            print("Original path not found for `%s'" % f)
        else:
            print("`%s' <- new file" % (af))
            do_svn_copy(None, af)
    elif orig != af:
        print("`%s' <- `%s'" % (af, orig))
        do_copy(orig, af)
    else:
        print("`%s' not copied" % (af))

if __name__ == "__main__":
    for f in sys.argv[1:]:
        if os.path.isdir(f):
            # Checking status of each file would be gruesomely long process. Quickly check
            # `svn st' to get the list of changes - then handle each copied file and recurse into
            # each copied directory
            status = subprocess.check_output(["svn", "st", f], universal_newlines=True)
            for l in status.splitlines():
                if len(l) < 4 or l[3] != "+":
                    continue
                path = l[8:]
                if os.path.isdir(path):
                    for c, ds, fs in os.walk(path):
                        for f in fs:
                            handle_path(os.path.join(c, f))
                else:
                    handle_path(path)
        else:
            handle_path(f)
