# -*- encoding: latin-1 -*-
u"""Subversion pre-commit hook.
Checks to avoid generated files being comitted.

But with an option for dispensation: Mention the file in the log message,
or merely the suspect extension, and the files are accepted.
"""

import sys, os, re, os.path
from subprocess import Popen, PIPE
REPOS,TXN = sys.argv[1:]

suspect_extensions_d = dict((    
    ('.$$$', None),
    ('.a', 'UNIX library'),
    ('.a43', u'IAR executable'),
    ('.bak', None),
    ('.bin', u'Program image'),
    ('.bmp', u'Bitmap'),
    ('.d43', u'IAR executable'),
    ('.dcpil', u'.NET intermediate something'),
    ('.dcu', u'Delphi unit'),
    ('.dcuil', u'.NET assembler'),
    ('.dep', u'IAR dependency-cache'),
    ('.dll', None),
    ('.dsk', u'IAR Desktop (window locations and stuff)'),
    ('.dsm', u'Delphi symbol module'),
    ('.err', u'Fx help compiler output'),
    ('.ewd', u'IAR Desktop'),
    ('.exe', None),
    ('.gid', u'MS Windows auto-generates these when .hlp-files are used'),
    ('.gz', None),
    ('.ilk', 'msvc incremental linking files'),
    ('.lib', None),
    ('.lst', None),
    ('.mak', None),
    ('.map', None),
    ('.o', None),
    ('.obj', None),
    ('.out', None),
    ('.pdb', 'msvc program database files'), 
    ('.pdf', None),
    ('.pyc', u'Python bytecode'),
    ('.pyo', u'Python bytecode'),
    ('.r43', u'IAR object files'),
    ('.rar', u'WinRAR archive'),
    ('.resources', u"Some Delphi.NET autogenerated stuff"),
    ('.rsp', 'response files'), 
    ('.tags', u'TAGS file'),
    ('.tds', 'borland debug symbols'), 
    ('.temp', None),
    ('.tgz', u'tar/gzip archive'),
    ('.tmp', None),
    ('.zip', u'Archives are a bad match with Subversion: they use a disproportional amount of storage space'),
    ))
suspect_extensions = suspect_extensions_d.keys()

suspect_path_components = set(['~', '#', '/old/', ' old/', ' old ', '/old ', '/exe/', '/Debug/', '/Release/'])

def out(tekst):
    print >>sys.stderr, tekst.encode('latin-1','xmlcharrefreplace')

def afkod_mod(lin):
    return lin[0], lin[4:].rstrip()


for candidate_location in [
    r'c:\Program Files\Subversion\bin',
    r'c:\Programmer\Subversion\bin',
    ]:
    candidate_svnlook_exe = os.path.join(candidate_location, 'svnlook.exe')
    if os.path.isfile(candidate_svnlook_exe):
        _svnlook_exe = candidate_svnlook_exe
        if ' ' in _svnlook_exe:
            # what follows cannot deal with spaces on the path, so we must
            # instead hope that svnlook is on the PATH.
            _svnlook_exe = 'svnlook.exe'
        break
else:
    _svnlook_exe = 'svnlook.exe'

def svnlook_t(cmd, args):
    kommando = [_svnlook_exe, cmd, REPOS, '-t', TXN] + args
    if 0:
        p = Popen(kommando, stdout=PIPE, stderr=PIPE)
        res = p.stdout.read(), p.stderr.read()
        p.wait()
        return res
    else:
        childin,childout,childerr = os.popen3(' '.join(kommando))
        res = list(childout.xreadlines()), childerr.read()
        return res

changed,err = svnlook_t('changed', [])
commitmsg,err_commitmsg = svnlook_t('log', [])
commitmsg_lower = '\n'.join(commitmsg).lower()

if err != '':
    out(u"pre-commit.py: Unexpected svnlook error.")
    out(err)
    sys.exit(1)
else:
    mods = [afkod_mod(lin) for lin in changed if lin != '']
    found_suspect_extensions = set()
    suspect_path_components_found = set()
    
    for type,name in mods:
        for comp in suspect_path_components:
            if (comp in name or comp in name.lower()) and os.path.basename(name).lower() not in commitmsg_lower:
                if comp not in suspect_path_components_found:
                    suspect_path_components_found.add(comp)
                    out(u'%r found (e.g. in %s)' % (repr(comp), repr(name)))
                    out(u"Please reconsider if the %s-files belong in the repository" % (comp,))
                    out(u"If due to VERY SPECIAL CIRCUMSTANCES you wish to "
                        "commit these files anyway, please explain why in "
                        "the log message.")
        
        ext = os.path.splitext(name)[1].lower()
        if ext in suspect_extensions and type in 'AMU':
            if ext not in found_suspect_extensions:
                basename = os.path.basename(name)
                if (basename.lower() in commitmsg_lower
                    or re.search('[^a-zA-Z_0-9]'+re.escape(ext), commitmsg_lower)):
                    pass # ignorer
                else:
                    out(u"Do you really want to commit %s-files?" % (ext,))
                    description = suspect_extensions_d[ext]
                    if description is not None:
                        out(u'%s is described as: %s' % (ext,description))
                    found_suspect_extensions.add(ext)
    if len(found_suspect_extensions) > 0:
        out(u"Please reconsider if the %s-files belong the the repository" % (
            '/'.join(found_suspect_extensions)))
        out(u"If due to VERY SPECIAL CIRCUMSTANCES you wish to commit those "
            "files anyway, please explain why in the log message.")
        out(u"During the course of your elaborate and eloquent explanation "
            "of what those special circumstances are, chances are that you "
            "will happen to mention the names of the problematic files - and "
            "in doing that, your commit will be accepted. Wildcards can be "
            "used.")

    if (len(suspect_path_components_found) > 0
        or len(found_suspect_extensions) > 0):
        sys.exit(1)
