#!/usr/bin/python

import os
import re
import sys
import glob
import requests
import argparse
import fnmatch
import zlib
from time import sleep
from json import loads

from .__version__ import (
    __author__,
    __author_email__,
    __copyright__,
    __description__,
    __license__,
    __title__,
    __url__,
    __version__,
)

try:
    from urllib.parse import urlencode
except ImportError:  # pragma: no cover
    from urllib import urlencode

quote = None
if sys.platform == "win32":  # pragma: no cover
    try:
        # https://github.com/python/cpython/blob/3.7/Lib/subprocess.py#L174-L175
        from subprocess import list2cmdline

        def quote(arg):
            return list2cmdline([arg])

    except ImportError:
        pass

if quote is None:
    try:
        from shlex import quote
    except ImportError:  # pragma: no cover
        from pipes import quote

import subprocess

# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
import logging

logging.captureWarnings(True)


version = __version__

COLOR = True

is_merge_commit = re.compile(r"^Merge\s\w{40}\sinto\s\w{40}$")

remove_token = re.compile(r"token=[^\&]+").sub


def sanitize_arg(replacement, arg):
    return re.sub(r"[\&]+", replacement, arg, 0, re.MULTILINE)


ignored_path = re.compile(
    r"(/vendor)|"
    r"(/js/generated/coverage)|"
    r"(/__pycache__)|"
    r"(/coverage/instrumented)|"
    r"(/build/lib)|"
    r"(/htmlcov)|"
    r"(/node_modules)|"
    r"(/\.yarn-cache)|"
    r"(\.egg-info)|"
    r"(/\.git)|"
    r"(/\.hg)|"
    r"(/\.tox)|"
    r"(/\.?v?(irtual)?envs?)",
    re.I,
).search

ignored_report = re.compile(
    ".*("
    r"(/\.coverage.*)|"
    r"(\.coveragerc)|"
    r"(\.egg)|"
    r"(\.gif)|"
    r"(\.ini)|"
    r"(\.less)|"
    r"(\.jpeg)|"
    r"(\.jpg)|"
    r"(\.md)|"
    r"(\.png)|"
    r"(\.p?sql)|"
    r"(\.whl)|"
    r"(\.cpp)|"
    r"(\.pyc?)|"
    r"(\.cfg)|"
    r"(\.class)|"
    r"(\.js)|"
    r"(\.html)|"
    r"(\.sh)|"
    r"(\.tar\.gz)|"
    r"(\.yml)|"
    r"(\.xcconfig)|"
    r"(\.data)|"
    r"(coverage\.db)|"
    r"(\.?codecov\.yml)|"
    r"(coverage\.jade)|"
    r"(include\.lst)|"
    r"(inputFiles\.lst)|"
    r"(createdFiles\.lst)|"
    r"(scoverage\.measurements\..*)|"
    r"(test_.*_coverage\.txt)|"
    r"(conftest_.*\.c\.gcov)"
    ")$",
    re.I,
).match

is_report = re.compile(
    ".*("
    r"([^/]*coverage[^/]*)|"
    r"(\.gcov)|"
    r"(\.lcov)|"
    r"(\.lst)|"
    r"(clover\.xml)|"
    r"(cobertura\.xml)|"
    r"(coverage-final\.json)|"
    r"(coverage-summary\.json)|"
    r"(gcov\.info)|"
    r"(([^/]*\.)?codecov\.[^/]*)|"
    r"(jacoco[^/]*\.xml)|"
    r"(lcov\.info)|"
    r"(luacov\.report\.out)|"
    r"(nosetests\.xml)|"
    r"(report\.xml)"
    ")$",
    re.I,
).match

opj = os.path.join  # for faster access


def write(text, color=None):
    global COLOR
    if text and COLOR:
        text = text.replace("==>", "\033[90m==>\033[0m")
        text = text.replace("    +", "    \033[32m+\033[0m")
        text = text.replace("XX>", "\033[31mXX>\033[0m")
        if text[:6] == "Error:":
            text = "\033[41mError:\033[0m\033[91m%s\033[0m" % text[6:]
        elif text[:4] == "Tip:":
            text = "\033[42mTip:\033[0m\033[32m%s\033[0m" % text[4:]
        elif text.strip()[:4] == "http":
            text = "\033[92m%s\033[0m" % text
        elif text[:7] == "Codecov":
            text = (
                """
      _____          _
     / ____|        | |
    | |     ___   __| | ___  ___ _____   __
    | |    / _ \ / _  |/ _ \/ __/ _ \ \ / /
    | |___| (_) | (_| |  __/ (_| (_) \ V /
     \_____\___/ \____|\___|\___\___/ \_/
                                    %s\n"""
                % text.split(" ")[1]
            )
        elif color == "red":
            text = "\033[91m%s\033[0m" % text
        elif color == "green":
            text = "\033[92m%s\033[0m" % text

    if text:
        sys.stdout.write(text + "\n")


