#!/usr/bin/env python

import sys

# copied from portable
def removeReadOnlyFiles(fn, path):
    if not os.access(path, os.W_OK):
        os.chmod(path, stat.S_IWUSR)
        fn(path)
    else:
        raise Exception("Could not delete %s" % path)


def stream2str(stream):
    return str(stream, encoding='UTF-8')


if sys.version_info[0] < 3:
    print('git-repo is written for python3, your version is %s, please upgrade from www.python.org.' % sys.version)
    exit(0)

## repo default configuration
##
REPO_URL = 'https://github.com/esrlabs/git-repo'
REPO_REV = 'master'

# Copyright (C) 2008 Google Inc.
#
# Licensed 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.

# increment this whenever we make important changes to this script
VERSION = (1, 19, 2)

# increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (1, 1)
MAINTAINER_KEYS = """

     Matthias Putz (E.S.R.Labs) <matthias.putz@esrlabs.com>
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)

mQENBFFLBlsBCADb7H48CO/QoZRcTZb6u+XP9x43YnJmPziU142s/FvwdV1A83ck
IgBUGTkC2Nt5caCN2UihniaIG0Vb1koJwuFk7AWQsliU3q4A6k+mlN+I4E08JmwJ
kQk1AMKV6zb3bfmDp4zy+ZobySi2iBY0mghfOfQavKOGyCDT8GFlvpjumg4ykj9j
hEHI0twso3cD7QCKuPpsnyHvVHHUjP8AXZfhjJVKnc2zvqZGcU1sAiqNWiDwk/e3
Oo4kmEBoJ19UhLj5NRXdBkA9N3mHyYSTpn+qhz1JUS74smF2vcNxt3CPO/FqDwnS
Xxd2nkL7j1/5atHVQHDExb4HaIMlD1TFYxsrABEBAAG0JkUuUy5SLkxhYnMgPG1h
dHRoaWFzLnB1dHpAZXNybGFicy5jb20+iQE4BBMBAgAiBQJRSwZbAhsDBgsJCAcD
AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDaOdHhtWagKjB6B/wK8LABMMeB1/XD23O3
BzBXWLYURgiZk75vGfVsChMc11Z6HxFgafABjh3VmWLQC/MmxlnoJX1wAohlSQbq
q7XAmXshkYiAF+X3/jEXV8DPavHj0E34mnmnBdS0cpx0zoWYIbtb7nIkSPVX4pzo
fgHEomphzHU/q27rOrOslNjdPyyF/96Kzcaip8NwfSt06qbJCHMzvWPtKw+GdQuL
qA5GLVXj+yPXp18ARsqZa/LIrmWbTzmDYb4Zdd42E/L0x+WQc/DusFxnJEMKTcAk
3yHYIJXmHe/KIeLTSeni+yrUCJa/4g5s8KB5PRNsD00CZgQjObz1GgLxeS71gTOX
nudRuQENBFFLBlsBCACafbdP+bF8R/P3inTu5fIBRcGThMx1ivDCMiGZCP7U0eKj
rXQZ60u327I4J1a0H8nwmIE3O7ufIXyl79OXXbxJhhW7qprA/qrtr2EnC2Qgb9cu
eX6ZazYwjamhpZCsEZw4zAygfRKOMp7OETtCO05LmyoC4vnpMYmUOxqTnqhAKPdJ
Xys2cRe8f+b94ueYzxZa0eO0qSsAf1w+IbDfKwbbTtBGRP6hpBHD0dCRE9ptFqrv
wj/XlNQUKO65XEJ7dNQltHuZfSK10OTNmvt8PUq0qY9wsaDvnk4MXcvHVd8HsssZ
VbVChyk0RPla+WtA3R2ftq68HhUe4xMrp1MPiRjvABEBAAGJAR8EGAECAAkFAlFL
BlsCGwwACgkQ2jnR4bVmoCqKiggAz9yq/SDG9uKtNOPod4jSAqLvdz4t2/tZZReT
DGA9SyTnF094hKJ1KpLdm9Niv6iHbikHxFloSTKXLMrtfyqbtX2ne62NlBzQhLzn
Cph6D1k/c7wa2E/3HHq/bcQ3oZwExyy75d0V7/caGXr/ezf+ghWz5YDV53j99KWx
i3RIBfNgsHpCow9/2JG/c38HK9oMRDES3Rjvg8zxnNJGUDOr1AyW2JpTQtYDWAoc
UYOxmBnVcAjZq8uA0+88cjuCwlh1cnVik75uGOFvffBEDa3C1uflhDwW9nngSi4O
tKO/KcU9db+FQKCvKRYwbKWhNgtukdMDh0BZGIsdLNRUsdLEOA==
=o/U8
-----END PGP PUBLIC KEY BLOCK-----
"""

