summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/third_party_build/save_patch_stack.py
blob: 907b90f59307805859533602419f5e9a2e9f8c2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# 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 atexit
import os
import re
import shutil
import sys

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.

error_help = None
script_name = os.path.basename(__file__)


@atexit.register
def early_exit_handler():
    print("*** ERROR *** {} did not complete successfully".format(script_name))
    if error_help is not None:
        print(error_help)


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 --no-signature --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(r"^(\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_script_dir = "dom/media/webrtc/third_party_build"
    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(
        "--script-path",
        default=default_script_dir,
        help="path to script directory (defaults to {})".format(default_script_dir),
    )
    parser.add_argument(
        "--separate-commit-bug-number",
        type=int,
        help="integer Bugzilla number (example: 1800920), if provided will write patch stack as separate commit",
    )
    parser.add_argument(
        "--skip-startup-sanity",
        action="store_true",
        default=False,
        help="skip checking for clean repo and doing the initial verify vendoring",
    )
    args = parser.parse_args()

    if not args.skip_startup_sanity:
        # make sure the mercurial repo is clean before beginning
        error_help = (
            "There are modified or untracked files in the mercurial repo.\n"
            "Please start with a clean repo before running {}"
        ).format(script_name)
        stdout_lines = run_hg("hg status")
        if len(stdout_lines) != 0:
            sys.exit(1)

        # make sure the github repo exists
        error_help = (
            "No moz-libwebrtc github repo found at {}\n"
            "Please run restore_patch_stack.py before running {}"
        ).format(args.repo_path, script_name)
        if not os.path.exists(args.repo_path):
            sys.exit(1)
        error_help = None

        print("Verifying vendoring before saving patch-stack...")
        run_shell("bash {}/verify_vendoring.sh".format(args.script_path), False)

    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,
    )

    # unregister the exit handler so the normal exit doesn't falsely
    # report as an error.
    atexit.unregister(early_exit_handler)