def fopen(path):
    try:
        if sys.version_info < (3, 0):
            with open(path, "r") as f:
                return f.read()
        else:
            try:
                with open(path, "r", encoding="utf-8") as f:
                    return f.read()
            except UnicodeDecodeError:
                with open(path, "r", encoding="ISO-8859-1") as f:
                    return f.read()
    except Exception as e:
        # on none of that works. just print the issue and continue
        write("    - Ignored: " + str(e))


def read(filepath):
    try:
        report = fopen(filepath)
        if report is None:
            return
        write("    + %s bytes=%d" % (filepath, os.path.getsize(filepath)))
        return "# path=" + filepath + "\n" + report
    except Exception as e:
        # Ex: No such file or directory, skip them
        write("    - Ignored: " + str(e))


def check_output(cmd, **popen_args):
    from subprocess import Popen, PIPE, CalledProcessError

    process = Popen(cmd, stdout=PIPE, **popen_args)
    output, _ = process.communicate()
    if process.returncode:
        raise CalledProcessError(process.returncode, cmd)
    else:
        assert process.returncode == 0
        return output.decode("utf-8")


def try_to_run(cmd, shell=False, cwd=None):
    try:
        return check_output(cmd, shell=shell, cwd=cwd)
    except Exception as e:
        write("    Error running `%s`: %s" % (cmd, e or str(e)))
        return None


def run_python_coverage(args):
    """Run the Python coverage tool

    If it's importable in this Python, launch it using 'python -m'.
    Otherwise, look it up on PATH like any other command.
    """
    try:
        import coverage
    except ImportError:
        # Coverage is not installed on this Python. Hope it's on PATH.
        try_to_run(["coverage"] + args, shell=False)
    else:
        # Coverage is installed on this Python. Run it as a module.
        try_to_run([sys.executable, "-m", "coverage"] + args, shell=False)


def remove_non_ascii(data):
    try:
        return data.decode("utf8") + ""
    except:
        return "".join([i if ord(i) < 128 else "" for i in data])


def _add_env_if_not_empty(lst, value):
    if os.getenv(value) is not None:
        lst.add(value)


def find_files(directory, patterns, recursive=True, exclude_dirs=[]):
    if recursive:
        items = os.walk(directory, followlinks=False)
    else:
        items = [next(os.walk(directory, followLinks=False))]
    if not isinstance(patterns, list):
        patterns = [patterns]
    for root, dirs, files in items:
        dirs[:] = [d for d in dirs if d not in exclude_dirs]
        for basename in files:
            match = False
            for pattern in patterns:
                if fnmatch.fnmatch(basename, pattern):
                    match = True
                    break
            if match:
                filename = os.path.join(root, basename)
                yield filename


def generate_toc(root):
    res = (
        try_to_run(["git", "ls-files"], cwd=root)
        or try_to_run(["git", "ls-files"])
        or try_to_run(["hg", "locate"], cwd=root)
        or try_to_run(["hg", "locate"])
    )
    if res is None:
        return ""
    return str(res).strip() or ""


def retry_upload(url, request_method, retries=3, break_codes=(200,), **kwargs):
    for _ in range(retries):
        res = request_method(url, **kwargs)
        if res.status_code in break_codes:
            return res
    return res