GIT = 'git'                     # our git command
MIN_GIT_VERSION = (1, 7, 2)     # minimum supported git version
repodir = '.repo'               # name of repo's private directory
S_repo = 'repo'                 # special repo repository
S_manifests = 'manifests'       # special manifest repository
REPO_MAIN = S_repo + '/main.py' # main script

import optparse
import os
import re
import stat
import subprocess
import urllib.request
import urllib.error

home_dot_repo = os.path.expanduser('~/.repoconfig_esrlabs')
gpg_dir = os.path.join(home_dot_repo, 'gnupg')

extra_args = []
init_optparse = optparse.OptionParser(usage="repo init -u url [options]")

# Logging
group = init_optparse.add_option_group('Logging options')
group.add_option('-q', '--quiet',
                 dest="quiet", action="store_true", default=False,
                 help="be quiet")

# Manifest
group = init_optparse.add_option_group('Manifest options')
group.add_option('-u', '--manifest-url',
                 dest='manifest_url',
                 help='manifest repository location', metavar='URL')
group.add_option('-b', '--manifest-branch',
                 dest='manifest_branch',
                 help='manifest branch or revision', metavar='REVISION')
group.add_option('-m', '--manifest-name',
                 dest='manifest_name',
                 help='initial manifest file', metavar='NAME.xml')
group.add_option('--mirror',
                 dest='mirror', action='store_true',
                 help='create a replica of the remote repositories '
                      'rather than a client working directory')
group.add_option('--reference',
                 dest='reference',
                 help='location of mirror directory', metavar='DIR')
group.add_option('--depth', type='int', default=None,
                 dest='depth',
                 help='create a shallow clone with given depth; see git clone')
group.add_option('-g', '--groups',
                 dest='groups', default='default',
                 help='restrict manifest projects to ones with a specified group',
                 metavar='GROUP')
group.add_option('-p', '--platform',
                 dest='platform', default="auto",
                 help='restrict manifest projects to ones with a specified '
                      'platform group [auto|all|none|linux|darwin|...]',
                 metavar='PLATFORM')


# Tool
group = init_optparse.add_option_group('repo Version options')
group.add_option('--repo-url',
                 dest='repo_url',
                 help='repo repository location', metavar='URL')
group.add_option('--repo-branch',
                 dest='repo_branch',
                 help='repo branch or revision', metavar='REVISION')
group.add_option('--no-repo-verify',
                 dest='no_repo_verify', action='store_true',
                 help='do not verify repo source code')

# Other
group = init_optparse.add_option_group('Other options')
group.add_option('--config-name',
                 dest='config_name', action="store_true", default=False,
                 help='Always prompt for name/e-mail')


class CloneFailure(Exception):
    """Indicate the remote clone of repo itself failed.
    """


def _Init(args):
    """Installs repo by cloning it over the network.
    """
    opt, args = init_optparse.parse_args(args)
    if args:
        init_optparse.print_usage()
        sys.exit(1)

    url = opt.repo_url
    if not url:
        url = REPO_URL
        extra_args.append('--repo-url=%s' % url)

    branch = opt.repo_branch
    if not branch:
        branch = REPO_REV
        extra_args.append('--repo-branch=%s' % branch)

    if branch.startswith('refs/heads/'):
        branch = branch[len('refs/heads/'):]
    if branch.startswith('refs/'):
        print("fatal: invalid branch name '%s'" % branch, file=sys.stderr)
        raise CloneFailure()

    if not os.path.isdir(repodir):
        try:
            os.mkdir(repodir)
        except OSError as e:
            print('fatal: cannot make %s directory: %s'
                  % (repodir, e.strerror), file=sys.stderr)
            # Don't raise CloneFailure; that would delete the
            # name. Instead exit immediately.
            #
            sys.exit(1)

    _CheckGitVersion()
    try:
        if NeedSetupGnuPG():
            can_verify = SetupGnuPG(opt.quiet)
        else:
            can_verify = True

        dst = os.path.abspath(os.path.join(repodir, S_repo))
        _Clone(url, dst, opt.quiet)

        if can_verify and not opt.no_repo_verify:
            rev = _Verify(dst, branch, opt.quiet)
        else:
            rev = 'refs/remotes/origin/%s^0' % branch

        _Checkout(dst, branch, rev, opt.quiet)
    except CloneFailure:
        if opt.quiet:
            print('fatal: repo init failed; run without --quiet to see why',
                  file=sys.stderr)
        raise


