Pier Angelo Vendrame pushed to branch tor-browser-128.3.0esr-14.0-1 at The Tor Project / Applications / Tor Browser

Commits:

2 changed files:

Changes:

  • .gitlab/ci/update-translations.yml
    ... ... @@ -28,6 +28,7 @@ combine-en-US-translations:
    28 28
           base-browser:base-browser.ftl
    
    29 29
           fenix-torbrowserstringsxml:torbrowser_strings.xml
    
    30 30
         '
    
    31
    +    TRANSLATION_INCLUDE_LEGACY: "true"
    
    31 32
       cache:
    
    32 33
         paths:
    
    33 34
           - .cache/pip
    

  • tools/torbrowser/l10n/combine-translation-versions.py
    ... ... @@ -67,136 +67,227 @@ def git_lines(git_args: list[str]) -> list[str]:
    67 67
         return [line for line in git_text(git_args).split("\n") if line]
    
    68 68
     
    
    69 69
     
    
    70
    -def git_file_paths(git_ref: str) -> list[str]:
    
    71
    -    """Get the full list of file paths found under the given tree.
    
    72
    -
    
    73
    -    :param git_ref: The git reference for the tree to search.
    
    74
    -    :returns: The found file paths.
    
    75
    -    """
    
    76
    -    return git_lines(["ls-tree", "-r", "--format=%(path)", git_ref])
    
    77
    -
    
    78
    -
    
    79
    -def matching_path(search_paths: list[str], filename: str) -> str | None:
    
    80
    -    """Get the matching file path with the given filename, if it exists.
    
    81
    -
    
    82
    -    :param search_paths: The file paths to search through.
    
    83
    -    :param filename: The file name to match.
    
    84
    -    :returns: The unique file path with the matching name, or None if no such
    
    85
    -      match was found.
    
    86
    -    :throws Exception: If multiple paths shared the same file name.
    
    87
    -    """
    
    88
    -    matching = [path for path in search_paths if os.path.basename(path) == filename]
    
    89
    -    if not matching:
    
    90
    -        return None
    
    91
    -    if len(matching) > 1:
    
    92
    -        raise Exception("Multiple occurrences of {filename}")
    
    93
    -    return matching[0]
    
    94
    -
    
    95
    -
    
    96
    -def git_file_content(git_ref: str, path: str | None) -> str | None:
    
    97
    -    """Get the file content of the specified git blob object.
    
    98
    -
    
    99
    -    :param git_ref: The reference for the tree to find the file under.
    
    100
    -    :param path: The file path for the object, or None if there is no path.
    
    101
    -    :returns: The file content, or None if no path was given.
    
    102
    -    """
    
    103
    -    if path is None:
    
    104
    -        return None
    
    105
    -    return git_text(["cat-file", "blob", f"{git_ref}:{path}"])
    
    106
    -
    
    107
    -
    
    108
    -def get_stable_branch(branch_prefix: str) -> str:
    
    70
    +class BrowserBranch:
    
    71
    +    """Represents a browser git branch."""
    
    72
    +
    
    73
    +    def __init__(self, branch_name: str, is_head: bool = False) -> None:
    
    74
    +        """Create a new instance.
    
    75
    +
    
    76
    +        :param branch_name: The branch's git name.
    
    77
    +        :param is_head: Whether the branch matches "HEAD".
    
    78
    +        """
    
    79
    +        version_match = re.match(
    
    80
    +            r"(?P<prefix>[a-z]+\-browser)\-"
    
    81
    +            r"(?P<firefox>[0-9]+(?:\.[0-9]+){1,2})esr\-"
    
    82
    +            r"(?P<browser>[0-9]+\.[05])\-"
    
    83
    +            r"(?P<number>[0-9]+)$",
    
    84
    +            branch_name,
    
    85
    +        )
    
    86
    +
    
    87
    +        if not version_match:
    
    88
    +            raise ValueError(f"Unable to parse the version from the ref {branch_name}")
    
    89
    +
    
    90
    +        self.name = branch_name
    
    91
    +        self.prefix = version_match.group("prefix")
    
    92
    +        self.browser_version = version_match.group("browser")
    
    93
    +        self._is_head = is_head
    
    94
    +        self._ref = "HEAD" if is_head else f"origin/{branch_name}"
    
    95
    +
    
    96
    +        firefox_nums = [int(n) for n in version_match.group("firefox").split(".")]
    
    97
    +        if len(firefox_nums) == 2:
    
    98
    +            firefox_nums.append(0)
    
    99
    +        browser_nums = [int(n) for n in self.browser_version.split(".")]
    
    100
    +        branch_number = int(version_match.group("number"))
    
    101
    +        # Prioritise the firefox ESR version, then the browser version then the
    
    102
    +        # branch number.
    
    103
    +        self._ordered = (
    
    104
    +            firefox_nums[0],
    
    105
    +            firefox_nums[1],
    
    106
    +            firefox_nums[2],
    
    107
    +            browser_nums[0],
    
    108
    +            browser_nums[1],
    
    109
    +            branch_number,
    
    110
    +        )
    
    111
    +
    
    112
    +        # Minor version for browser is only ever "0" or "5", so we can convert
    
    113
    +        # the version to an integer.
    
    114
    +        self._browser_int_version = int(2 * float(self.browser_version))
    
    115
    +
    
    116
    +        self._file_paths: list[str] | None = None
    
    117
    +
    
    118
    +    def release_below(self, other: "BrowserBranch", num: int) -> bool:
    
    119
    +        """Determine whether another branch is within range of a previous
    
    120
    +        browser release.
    
    121
    +
    
    122
    +        The browser versions are expected to increment by "0.5", and a previous
    
    123
    +        release branch's version is expected to be `num * 0.5` behind the
    
    124
    +        current one.
    
    125
    +
    
    126
    +        :param other: The branch to compare.
    
    127
    +        :param num: The number of "0.5" releases behind to test with.
    
    128
    +        """
    
    129
    +        return other._browser_int_version == self._browser_int_version - num
    
    130
    +
    
    131
    +    def __lt__(self, other: "BrowserBranch") -> bool:
    
    132
    +        return self._ordered < other._ordered
    
    133
    +
    
    134
    +    def __gt__(self, other: "BrowserBranch") -> bool:
    
    135
    +        return self._ordered > other._ordered
    
    136
    +
    
    137
    +    def get_file_content(self, filename: str) -> str | None:
    
    138
    +        """Fetch the file content for the named file in this branch.
    
    139
    +
    
    140
    +        :param filename: The name of the file to fetch the content for.
    
    141
    +        :returns: The file content, or `None` if no file could be found.
    
    142
    +        """
    
    143
    +        if self._file_paths is None:
    
    144
    +            if not self._is_head:
    
    145
    +                # Minimal fetch of non-HEAD branch to get the file paths.
    
    146
    +                # Individual file blobs will be downloaded as needed.
    
    147
    +                git_run(
    
    148
    +                    ["fetch", "--depth=1", "--filter=blob:none", "origin", self._ref]
    
    149
    +                )
    
    150
    +            self._file_paths = git_lines(
    
    151
    +                ["ls-tree", "-r", "--format=%(path)", self._ref]
    
    152
    +            )
    
    153
    +
    
    154
    +        matching = [
    
    155
    +            path for path in self._file_paths if os.path.basename(path) == filename
    
    156
    +        ]
    
    157
    +        if not matching:
    
    158
    +            return None
    
    159
    +        if len(matching) > 1:
    
    160
    +            raise Exception(f"Multiple occurrences of {filename}")
    
    161
    +
    
    162
    +        path = matching[0]
    
    163
    +
    
    164
    +        return git_text(["cat-file", "blob", f"{self._ref}:{path}"])
    
    165
    +
    
    166
    +
    
    167
    +def get_stable_branch(
    
    168
    +    compare_version: BrowserBranch,
    
    169
    +) -> tuple[BrowserBranch, BrowserBranch | None]:
    
    109 170
         """Find the most recent stable branch in the origin repository.
    
    110 171
     
    
    111
    -    :param branch_prefix: The prefix that the stable branch should have.
    
    112
    -    :returns: The branch name.
    
    172
    +    :param compare_version: The development branch to compare against.
    
    173
    +    :returns: The stable and legacy branches. If no legacy branch is found,
    
    174
    +      `None` will be returned instead.
    
    113 175
         """
    
    114
    -    tag_glob = f"{branch_prefix}-*-build1"
    
    176
    +    # We search for build1 tags. These are added *after* the rebase of browser
    
    177
    +    # commits, so the corresponding branch should contain our strings.
    
    178
    +    # Moreover, we *assume* that the branch with the most recent ESR version
    
    179
    +    # with such a tag will be used in the *next* stable build in
    
    180
    +    # tor-browser-build.
    
    181
    +    tag_glob = f"{compare_version.prefix}-*esr-*-*-build1"
    
    182
    +
    
    115 183
         # To speed up, only fetch the tags without blobs.
    
    116 184
         git_run(
    
    117 185
             ["fetch", "--depth=1", "--filter=object:type=tag", "origin", "tag", tag_glob]
    
    118 186
         )
    
    119
    -    # Get most recent stable tag.
    
    187
    +    stable_branches = []
    
    188
    +    legacy_branches = []
    
    189
    +    stable_annotation_regex = re.compile(r"\bstable\b")
    
    190
    +    legacy_annotation_regex = re.compile(r"\blegacy\b")
    
    191
    +
    
    120 192
         for build_tag, annotation in (
    
    121
    -        line.split(" ", 1)
    
    122
    -        for line in git_lines(["tag", "-n1", "--list", tag_glob, "--sort=-taggerdate"])
    
    193
    +        line.split(" ", 1) for line in git_lines(["tag", "-n1", "--list", tag_glob])
    
    123 194
         ):
    
    124
    -        if "stable" in annotation:
    
    195
    +        is_stable = bool(stable_annotation_regex.search(annotation))
    
    196
    +        is_legacy = bool(legacy_annotation_regex.search(annotation))
    
    197
    +        if not is_stable and not is_legacy:
    
    198
    +            continue
    
    199
    +        try:
    
    125 200
                 # Branch name is the same as the tag, minus "-build1".
    
    126
    -            return re.sub(r"-build1$", "", build_tag)
    
    127
    -    raise Exception("No stable build1 tag found")
    
    128
    -
    
    129
    -
    
    130
    -def get_version_from_branch_name(branch_name: str) -> tuple[str, float]:
    
    131
    -    """Get the branch prefix and version from its name.
    
    132
    -
    
    133
    -    :param branch_name: The branch to extract from.
    
    134
    -    :returns: The branch prefix and its version number.
    
    135
    -    """
    
    136
    -    version_match = re.match(
    
    137
    -        r"([a-z-]+)-[^-]*-([0-9]+\.[05])-",
    
    138
    -        branch_name,
    
    201
    +            branch = BrowserBranch(re.sub(r"-build1$", "", build_tag))
    
    202
    +        except ValueError:
    
    203
    +            logger.warning(f"Could not read the version for {build_tag}")
    
    204
    +            continue
    
    205
    +        if branch.prefix != compare_version.prefix:
    
    206
    +            continue
    
    207
    +        if is_stable:
    
    208
    +            # Stable can be one release version behind.
    
    209
    +            # NOTE: In principle, when switching between versions there may be a
    
    210
    +            # window of time where the development branch has not yet progressed
    
    211
    +            # to the next "0.5" release, so has the same browser version as the
    
    212
    +            # stable branch. So we also allow for matching browser versions.
    
    213
    +            # NOTE:
    
    214
    +            # 1. The "Will be unused in" message will not make sense, but we do
    
    215
    +            #    not expect string differences in this scenario.
    
    216
    +            # 2. We do not expect this scenario to last for long.
    
    217
    +            if not (
    
    218
    +                compare_version.release_below(branch, 1)
    
    219
    +                or compare_version.release_below(branch, 0)
    
    220
    +            ):
    
    221
    +                continue
    
    222
    +            stable_branches.append(branch)
    
    223
    +        elif is_legacy:
    
    224
    +            # Legacy can be two release versions behind.
    
    225
    +            # We also allow for being just one version behind.
    
    226
    +            if not (
    
    227
    +                compare_version.release_below(branch, 2)
    
    228
    +                or compare_version.release_below(branch, 1)
    
    229
    +            ):
    
    230
    +                continue
    
    231
    +            legacy_branches.append(branch)
    
    232
    +
    
    233
    +    if not stable_branches:
    
    234
    +        raise Exception("No stable build1 branch found")
    
    235
    +
    
    236
    +    return (
    
    237
    +        # Return the stable branch with the highest version.
    
    238
    +        max(stable_branches),
    
    239
    +        max(legacy_branches) if legacy_branches else None,
    
    139 240
         )
    
    140 241
     
    
    141
    -    if not version_match:
    
    142
    -        raise ValueError(f"Unable to parse the version from the branch {branch_name}")
    
    143 242
     
    
    144
    -    return (version_match.group(1), float(version_match.group(2)))
    
    243
    +current_branch = BrowserBranch(args.current_branch, is_head=True)
    
    145 244
     
    
    245
    +stable_branch, legacy_branch = get_stable_branch(current_branch)
    
    146 246
     
    
    147
    -branch_prefix, current_version = get_version_from_branch_name(args.current_branch)
    
    247
    +if os.environ.get("TRANSLATION_INCLUDE_LEGACY", "") != "true":
    
    248
    +    legacy_branch = None
    
    148 249
     
    
    149
    -stable_branch = get_stable_branch(branch_prefix)
    
    150
    -_, stable_version = get_version_from_branch_name(stable_branch)
    
    151
    -
    
    152
    -if stable_version > current_version or stable_version < current_version - 0.5:
    
    153
    -    raise Exception(
    
    154
    -        f"Version of stable branch {stable_branch} is not within 0.5 of the "
    
    155
    -        f"current branch {args.current_branch}"
    
    156
    -    )
    
    157
    -
    
    158
    -# Minimal fetch of stable_branch.
    
    159
    -# Individual file blobs will be downloaded as needed.
    
    160
    -git_run(["fetch", "--depth=1", "--filter=blob:none", "origin", stable_branch])
    
    161
    -
    
    162
    -current_file_paths = git_file_paths("HEAD")
    
    163
    -old_file_paths = git_file_paths(f"origin/{stable_branch}")
    
    164
    -
    
    165
    -ci_commit = os.environ.get("CI_COMMIT_SHA", "")
    
    166
    -ci_url_base = os.environ.get("CI_PROJECT_URL", "")
    
    167
    -
    
    168
    -json_data = {
    
    169
    -    "commit": ci_commit,
    
    170
    -    "commit-url": f"{ci_url_base}/-/commit/{ci_commit}"
    
    171
    -    if (ci_commit and ci_url_base)
    
    172
    -    else "",
    
    173
    -    "project-path": os.environ.get("CI_PROJECT_PATH", ""),
    
    174
    -    "current-branch": args.current_branch,
    
    175
    -    "stable-branch": stable_branch,
    
    176
    -    "files": [],
    
    177
    -}
    
    250
    +files_list = []
    
    178 251
     
    
    179 252
     for translation_branch, name in (
    
    180 253
         part.strip().split(":", 1) for part in args.filenames.split(" ") if part.strip()
    
    181 254
     ):
    
    182
    -    current_path = matching_path(current_file_paths, name)
    
    183
    -    old_path = matching_path(old_file_paths, name)
    
    255
    +    current_content = current_branch.get_file_content(name)
    
    256
    +    stable_content = stable_branch.get_file_content(name)
    
    184 257
     
    
    185
    -    if current_path is None and old_path is None:
    
    258
    +    if current_content is None and stable_content is None:
    
    186 259
             # No file in either branch.
    
    187 260
             logger.warning(f"{name} does not exist in either the current or stable branch")
    
    188
    -    elif current_path is None:
    
    261
    +    elif current_content is None:
    
    189 262
             logger.warning(f"{name} deleted in the current branch")
    
    190
    -    elif old_path is None:
    
    263
    +    elif stable_content is None:
    
    191 264
             logger.warning(f"{name} does not exist in the stable branch")
    
    192 265
     
    
    193 266
         content = combine_files(
    
    194 267
             name,
    
    195
    -        git_file_content("HEAD", current_path),
    
    196
    -        git_file_content(f"origin/{stable_branch}", old_path),
    
    197
    -        f"Will be unused in Tor Browser {current_version}!",
    
    268
    +        current_content,
    
    269
    +        stable_content,
    
    270
    +        f"Will be unused in Tor Browser {current_branch.browser_version}!",
    
    198 271
         )
    
    199
    -    json_data["files"].append(
    
    272
    +
    
    273
    +    if legacy_branch:
    
    274
    +        legacy_content = legacy_branch.get_file_content(name)
    
    275
    +        if (
    
    276
    +            legacy_content is not None
    
    277
    +            and current_content is None
    
    278
    +            and stable_content is None
    
    279
    +        ):
    
    280
    +            logger.warning(f"{name} still exists in the legacy branch")
    
    281
    +        elif legacy_content is None:
    
    282
    +            logger.warning(f"{name} does not exist in the legacy branch")
    
    283
    +        content = combine_files(
    
    284
    +            name,
    
    285
    +            content,
    
    286
    +            legacy_content,
    
    287
    +            f"Unused in Tor Browser {stable_branch.browser_version}!",
    
    288
    +        )
    
    289
    +
    
    290
    +    files_list.append(
    
    200 291
             {
    
    201 292
                 "name": name,
    
    202 293
                 "branch": translation_branch,
    
    ... ... @@ -204,5 +295,23 @@ for translation_branch, name in (
    204 295
             }
    
    205 296
         )
    
    206 297
     
    
    298
    +
    
    299
    +ci_commit = os.environ.get("CI_COMMIT_SHA", "")
    
    300
    +ci_url_base = os.environ.get("CI_PROJECT_URL", "")
    
    301
    +
    
    302
    +json_data = {
    
    303
    +    "commit": ci_commit,
    
    304
    +    "commit-url": f"{ci_url_base}/-/commit/{ci_commit}"
    
    305
    +    if (ci_commit and ci_url_base)
    
    306
    +    else "",
    
    307
    +    "project-path": os.environ.get("CI_PROJECT_PATH", ""),
    
    308
    +    "current-branch": current_branch.name,
    
    309
    +    "stable-branch": stable_branch.name,
    
    310
    +    "files": files_list,
    
    311
    +}
    
    312
    +
    
    313
    +if legacy_branch:
    
    314
    +    json_data["legacy-branch"] = legacy_branch.name
    
    315
    +
    
    207 316
     with open(args.outname, "w") as file:
    
    208 317
         json.dump(json_data, file)