def main(*argv, **kwargs):
    root = os.getcwd()

    # Build Parser
    # ------------
    parser = argparse.ArgumentParser(
        prog="codecov",
        add_help=True,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""Upload reports to Codecov""",
    )
    basics = parser.add_argument_group(
        "======================== Basics ========================"
    )
    basics.add_argument(
        "--version",
        action="version",
        version="Codecov py-v" + version + " - https://codecov.io/",
    )
    basics.add_argument(
        "--token",
        "-t",
        default=os.getenv("CODECOV_TOKEN"),
        help="Private repository token or @filename for file containing the token. Defaults to $CODECOV_TOKEN. Not required for public repositories on Travis CI, CircleCI and AppVeyor",
    )
    basics.add_argument(
        "--file",
        "-f",
        nargs="*",
        default=None,
        help="Target a specific file for uploading",
    )
    basics.add_argument(
        "--flags",
        "-F",
        nargs="*",
        default=None,
        help="Flag these uploaded files with custom labels",
    )
    basics.add_argument(
        "--env",
        "-e",
        nargs="*",
        default=None,
        help="Store environment variables to help distinguish CI builds.",
    )
    basics.add_argument(
        "--required",
        action="store_true",
        default=False,
        help="If Codecov fails it will exit 1 - possibly failing the CI build.",
    )
    basics.add_argument(
        "--name",
        "-n",
        default=os.getenv("CODECOV_NAME"),
        help="Custom defined name of the upload. Visible in Codecov UI. Defaults to $CODECOV_NAME.",
    )

    gcov = parser.add_argument_group(
        "======================== gcov ========================"
    )
    gcov.add_argument(
        "--gcov-root", default=None, help="Project root directory when preparing gcov"
    )
    gcov.add_argument(
        "--gcov-glob",
        nargs="*",
        default=[],
        help="Paths to ignore during gcov gathering",
    )
    gcov.add_argument(
        "--gcov-exec", default="gcov", help="gcov executable to run. Defaults to 'gcov'"
    )
    gcov.add_argument("--gcov-args", default="", help="extra arguments to pass to gcov")

    advanced = parser.add_argument_group(
        "======================== Advanced ========================"
    )
    advanced.add_argument(
        "-X",
        "--disable",
        nargs="*",
        default=[],
        help="Disable features. Accepting **search** to disable crawling through directories, **detect** to disable detecting CI provider, **gcov** disable gcov commands, `pycov` disables running python `coverage xml`, **fix** to disable report adjustments https://docs.codecov.io/docs/fixing-reports",
    )
    advanced.add_argument(
        "--root",
        default=None,
        help="Project directory. Default: current direcory or provided in CI environment variables",
    )
    advanced.add_argument(
        "--commit", "-c", default=None, help="Commit SHA, set automatically"
    )
    advanced.add_argument(
        "--prefix",
        "-P",
        default=None,
        help="Prefix network paths to help resolve paths: https://github.com/codecov/support/issues/472",
    )
    advanced.add_argument("--branch", "-b", default=None, help="Branch name")
    advanced.add_argument(
        "--build",
        default=None,
        help="Specify a custom build number to distinguish CI jobs, provided automatically for supported CI companies",
    )
    advanced.add_argument(
        "--pr",
        default=None,
        help="Specify a custom pr number, provided automatically for supported CI companies",
    )
    advanced.add_argument("--tag", default=None, help="Git tag")
    advanced.add_argument(
        "--tries",
        default=3,
        type=int,
        help="Specify the total number of attempts to make when uploading coverage report",
    )

    enterprise = parser.add_argument_group(
        "======================== Enterprise ========================"
    )
    enterprise.add_argument(
        "--slug",
        "-r",
        default=os.getenv("CODECOV_SLUG"),
        help="Specify repository slug for Enterprise ex. owner/repo",
    )
    enterprise.add_argument(
        "--url",
        "-u",
        default=os.getenv("CODECOV_URL", "https://codecov.io"),
        help="Your Codecov endpoint",
    )
    enterprise.add_argument(
        "--cacert",
        default=os.getenv("CODECOV_CACERT", os.getenv("CURL_CA_BUNDLE")),
        help="Certificate pem bundle used to verify with your Codecov instance",
    )

    debugging = parser.add_argument_group(
        "======================== Debugging ========================"
    )
    debugging.add_argument(
        "--dump",
        action="store_true",
        help="Dump collected data and do not send to Codecov",
    )
    debugging.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Be verbose, e.g. dump the collected data",
    )
    debugging.add_argument(
        "--no-color", action="store_true", help="Do not output with color"
    )

    # Parse Arguments
    # ---------------
    if argv:
        codecov = parser.parse_args(argv)
    else:
        codecov = parser.parse_args()

    global COLOR
    COLOR = not codecov.no_color

    include_env = set()

    # add from cli
    if codecov.env:
        # -e VAR1,VAR2 or -e VAR1 -e VAR2
        for env in codecov.env:
            for e in env.split(","):
                include_env.add(e.strip())

    # add from env
    if os.getenv("CODECOV_ENV"):
        for env in os.getenv("CODECOV_ENV").split(","):
            include_env.add(env.strip())

    write("Codecov v" + version)
    query = dict(commit="", branch="", job="", pr="", build_url="", token=codecov.token)
    language = None

    if os.getenv("TOXENV"):
        _add_env_if_not_empty(include_env, "TOXENV")

    # Detect CI
    # ---------
    if "detect" in codecov.disable:
        write("XX> Detecting CI provider disabled.")

    else:
        write("==> Detecting CI provider")
        # -------
        # Jenkins
        # -------
        if os.getenv("JENKINS_URL"):
            # https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project
            # https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin#GitHubpullrequestbuilderplugin-EnvironmentVariables
            query.update(
                dict(
                    branch=os.getenv("ghprbSourceBranch")
                    or os.getenv("GIT_BRANCH")
                    or os.getenv("BRANCH_NAME"),
                    service="jenkins",
                    commit=os.getenv("ghprbActualCommit") or os.getenv("GIT_COMMIT"),
                    pr=os.getenv("ghprbPullId") or os.getenv("CHANGE_ID"),
                    build=os.getenv("BUILD_NUMBER"),
                    build_url=os.getenv("BUILD_URL"),
                )
            )
            root = os.getenv("WORKSPACE") or root
            write("    Jenkins Detected")

        # ---------
        # Travis CI
        # ---------
        elif (
            os.getenv("CI") == "true"
            and os.getenv("TRAVIS") == "true"
            and os.getenv("SHIPPABLE") != "true"
        ):
            # http://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
            query.update(
                dict(
                    branch=os.getenv("TRAVIS_BRANCH"),
                    service="travis",
                    build=os.getenv("TRAVIS_JOB_NUMBER"),
                    pr=os.getenv("TRAVIS_PULL_REQUEST"),
                    job=os.getenv("TRAVIS_JOB_ID"),
                    tag=os.getenv("TRAVIS_TAG"),
                    slug=os.getenv("TRAVIS_REPO_SLUG"),
                    commit=os.getenv("TRAVIS_COMMIT"),
                )
            )
            root = os.getenv("TRAVIS_BUILD_DIR") or root
            write("    Travis Detected")
            language = (
                list(
                    filter(
                        lambda l: os.getenv("TRAVIS_%s_VERSION" % l.upper()),
                        (
                            "dart",
                            "go",
                            "haxe",
                            "jdk",
                            "julia",
                            "node",
                            "otp",
                            "xcode",
                            "perl",
                            "php",
                            "python",
                            "r",
                            "ruby",
                            "rust",
                            "scala",
                        ),
                    )
                )
                + [""]
            )[0]

            _add_env_if_not_empty(include_env, "TRAVIS_OS_NAME")
            if language:
                _add_env_if_not_empty(
                    include_env, "TRAVIS_%s_VERSION" % language.upper()
                )

        # --------
        # Codeship
        # --------
        elif os.getenv("CI") == "true" and os.getenv("CI_NAME") == "codeship":
            # https://www.codeship.io/documentation/continuous-integration/set-environment-variables/
            query.update(
                dict(
                    branch=os.getenv("CI_BRANCH"),
                    service="codeship",
                    build=os.getenv("CI_BUILD_NUMBER"),
                    build_url=os.getenv("CI_BUILD_URL"),
                    commit=os.getenv("CI_COMMIT_ID"),
                )
            )
            write("    Codeship Detected")

        # ---------
        # Buildkite
        # ---------
        elif os.getenv("CI") == "true" and os.getenv("BUILDKITE") == "true":
            # https://buildkite.com/docs/guides/environment-variables
            query.update(
                dict(
                    branch=os.getenv("BUILDKITE_BRANCH"),
                    service="buildkite",
                    build=os.getenv("BUILDKITE_BUILD_NUMBER")
                    + "."
                    + os.getenv("BUILDKITE_JOB_ID"),
                    slug=os.getenv("BUILDKITE_PROJECT_SLUG"),
                    build_url=os.getenv("BUILDKITE_BUILD_URL"),
                    commit=os.getenv("BUILDKITE_COMMIT"),
                )
            )
            write("    Buildkite Detected")

        # ---------
        # Circle CI
        # ---------
        elif os.getenv("CI") == "true" and os.getenv("CIRCLECI") == "true":
            # https://circleci.com/docs/environment-variables
            query.update(
                dict(
                    branch=os.getenv("CIRCLE_BRANCH"),
                    service="circleci",
                    build=os.getenv("CIRCLE_BUILD_NUM")
                    + "."
                    + os.getenv("CIRCLE_NODE_INDEX"),
                    job=os.getenv("CIRCLE_BUILD_NUM")
                    + "."
                    + os.getenv("CIRCLE_NODE_INDEX"),
                    pr=os.getenv("CIRCLE_PR_NUMBER"),
                    slug=os.getenv("CIRCLE_PROJECT_USERNAME")
                    + "/"
                    + os.getenv("CIRCLE_PROJECT_REPONAME"),
                    commit=os.getenv("CIRCLE_SHA1"),
                )
            )
            write("    Circle CI Detected")

        # ---------
        # Semaphore
        # ---------
        elif os.getenv("CI") == "true" and os.getenv("SEMAPHORE") == "true":
            # https://semaphoreapp.com/docs/available-environment-variables.html
            query.update(
                dict(
                    branch=os.getenv("BRANCH_NAME"),
                    service="semaphore",
                    build=os.getenv("SEMAPHORE_BUILD_NUMBER")
                    + "."
                    + os.getenv("SEMAPHORE_CURRENT_THREAD"),
                    slug=os.getenv("SEMAPHORE_REPO_SLUG"),
                    commit=os.getenv("REVISION"),
                )
            )
            write("    Semaphore Detected")

        # ----------
        # Greenhouse
        # ----------
        elif os.getenv("GREENHOUSE") == "true":
            # http://docs.greenhouseci.com/docs/environment-variables-files
            query.update(
                dict(
                    branch=os.getenv("GREENHOUSE_BRANCH"),
                    service="greenhouse",
                    build=os.getenv("GREENHOUSE_BUILD_NUMBER"),
                    build_url=os.getenv("GREENHOUSE_BUILD_URL"),
                    pr=os.getenv("GREENHOUSE_PULL_REQUEST"),
                    commit=os.getenv("GREENHOUSE_COMMIT"),
                )
            )
            write("    Greenhouse Detected")

        # --------
        # drone.io
        # --------
        elif os.getenv("CI") == "drone" and os.getenv("DRONE") == "true":
            # http://docs.drone.io/env.html
            query.update(
                dict(
                    branch=os.getenv("DRONE_BRANCH"),
                    service="drone.io",
                    build=os.getenv("DRONE_BUILD_NUMBER"),
                    build_url=os.getenv("DRONE_BUILD_LINK"),
                )
            )
            root = os.getenv("DRONE_BUILD_DIR") or root
            write("    Drone Detected")

        # --------
        # TeamCity
        # --------
        elif os.getenv("TEAMCITY_VERSION"):
            # https://confluence.jetbrains.com/plugins/servlet/mobile#content/view/74847298
            query.update(
                dict(
                    service="teamcity",
                    build=os.getenv("BUILD_NUMBER"),
                    commit=os.getenv("BUILD_VCS_NUMBER"),
                )
            )
            write("    TeamCity CI Detected")

        # --------
        # AppVeyor
        # --------
        elif (
            os.getenv("CI", "false").lower() == "true"
            and os.getenv("APPVEYOR", "false").lower() == "true"
        ):
            # http://www.appveyor.com/docs/environment-variables
            query.update(
                dict(
                    branch=os.getenv("APPVEYOR_REPO_BRANCH"),
                    service="appveyor",
                    job="/".join(
                        (
                            os.getenv("APPVEYOR_ACCOUNT_NAME"),
                            os.getenv("APPVEYOR_PROJECT_SLUG"),
                            os.getenv("APPVEYOR_BUILD_VERSION"),
                        )
                    ),
                    build=os.getenv("APPVEYOR_JOB_ID"),
                    pr=os.getenv("APPVEYOR_PULL_REQUEST_NUMBER"),
                    slug=os.getenv("APPVEYOR_REPO_NAME"),
                    commit=os.getenv("APPVEYOR_REPO_COMMIT"),
                )
            )
            write("    AppVeyor Detected")
            codecov.disable.append("search")

        # -------
        # Wercker
        # -------
        elif os.getenv("CI") == "true" and os.getenv("WERCKER_GIT_BRANCH"):
            # http://devcenter.wercker.com/articles/steps/variables.html
            query.update(
                dict(
                    branch=os.getenv("WERCKER_GIT_BRANCH"),
                    service="wercker",
                    build=os.getenv("WERCKER_MAIN_PIPELINE_STARTED"),
                    slug=os.getenv("WERCKER_GIT_OWNER")
                    + "/"
                    + os.getenv("WERCKER_GIT_REPOSITORY"),
                    commit=os.getenv("WERCKER_GIT_COMMIT"),
                )
            )
            write("    Wercker Detected")

        # ------
        # Magnum
        # ------
        elif os.getenv("CI") == "true" and os.getenv("MAGNUM") == "true":
            # https://magnum-ci.com/docs/environment
            query.update(
                dict(
                    service="magnum",
                    branch=os.getenv("CI_BRANCH"),
                    build=os.getenv("CI_BUILD_NUMBER"),
                    commit=os.getenv("CI_COMMIT"),
                )
            )
            write("    Magnum Detected")

        # ---------
        # Shippable
        # ---------
        elif os.getenv("SHIPPABLE") == "true":
            # http://docs.shippable.com/en/latest/config.html#common-environment-variables
            query.update(
                dict(
                    branch=os.getenv("BRANCH"),
                    service="shippable",
                    build=os.getenv("BUILD_NUMBER"),
                    build_url=os.getenv("BUILD_URL"),
                    pr=os.getenv("PULL_REQUEST"),
                    slug=os.getenv("REPO_NAME"),
                    commit=os.getenv("COMMIT"),
                )
            )
            write("    Shippable Detected")

        # ---------
        # Gitlab CI
        # ---------
        elif os.getenv("CI_SERVER_NAME", "").startswith("GitLab"):
            # https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
            # https://gitlab.com/gitlab-org/gitlab-ci-runner/blob/master/lib/build.rb
            query.update(
                dict(
                    service="gitlab",
                    branch=os.getenv(
                        "CI_COMMIT_REF_NAME", os.getenv("CI_BUILD_REF_NAME")
                    ),
                    build=os.getenv("CI_JOB_ID", os.getenv("CI_BUILD_ID")),
                    commit=os.getenv("CI_COMMIT_SHA", os.getenv("CI_BUILD_REF")),
                )
            )
            if sys.platform == "win32" or os.getenv("CI_PROJECT_DIR", "").startswith(
                "/"
            ):
                root = os.getenv("CI_PROJECT_DIR")
            else:
                root = os.getenv("HOME") + "/" + os.getenv("CI_PROJECT_DIR", "")

            if os.getenv("CI_BUILD_REPO"):
                query["slug"] = (
                    os.getenv("CI_BUILD_REPO").split("/", 3)[-1].replace(".git", "")
                )
            elif os.getenv("CI_REPOSITORY_URL"):
                query["slug"] = (
                    os.getenv("CI_REPOSITORY_URL").split("/", 3)[-1].replace(".git", "")
                )

            write("    Gitlab CI Detected")

        # --------------
        # GitHub Actions
        # --------------
        elif os.getenv("GITHUB_ACTION"):
            # https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables
            query.update(
                dict(
                    service="github-actions",
                    build=os.getenv("GITHUB_RUN_ID"),
                    commit=os.getenv("GITHUB_SHA"),
                    slug=os.getenv("GITHUB_REPOSITORY"),
                    build_url="http://github.com/"
                    + os.getenv("GITHUB_REPOSITORY")
                    + "/actions/runs/"
                    + os.getenv("GITHUB_RUN_ID"),
                )
            )

            if os.getenv("GITHUB_REF"):
                query["branch"] = os.getenv("GITHUB_REF").split("/", 3)[-1]
            if os.getenv("GITHUB_HEAD_REF"):
                # PR refs are in the format: refs/pull/7/merge
                query["pr"] = os.getenv("GITHUB_REF").split("/")[-2]
                query["branch"] = os.getenv("GITHUB_HEAD_REF")

            write("    GitHub Actions CI Detected")

        else:
            query.update(
                dict(
                    commit=os.getenv("VCS_COMMIT_ID", ""),
                    branch=os.getenv("VCS_BRANCH_NAME", ""),
                    pr=os.getenv("VCS_PULL_REQUEST", ""),
                    slug=os.getenv("VCS_SLUG", ""),
                    build_url=os.getenv("CI_BUILD_URL", ""),
                    build=os.getenv("CI_BUILD_ID", ""),
                )
            )

        # ------
        # git/hg
        # ------
        if not query.get("branch"):
            try:
                # find branch, commit, repo from git command
                branch = try_to_run(
                    ["git", "rev-parse", "--abbrev-ref", "HEAD"]
                ) or try_to_run(["hg", "branch"])
                query["branch"] = branch if branch != "HEAD" else ""
                write("  -> Got branch from git/hg")

            except:
                write("  x> Failed to get branch from git/hg")

        if not query.get("commit"):
            try:
                query["commit"] = try_to_run(
                    ["git", "rev-parse", "HEAD"]
                ) or try_to_run(["hg", "id", "-i", "--debug", "|", "tr", "-d", "'+'"])
                write("  -> Got sha from git/hg")

            except:  # pragma: no cover
                write("  x> Failed to get sha from git/hg")

    # Update Query
    # ------------
    if codecov.name:
        query["name"] = codecov.name

    if codecov.flags:
        query["flags"] = ",".join(codecov.flags)

    if codecov.build:
        query["build"] = codecov.build

    if codecov.pr:
        query["pr"] = codecov.pr

    if codecov.commit:
        query["commit"] = codecov.commit

    elif query["pr"] and query["pr"] != "false":
        # Merge Commits
        # -------------
        res = try_to_run(["git", "log", "-1", "--pretty=%B"])
        if res and is_merge_commit.match(res.strip()):
            query["commit"] = res.split(" ")[1]
            write("    Fixing merge commit SHA")

    if codecov.slug:
        query["slug"] = codecov.slug

    if codecov.branch:
        query["branch"] = codecov.branch

    if codecov.tag:
        query["tag"] = codecov.tag

    if codecov.root:
        root = codecov.root

    root = quote(root)

    # Upload
    # ------
    try:
        write("==> Preparing upload")

        # Read token from file
        # --------------------
        if query.get("token") and query.get("token")[0] == "@":
            write("    Reading token from file")
            query["token"] = fopen(opj(os.getcwd(), query["token"][1:])).strip()

        assert query.get("commit") not in (
            "",
            None,
        ), "Commit sha is missing. Please specify via --commit=:sha"

        # Build TOC
        # ---------
        toc = generate_toc(root)

        if codecov.prefix:
            prefix = codecov.prefix.strip("/")
            toc = "{}/{}".format(prefix, toc.replace("\n", "\n{}/".format(prefix)))

        # Detect codecov.yml location
        yaml_location = re.search(r"\.?codecov\.ya?ml$", toc, re.M)
        if yaml_location:
            yaml_location = yaml_location.group()
            yaml_path = opj(root, yaml_location)
            if os.path.exists(yaml_path):
                query["yaml"] = yaml_location
                yaml = fopen(yaml_path)
                _token = re.search(
                    r"token: (\'|\")?([0-9a-f]{8}(-?[0-9a-f]{4}){3}-?[0-9a-f]{12})",
                    yaml,
                    re.M,
                )
                if _token:
                    query["token"] = _token.groups()[1]

                _slug = re.search(
                    r"slug: (\'|\")?([\w\-\.\+]+\/[\w\-\.\+]+)", yaml, re.M
                )
                if _slug:
                    query["slug"] = _slug.groups()[1]

        assert query.get("job") or query.get("token"), "Missing repository upload token"

        # Processing gcov
        # ---------------
        if "gcov" in codecov.disable:
            write("XX> Skip processing gcov")

        else:
            dont_search_here = ["bower_components" "node_modules" "vendor"]
            if codecov.gcov_glob:
                dont_search_here.append(codecov.gcov_glob)

            write("==> Processing gcov (disable by -X gcov)")
            for path in find_files(
                sanitize_arg("", codecov.gcov_root or root),
                "*.gcno",
                True,
                dont_search_here,
            ):
                cmd = sanitize_arg("", codecov.gcov_exec or "").split(" ")
                cmd.append("-pb")
                if codecov.gcov_args:
                    cmd.append(sanitize_arg("", codecov.gcov_args or ""))
                cmd.append(path)
                write("    Executing gcov (%s)" % cmd)
                write(try_to_run(cmd))

        # Collect Reports
        # ---------------
        write("==> Collecting reports")
        reports = []

        if "search" in codecov.disable:
            write("XX> Searching for reports disabled")
        else:

            # Detect .bowerrc
            # ---------------
            bower_components = "/bower_components"
            bowerrc = opj(root, ".bowerrc")
            if os.path.exists(bowerrc):
                write("    Detecting .bowerrc file")
                try:
                    bower_components = "/" + (
                        loads(fopen(bowerrc)).get("directory") or "bower_components"
                    ).replace("./", "").strip("/")
                    write("    .bowerrc detected, ignoring " + bower_components)
                except Exception as e:
                    write("    .bowerrc parsing error: " + str(e))

            # Find reports
            # ------------
            for _root, dirs, files in os.walk(root):
                # need to replace('\\', '/') for Windows
                if not ignored_path(
                    _root.replace("\\", "/")
                ) and bower_components not in _root.replace("\\", "/"):
                    # add data to tboc
                    for filepath in files:
                        fullpath = opj(_root, filepath)
                        if (
                            not codecov.file
                            and is_report(fullpath.replace("\\", "/"))
                            and not ignored_report(fullpath.replace("\\", "/"))
                        ):
                            # found report
                            reports.append(read(fullpath))

        # Read Reports
        # ------------
        if codecov.file:
            write("    Targeting specific files")
            reports.extend(filter(bool, map(read, codecov.file)))

        elif "pycov" not in codecov.disable:
            # Call `coverage xml` when .coverage exists
            # -----------------------------------------
            # Ran from current directory
            if glob.glob(opj(os.getcwd(), ".coverage.*")):
                write("    Merging coverage reports")
                # The `-a` option is mandatory here. If we
                # have a `.coverage` in the current directory, calling
                # without the option would delete the previous data
                run_python_coverage(["combine", "-a"])

            if os.path.exists(opj(os.getcwd(), ".coverage")) and not os.path.exists(
                opj(os.getcwd(), "coverage.xml")
            ):
                write("    Generating coverage xml reports for Python")
                # using `-i` to ignore "No source for code" error
                run_python_coverage(["xml", "-i"])
                reports.append(read(opj(os.getcwd(), "coverage.xml")))

        reports = list(filter(bool, reports))
        assert len(reports) > 0, "No coverage report found"

        # Storing Environment
        # -------------------
        env = ""
        if include_env:
            write("==> Appending environment variables")
            for k in include_env:
                if k:
                    write("    + " + k)

            env = (
                "\n".join(["%s=%s" % (k, os.getenv(k, "")) for k in include_env if k])
                + "\n<<<<<< ENV"
            )

        # join reports together
        reports = "\n".join(
            (
                env,
                (toc or ""),
                "<<<<<< network",
                "\n<<<<<< EOF\n".join(reports),
                "<<<<<< EOF",
            )
        )

        query["package"] = "py" + version
        urlargs = (
            urlencode(
                dict([(k, v.strip()) for k, v in query.items() if v not in ("", None)])
            )
        ).replace("+", "%20")

        result = ""
        if codecov.dump:
            write("-------------------- Debug --------------------")
            write("    .url " + codecov.url)
            write("    .query " + remove_token("token=<secret>", urlargs))
            write(reports)
            write("--------------------  EOF  --------------------")
        else:
            write("==> Uploading")
            write("    .url " + codecov.url)
            write("    .query " + remove_token("token=<secret>", urlargs))
            if codecov.verbose:
                write("-------------------- Reports --------------------")
                write(reports)
                write("-------------------------------------------------")

            # Handle reports encoding for Python 2 and 3
            if not isinstance(reports, bytes):
                reports = reports.encode("utf-8")

            # Compress reports using zlib and output with gzip header
            write("    Gzipping contents..")
            gzip_worker = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16)
            reports_gzip = gzip_worker.compress(reports) + gzip_worker.flush()
            write("    Compressed contents to {0} bytes".format(len(reports_gzip)))

            success = False
            if "s3" not in codecov.disable:
                try:
                    write("    Pinging Codecov...")
                    res = retry_upload(
                        "%s/upload/v4?%s" % (codecov.url, urlargs),
                        requests.post,
                        retries=codecov.tries,
                        break_codes=(200, 400, 406),
                        verify=codecov.cacert,
                        headers={
                            "Accept": "text/plain",
                            "X-Reduced-Redundancy": "false",
                            "X-Content-Type": "application/x-gzip",
                        },
                    )
                    if res.status_code in (400, 406):
                        raise Exception(res.text)

                    elif res.status_code < 500:
                        assert res.status_code == 200
                        res = res.text.strip().split()
                        result, upload_url = res[0], res[1]

                        write("    Uploading to S3...")
                        s3 = retry_upload(
                            upload_url,
                            requests.put,
                            retries=codecov.tries,
                            verify=codecov.cacert,
                            data=reports_gzip,
                            headers={
                                "Content-Type": "application/x-gzip",
                                "Content-Encoding": "gzip",
                            },
                        )
                        s3.raise_for_status()
                        assert s3.status_code == 200
                        write("    " + result)
                        success = True

                except AssertionError:
                    write("    Direct to s3 failed. Using backup v2 endpoint.")

                # just incase, try traditional upload
                if not success:
                    write("    Uploading to Codecov...")
                    res = retry_upload(
                        "%s/upload/v2?%s" % (codecov.url, urlargs),
                        requests.post,
                        retries=codecov.tries,
                        verify=codecov.cacert,
                        data=reports_gzip,
                        headers={
                            "Accept": "text/plain",
                            "Content-Type": "application/x-gzip",
                            "Content-Encoding": "gzip",
                        },
                    )
                    if res.status_code < 500:
                        write("    " + res.text)
                        res.raise_for_status()
                        result = res.text
                        return

    except Exception as e:
        write("Error: " + str(e))
        if kwargs.get("debug"):
            raise

        write("")
        # detect language
        if language:
            write(
                "Tip: See an example %s repo: https://github.com/codecov/example-%s"
                % (language, language)
            )
        else:
            write(
                "Tip: See all example repositories: https://github.com/codecov?query=example"
            )

        write("Support channels:", "green")
        write(
            "  Email:   hello@codecov.io\n"
            "  IRC:     #codecov\n"
            "  Gitter:  https://gitter.im/codecov/support\n"
            "  Twitter: @codecov\n"
        )
        sys.exit(1 if codecov.required else 0)

    else:
        if kwargs.get("debug"):
            return dict(
                reports=reports,
                codecov=codecov,
                query=query,
                urlargs=urlargs,
                result=result,
            )


if __name__ == "__main__":
    main()