def _CheckGitVersion():
    cmd = [GIT, '--version']
    try:
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    except OSError as e:
        print(file=sys.stderr)
        print("fatal: '%s' is not available" % GIT, file=sys.stderr)
        print('fatal: %s' % e, file=sys.stderr)
        print(file=sys.stderr)
        print('Please make sure %s is installed and in your path.' % GIT,
              file=sys.stderr)
        raise CloneFailure()

    ver_str = stream2str(proc.stdout.read()).strip()
    proc.stdout.close()
    proc.wait()

    if not ver_str.startswith('git version '):
        print('error: "%s" unsupported' % ver_str, file=sys.stderr)
        raise CloneFailure()

    ver_str = ver_str[len('git version '):].strip()
    ver_act = tuple(map(int, ver_str.split('.')[0:3]))
    if ver_act < MIN_GIT_VERSION:
        need = '.'.join(map(str, MIN_GIT_VERSION))
        print('fatal: git %s or later required' % need, file=sys.stderr)
        raise CloneFailure()


def NeedSetupGnuPG():
    if not os.path.isdir(home_dot_repo):
        return True

    kv = os.path.join(home_dot_repo, 'keyring-version')
    if not os.path.exists(kv):
        return True

    kv = open(kv).read()
    if not kv:
        return True

    kv = tuple(map(int, kv.split('.')))
    if kv < KEYRING_VERSION:
        return True
    return False


def SetupGnuPG(quiet):
    if not os.path.isdir(home_dot_repo):
        try:
            os.mkdir(home_dot_repo)
        except OSError as e:
            print('fatal: cannot make %s directory: %s'
                  % (home_dot_repo, e.strerror), file=sys.stderr)
            sys.exit(1)

    if not os.path.isdir(gpg_dir):
        try:
            os.mkdir(gpg_dir, stat.S_IRWXU)
        except OSError as e:
            print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror),
                  file=sys.stderr)
            sys.exit(1)

    env = os.environ.copy()
    env['GNUPGHOME'] = gpg_dir

    cmd = ['gpg', '--import']
    try:
        proc = subprocess.Popen(cmd,
                                env=env,
                                stdin=subprocess.PIPE)
    except OSError as e:
        if not quiet:
            print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
            print('warning: Installing it is strongly encouraged.', file=sys.stderr)
            print(file=sys.stderr)
        return False

    proc.stdin.write(MAINTAINER_KEYS.encode())
    proc.stdin.close()

    if proc.wait() != 0:
        print('fatal: registering repo maintainer keys failed', file=sys.stderr)
        sys.exit(1)
    print()

    fd = open(os.path.join(home_dot_repo, 'keyring-version'), 'w')
    fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n')
    fd.close()
    return True


def _SetConfig(local, name, value):
    """Set a git configuration option to the specified value.
    """
    cmd = [GIT, 'config', name, value]
    if subprocess.Popen(cmd, cwd=local).wait() != 0:
        raise CloneFailure()


def _InitHttp():
    handlers = []

    mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
    try:
        import netrc

        n = netrc.netrc()
        for host in n.hosts:
            p = n.hosts[host]
            mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2])
            mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
    except:
        pass
    handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
    handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))

    if 'http_proxy' in os.environ:
        url = os.environ['http_proxy']
        handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url}))
    if 'REPO_CURL_VERBOSE' in os.environ:
        handlers.append(urllib.request.HTTPHandler(debuglevel=1))
        handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
    urllib.request.install_opener(urllib.request.build_opener(*handlers))


def _Fetch(url, local, src, quiet):
    if not quiet:
        print('Get %s' % url, file=sys.stderr)

    cmd = [GIT, 'fetch']
    if quiet:
        cmd.append('--quiet')
        err = subprocess.PIPE
    else:
        err = None
    cmd.append(src)
    cmd.append('+refs/heads/*:refs/remotes/origin/*')
    cmd.append('refs/tags/*:refs/tags/*')

    proc = subprocess.Popen(cmd, cwd=local, stderr=err)
    if err:
        proc.stderr.read()
        proc.stderr.close()
    if proc.wait() != 0:
        raise CloneFailure()


