#!/usr/bin/env python
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import optparse as op
import os
import re
import subprocess as sp
import sys
import tempfile
import textwrap
import urlparse


__usage__ = "%prog [OPTIONS] SVN_PATH GIT_DIR"

SVN_BASE = "https://svn.apache.org/repos/asf/"
REF_RE = re.compile("refs/remotes/([^@]+)")


def options():
    return [
        op.make_option('-a', '--authors', metavar="FILE", dest="authors",
            default="/usr/local/etc/asf-authors",
            help="Path to the ASF authors file."),
        op.make_option('-d', '--description', metavar="DESC", dest='desc',
            help="A short project description. ie, 'Apache Jackrabbit'")
    ]


def main():
    parser = op.OptionParser(usage=__usage__, option_list=options())
    opts, args = parser.parse_args()

    if len(args) == 0:
        parser.error("Missing required SVN_URL and GIT_DIR arguments.")
    if len(args) == 1:
        parser.error("Missing required GIT_DIR argument.")
    if len(args) > 2:
        parser.error("Unknown arguments: %s" % ", ".join(args[2:]))

    svn_url = urlparse.urljoin(SVN_BASE, args[0].lstrip().lstrip("/"))
    git_dir = args[1]
    os.putenv("GIT_DIR", git_dir)

    if opts.authors is None:
        opts.authors = "/usr/local/etc/asf-authors"

    if os.path.exists(git_dir):
        error("Git directory exists: %s" % git_dir)
    if not os.path.exists(opts.authors):
        error("Missing authors file: %s" % opts.authors)

    init_git_dir(git_dir, svn_url, opts.desc)
    clone_svn(git_dir, opts.authors)
    cleanup_clone()
    configure_asfgit(git_dir)


def init_git_dir(git_dir, svn_url, desc):
    log("Creating mirror in: %s" % git_dir)

    git("svn", "init", "-s", svn_url)
    git("config", "gitweb.owner", "The Apache Software Foundation")
    save(os.path.join(git_dir, "HEAD"), "ref: refs/heads/trunk")
    git("update-server-info")
    
    if desc is None:
        desc = run_editor(initial="Project description, ie 'Apache Jackrabbit'")
    if desc is None:
        error("No repository description provided.")
    save(os.path.join(git_dir, "description"), desc)


def clone_svn(git_dir, authors):
    log("Initializing Git repository.")
    git("svn", "fetch", "--authors-file", authors, "--log-window-size=10000")
    
    log("Updating branch refs.")
    refs = git("for-each-ref", "refs/remotes", capture=True)
    for ref in refs.splitlines():
        match = REF_RE.match(ref.split()[-1])
        if not match:
            continue
        ref = match.group(1)
        if ref.startswith("tags/"):
            continue
        git("update-ref", "refs/heads/%s" % ref, "refs/remotes/%s" % ref)

    log("Creating Git tags from SVN pseudo-tags")
    refs = git("for-each-ref", "refs/remotes/tags", capture=True)
    for ref in refs.splitlines():
        ref = ref.split()[-1]
        if ref.find("@") >= 0:
            continue
        tag = ref.split("/", 3)[-1]

        for opt in ("name", "email", "date"):
            fmt = "--format=%%(committer%s)" % opt
            val = git("for-each-ref", fmt, ref, capture=True).strip()
            os.putenv("GIT_COMMITTER_%s" % opt.upper(), val)
        git("tag", "-f", "-m", tag, tag, ref)


def cleanup_clone():
    log("Cleaning up new Git clone.")
    git("update-server-info")
    git("gc", "--aggressive")


def configure_asfgit(git_dir):
    asfgit = os.getenv("ASFGIT_ADMIN") or "/usr/local/etc/asfgit-admin"
    if not os.path.exists(asfgit):
        log("WARNING: asfgit-admin directory not found.")
        log("WARNING: Skipping hosting configuration.")
        return
    
    log("Installing hook symlinks.")
    for name in ("pre-receive", "post-receive"):
        src = os.path.abspath(os.path.join(asfgit, "hooks", name))
        dst = os.path.join(git_dir, "hooks", name)
        if not os.path.exists(src):
            error("Missing pre-receive hook: %s" % name)
        if os.path.exists(dst):
            os.unlink(dst)
        os.symlink(src, dst)
    
    log("Initializaing hosting configuration")

    cfgfile = os.path.join(asfgit, "conf", "gitconfig")
    dstfile = os.path.join(git_dir, "config")
    
    # Make sure it hasn't already been initialized
    try:
        sp.check_output(["git", "config", "hooks.asfgit.debug"])
        added = True
    except sp.CalledProcessError:
        added = False

    if not added:
        # Get base config
        if not os.path.exists(cfgfile):
            error("Missing default git configuration: %s" % cfgfile)
        with open(cfgfile) as handle:
            config = handle.read()
        git_repo = os.path.basename(git_dir)
        config = config % {"git_repo": git_repo}

        # Append the config data to the git config
        # and let the user review it.
        if not os.path.exists(dstfile):
            error("Missing destinationg git config: %s" % dstfile)
        with open(dstfile, "a") as handle:
            handle.write(config)
        
    # Boot the user's editor to review the conifg.
    run_editor(filename=dstfile)
    
    # Final steps
    git_repo = os.path.basename(git_dir)
    log(textwrap.dedent("""\
        
        
        To finish the hosting configuration you need to copy %(git_repo)s to
        the repository hosting directory and run the following:
              
          $ sudo chown -R nobody:daemon $(REPOS)/%(git_repo)s
          $ sudo chmod g+x $(REPOS)/%(git_repo)s
        
        At the time of this writing, $(REPOS) should be:
        
          /usr/local/www/git-wip-us.apache.org/repos/asf
        
        """ % {"git_repo": git_repo}))
    


def run_editor(filename=None, initial=""):
    editor = os.getenv("EDITOR", "nano")
    if filename is None:
        with tempfile.NamedTemporaryFile(delete=False) as tf:
            fname = tf.name
            tf.write(initial)
    else:
        fname = filename
    try:
        if sp.call([editor, fname]) != 0:
            return None
        with open(fname) as handle:
            return handle.read()
    finally:
        if filename is None:
            os.remove(fname)


def git(cmd, *args, **kwargs):
    cmd = ["git", cmd] + list(args)
    if kwargs.pop("capture", False):
        return run(cmd, stderr=sp.STDOUT)
    sp.check_call(cmd)


def run(cmd, *args, **kwargs):
    if isinstance(cmd, list):
        return sp.check_output(cmd, **kwargs)
    else:
        return sp.check_output([cmd] + list(args), **kwargs)


def save(fname, contents):
    with open(fname, "w") as handle:
        handle.write(contents)


def log(mesg):
    sys.stderr.write("%s\n" % mesg)


def error(mesg, exit_code=1):
    sys.stderr.write("ERROR: %s\n" % mesg)
    sys.exit(exit_code)


if __name__ == '__main__':
    main()