# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import argparse
import os
import re
import shutil

from run_operations import run_git, run_hg, run_shell

# This script saves the mozilla patch stack and no-op commit tracking
# files.  This makes our fast-forward process much more resilient by
# saving the intermediate state after each upstream commit is processed.


def save_patch_stack(
    github_path,
    github_branch,
    patch_directory,
    state_directory,
    target_branch_head,
    bug_number,
):
    # remove the current patch files
    files_to_remove = os.listdir(patch_directory)
    for file in files_to_remove:
        os.remove(os.path.join(patch_directory, file))

    # find the base of the patch stack
    cmd = "git merge-base {} {}".format(github_branch, target_branch_head)
    stdout_lines = run_git(cmd, github_path)
    merge_base = stdout_lines[0]

    # grab patch stack
    cmd = "git format-patch --keep-subject --output-directory {} {}..{}".format(
        patch_directory, merge_base, github_branch
    )
    run_git(cmd, github_path)

    # remove the commit summary from the file name
    patches_to_rename = os.listdir(patch_directory)
    for file in patches_to_rename:
        shortened_name = re.sub("^(\d\d\d\d)-.*\.patch", "\\1.patch", file)
        os.rename(
            os.path.join(patch_directory, file),
            os.path.join(patch_directory, shortened_name),
        )

    # remove the unhelpful first line of the patch files that only
    # causes diff churn.  For reasons why we can't skip creating backup
    # files during the in-place editing, see:
    # https://stackoverflow.com/questions/5694228/sed-in-place-flag-that-works-both-on-mac-bsd-and-linux
    run_shell("sed -i'.bak' -e '1d' {}/*.patch".format(patch_directory))
    run_shell("rm {}/*.patch.bak".format(patch_directory))

    # it is also helpful to save the no-op-cherry-pick-msg files from
    # the state directory so that if we're restoring a patch-stack we
    # also restore the possibly consumed no-op tracking files.
    no_op_files = [
        path
        for path in os.listdir(state_directory)
        if re.findall(".*no-op-cherry-pick-msg$", path)
    ]
    for file in no_op_files:
        shutil.copy(os.path.join(state_directory, file), patch_directory)

    # get missing files (that should be marked removed)
    cmd = "hg status --no-status --deleted {}".format(patch_directory)
    stdout_lines = run_hg(cmd)
    if len(stdout_lines) != 0:
        cmd = "hg rm {}".format(" ".join(stdout_lines))
        run_hg(cmd)

    # get unknown files (that should be marked added)
    cmd = "hg status --no-status --unknown {}".format(patch_directory)
    stdout_lines = run_hg(cmd)
    if len(stdout_lines) != 0:
        cmd = "hg add {}".format(" ".join(stdout_lines))
        run_hg(cmd)

    # if any files are marked for add/remove/modify, commit them
    cmd = "hg status --added --removed --modified {}".format(patch_directory)
    stdout_lines = run_hg(cmd)
    if (len(stdout_lines)) != 0:
        print("Updating {} files in {}".format(len(stdout_lines), patch_directory))
        if bug_number is None:
            run_hg("hg amend")
        else:
            run_shell(
                "hg commit --message 'Bug {} - updated libwebrtc patch stack'".format(
                    bug_number
                )
            )


if __name__ == "__main__":
    default_patch_dir = "third_party/libwebrtc/moz-patch-stack"
    default_state_dir = ".moz-fast-forward"

    parser = argparse.ArgumentParser(
        description="Save moz-libwebrtc github patch stack"
    )
    parser.add_argument(
        "--repo-path",
        required=True,
        help="path to libwebrtc repo",
    )
    parser.add_argument(
        "--branch",
        default="mozpatches",
        help="moz-libwebrtc branch (defaults to mozpatches)",
    )
    parser.add_argument(
        "--patch-path",
        default=default_patch_dir,
        help="path to save patches (defaults to {})".format(default_patch_dir),
    )
    parser.add_argument(
        "--state-path",
        default=default_state_dir,
        help="path to state directory (defaults to {})".format(default_state_dir),
    )
    parser.add_argument(
        "--target-branch-head",
        required=True,
        help="target branch head for fast-forward, should match MOZ_TARGET_UPSTREAM_BRANCH_HEAD in config_env",
    )
    parser.add_argument(
        "--separate-commit-bug-number",
        type=int,
        help="integer Bugzilla number (example: 1800920), if provided will write patch stack as separate commit",
    )
    args = parser.parse_args()

    save_patch_stack(
        args.repo_path,
        args.branch,
        os.path.abspath(args.patch_path),
        args.state_path,
        args.target_branch_head,
        args.separate_commit_bug_number,
    )