def _DownloadBundle(url, local, quiet):
    if not url.endswith('/'):
        url += '/'
    url += 'clone.bundle'

    proc = subprocess.Popen(
        [GIT, 'config', '--get-regexp', 'url.*.insteadof'],
        cwd=local,
        stdout=subprocess.PIPE)
    for line in proc.stdout:
        m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line)
        if m:
            new_url = m.group(1)
            old_url = m.group(2)
            if url.startswith(old_url):
                url = new_url + url[len(old_url):]
                break
    proc.stdout.close()
    proc.wait()

    if not url.startswith('http:') and not url.startswith('https:'):
        return False

    dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b')
    try:
        try:
            r = urllib.request.urlopen(url)
        except urllib.error.HTTPError as e:
            if e.code in [403, 404]:
                return False
            print('fatal: Cannot get %s' % url, file=sys.stderr)
            print('fatal: HTTP error %s' % e.code, file=sys.stderr)
            raise CloneFailure()
        except urllib.error.URLError as e:
            print('fatal: Cannot get %s' % url, file=sys.stderr)
            print('fatal: error %s' % e.reason, file=sys.stderr)
            raise CloneFailure()
        try:
            if not quiet:
                print('Get %s' % url, file=sys.stderr)
            while True:
                buf = r.read(8192)
                if buf == '':
                    return True
                dest.write(buf)
        finally:
            r.close()
    finally:
        dest.close()


def _ImportBundle(local):
    path = os.path.join(local, '.git', 'clone.bundle')
    try:
        _Fetch(local, local, path, True)
    finally:
        os.remove(path)


def _Clone(url, local, quiet):
    """Clones a git repository to a new subdirectory of repodir
    """
    try:
        os.mkdir(local)
    except OSError as e:
        print('fatal: cannot make %s directory: %s' % (local, e.strerror),
              file=sys.stderr)
        raise CloneFailure()

    cmd = [GIT, 'init', '--quiet']
    try:
        proc = subprocess.Popen(cmd, cwd=local)
    except OSError as e:
        print(file=sys.stderr)
        print("fatal: '%s' is not available" % GIT, file=sys.stderr)
        print('fatal: %s' % e, file=sys.stderr)
        print(file=sys.stderr)
        print('Please make sure %s is installed and in your path.' % GIT,
              file=sys.stderr)
        raise CloneFailure()
    if proc.wait() != 0:
        print('fatal: could not create %s' % local, file=sys.stderr)
        raise CloneFailure()

    _InitHttp()
    _SetConfig(local, 'remote.origin.url', url)
    _SetConfig(local, 'remote.origin.fetch',
               '+refs/heads/*:refs/remotes/origin/*')
    if _DownloadBundle(url, local, quiet):
        _ImportBundle(local)
    else:
        _Fetch(url, local, 'origin', quiet)


def _Verify(cwd, branch, quiet):
    """Verify the branch has been signed by a tag.
    """
    cmd = [GIT, 'describe', 'origin/%s' % branch]
    proc = subprocess.Popen(cmd,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            cwd=cwd)
    cur = stream2str(proc.stdout.read()).strip()
    proc.stdout.close()

    proc.stderr.read()
    proc.stderr.close()

    if proc.wait() != 0 or not cur:
        print(file=sys.stderr)
        print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr)
        raise CloneFailure()

    m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur)
    if m:
        cur = m.group(1)
        if not quiet:
            print(file=sys.stderr)
            print("info: Ignoring branch '%s'; using tagged release '%s'"
                  % (branch, cur), file=sys.stderr)
            print(file=sys.stderr)

    env = os.environ.copy()
    env['GNUPGHOME'] = gpg_dir

    cmd = [GIT, 'tag', '-v', cur]
    proc = subprocess.Popen(cmd,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            cwd=cwd,
                            env=env)
    out = proc.stdout.read()
    proc.stdout.close()

    err = proc.stderr.read()
    proc.stderr.close()

    if proc.wait() != 0:
        print(file=sys.stderr)
        print(out, file=sys.stderr)
        print(err, file=sys.stderr)
        print(file=sys.stderr)
        raise CloneFailure()
    return '%s^0' % cur


def _Checkout(cwd, branch, rev, quiet):
    """Checkout an upstream branch into the repository and track it.
    """
    cmd = [GIT, 'update-ref', 'refs/heads/default', rev]
    if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
        raise CloneFailure()

    _SetConfig(cwd, 'branch.default.remote', 'origin')
    _SetConfig(cwd, 'branch.default.merge', 'refs/heads/%s' % branch)

    cmd = [GIT, 'symbolic-ref', 'HEAD', 'refs/heads/default']
    if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
        raise CloneFailure()

    cmd = [GIT, 'read-tree', '--reset', '-u']
    if not quiet:
        cmd.append('-v')
    cmd.append('HEAD')
    if subprocess.Popen(cmd, cwd=cwd).wait() != 0:
        raise CloneFailure()


def _FindRepo():
    """Look for a repo installation, starting at the current directory.
    """
    curdir = os.getcwd()
    repo = None

    olddir = None
    while curdir != '/' \
        and curdir != olddir \
        and not repo:
        repo = os.path.join(curdir, repodir, REPO_MAIN)
        if not os.path.isfile(repo):
            repo = None
            olddir = curdir
            curdir = os.path.dirname(curdir)
    return (repo, os.path.join(curdir, repodir))


class _Options:
    help = False


def _ParseArguments(args):
    cmd = None
    opt = _Options()
    arg = []

    for i in range(len(args)):
        a = args[i]
        if a == '-h' or a == '--help':
            opt.help = True

        elif not a.startswith('-'):
            cmd = a
            arg = args[i + 1:]
            break
    return cmd, opt, arg


def _Usage():
    print(
        """usage: repo COMMAND [ARGS]

        repo is not yet installed.  Use "repo init" to install it here.

        The most commonly used repo commands are:

          init      Install repo in the current working directory
          help      Display detailed help on a command

        For access to the full online help, install repo ("repo init").
        """, file=sys.stderr)
    sys.exit(1)


def _Help(args):
    if args:
        if args[0] == 'init':
            init_optparse.print_help()
            sys.exit(0)
        else:
            print("error: '%s' is not a bootstrap command.\n"
                  '        For access to online help, install repo ("repo init").'
                  % args[0], file=sys.stderr)
    else:
        _Usage()
    sys.exit(1)


def _NotInstalled():
    print('error: repo is not installed.  Use "repo init" to install it here.',
          file=sys.stderr)
    sys.exit(1)


def _NoCommands(cmd):
    print("""error: command '%s' requires repo to be installed first.
        Use "repo init" to install it here.""" % cmd, file=sys.stderr)
    sys.exit(1)


def _RunSelf(wrapper_path):
    my_dir = os.path.dirname(wrapper_path)
    my_main = os.path.join(my_dir, 'main.py')
    my_git = os.path.join(my_dir, '.git')

    if os.path.isfile(my_main) and os.path.isdir(my_git):
        for name in ['git_config.py',
                     'project.py',
                     'subcmds']:
            if not os.path.exists(os.path.join(my_dir, name)):
                return None, None
        return my_main, my_git
    return None, None


def _SetDefaultsTo(gitdir):
    global REPO_URL
    global REPO_REV

    REPO_URL = gitdir
    proc = subprocess.Popen([GIT,
                             '--git-dir=%s' % gitdir,
                             'symbolic-ref',
                             'HEAD'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    REPO_REV = stream2str(proc.stdout.read()).strip()
    proc.stdout.close()

    proc.stderr.read()
    proc.stderr.close()

    if proc.wait() != 0:
        print('fatal: %s has no current branch' % gitdir, file=sys.stderr)
        sys.exit(1)


def main(orig_args):
    repo_main, rel_repo_dir = _FindRepo()
    cmd, opt, args = _ParseArguments(orig_args)

    wrapper_path = os.path.abspath(__file__)
    my_main, my_git = _RunSelf(wrapper_path)

    if not repo_main:
        if opt.help:
            _Usage()
        if cmd == 'help':
            _Help(args)
        if not cmd:
            _NotInstalled()
        if cmd == 'init':
            if my_git:
                _SetDefaultsTo(my_git)
            try:
                _Init(args)
            except CloneFailure:
                for root, dirs, files in os.walk(repodir, topdown=False):
                    for name in files:
                        removeReadOnlyFiles(os.remove, os.path.join(root, name))
                    for name in dirs:
                        os.rmdir(os.path.join(root, name))
                os.rmdir(repodir)
                sys.exit(1)
            repo_main, rel_repo_dir = _FindRepo()
        else:
            _NoCommands(cmd)

    if my_main:
        repo_main = my_main

    ver_str = '.'.join(map(str, VERSION))
    me = [sys.executable,
          repo_main, repo_main,
          '--repo-dir=%s' % rel_repo_dir,
          '--wrapper-version=%s' % ver_str,
          '--wrapper-path=%s' % wrapper_path,
          '--']
    me.extend(orig_args)
    me.extend(extra_args)
    try:
        sys.exit(subprocess.call(me))
    except OSError as e:
        print("fatal: unable to start %s" % repo_main, file=sys.stderr)
        print("fatal: %s" % e, file=sys.stderr)
        sys.exit(148)


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