Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit f8d291a

Browse files
committed
Bug 1881792 - part 1: Create mach android uplift command r=geckoview-reviewers,jcristau,owlish
Differential Revision: https://phabricator.services.mozilla.com/D202603
1 parent 612c6c0 commit f8d291a

1 file changed

Lines changed: 231 additions & 0 deletions

File tree

mobile/android/mach_commands.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
import argparse
66
import logging
77
import os
8+
import re
9+
import subprocess
810
import sys
911
import tarfile
1012

1113
import mozpack.path as mozpath
1214
from mach.decorators import Command, CommandArgument, SubCommand
1315
from mozbuild.base import MachCommandConditions as conditions
1416
from mozbuild.shellutil import split as shell_split
17+
from mozfile import which
1518

1619
# Mach's conditions facility doesn't support subcommands. Print a
1720
# deprecation message ourselves instead.
@@ -694,3 +697,231 @@ def emulator(
694697
"Unable to retrieve Android emulator return code.",
695698
)
696699
return 0
700+
701+
702+
@SubCommand(
703+
"android",
704+
"uplift",
705+
description="Uplift patch to https://github.com/mozilla-mobile/firefox-android.",
706+
)
707+
@CommandArgument(
708+
"--revset",
709+
"-r",
710+
default=None,
711+
help="Revision or revisions to uplift. Supported values are the same as what your "
712+
"VCS provides. Defaults to the current HEAD revision.",
713+
)
714+
@CommandArgument(
715+
"firefox_android_clone_dir",
716+
help="The directory where your local clone of "
717+
"https://github.com/mozilla-mobile/firefox-android repo is.",
718+
)
719+
def uplift(
720+
command_context,
721+
revset,
722+
firefox_android_clone_dir,
723+
):
724+
revset = _get_default_revset_if_needed(command_context, revset)
725+
major_version = _get_major_version(command_context, revset)
726+
uplift_version = major_version - 1
727+
bug_number = _get_bug_number(command_context, revset)
728+
new_branch_name = (
729+
f"{uplift_version}-bug{bug_number}" if bug_number else f"{uplift_version}-nobug"
730+
)
731+
command_context.log(
732+
logging.INFO,
733+
"uplift",
734+
{
735+
"new_branch_name": new_branch_name,
736+
"firefox_android_clone_dir": firefox_android_clone_dir,
737+
},
738+
"Creating branch {new_branch_name} in {firefox_android_clone_dir}...",
739+
)
740+
741+
try:
742+
_checkout_new_branch_updated_to_the_latest_remote(
743+
command_context, firefox_android_clone_dir, uplift_version, new_branch_name
744+
)
745+
_export_and_apply_revset(command_context, revset, firefox_android_clone_dir)
746+
except subprocess.CalledProcessError:
747+
return 1
748+
749+
command_context.log(
750+
logging.INFO,
751+
"uplift",
752+
{"revset": revset, "firefox_android_clone_dir": firefox_android_clone_dir},
753+
"Revision(s) {revset} now applied to {firefox_android_clone_dir}. Please go to "
754+
"this directory, inspect the commit(s), and push.",
755+
)
756+
return 0
757+
758+
759+
def _get_default_revset_if_needed(command_context, revset):
760+
if revset is not None:
761+
return revset
762+
if conditions.is_hg(command_context):
763+
return "."
764+
raise NotImplementedError()
765+
766+
767+
def _get_major_version(command_context, revset):
768+
milestone_txt = _get_milestone_txt(command_context, revset)
769+
version = _extract_version_from_milestone_txt(milestone_txt)
770+
return _extract_major_version(version)
771+
772+
773+
def _get_bug_number(command_context, revision):
774+
revision_message = _extract_revision_message(command_context, revision)
775+
return _extract_bug_number(revision_message)
776+
777+
778+
def _get_milestone_txt(command_context, revset):
779+
if conditions.is_hg(command_context):
780+
args = [
781+
str(which("hg")),
782+
"cat",
783+
"-r",
784+
f"first({revset})",
785+
"config/milestone.txt",
786+
]
787+
return subprocess.check_output(args, text=True)
788+
else:
789+
raise NotImplementedError()
790+
791+
792+
def _extract_version_from_milestone_txt(milestone_txt):
793+
return milestone_txt.splitlines()[-1]
794+
795+
796+
def _extract_major_version(version):
797+
return int(version.split(".")[0])
798+
799+
800+
def _extract_revision_message(command_context, revision):
801+
if conditions.is_hg(command_context):
802+
args = [
803+
str(which("hg")),
804+
"log",
805+
"--rev",
806+
f"first({revision})",
807+
"--template",
808+
"{desc}",
809+
]
810+
return subprocess.check_output(args, text=True)
811+
else:
812+
raise NotImplementedError()
813+
814+
815+
# Source: https://hg.mozilla.org/hgcustom/version-control-tools/file/cef43d3d676e9f9e9668a50a5d90c012e4025e5b/pylib/mozautomation/mozautomation/commitparser.py#l12
816+
_BUG_TEMPLATE = re.compile(
817+
r"""# bug followed by any sequence of numbers, or
818+
# a standalone sequence of numbers
819+
(
820+
(?:
821+
bug |
822+
b= |
823+
# a sequence of 5+ numbers preceded by whitespace
824+
(?=\b\#?\d{5,}) |
825+
# numbers at the very beginning
826+
^(?=\d)
827+
)
828+
(?:\s*\#?)(\d+)(?=\b)
829+
)""",
830+
re.I | re.X,
831+
)
832+
833+
834+
def _extract_bug_number(revision_message):
835+
try:
836+
return _BUG_TEMPLATE.match(revision_message).group(2)
837+
except AttributeError:
838+
return ""
839+
840+
841+
_FIREFOX_ANDROID_URL = "https://github.com/mozilla-mobile/firefox-android"
842+
843+
844+
def _checkout_new_branch_updated_to_the_latest_remote(
845+
command_context, firefox_android_clone_dir, uplift_version, new_branch_name
846+
):
847+
args = [
848+
str(which("git")),
849+
"fetch",
850+
_FIREFOX_ANDROID_URL,
851+
f"+releases_v{uplift_version}:{new_branch_name}",
852+
]
853+
854+
try:
855+
subprocess.check_call(args, cwd=firefox_android_clone_dir)
856+
except subprocess.CalledProcessError:
857+
command_context.log(
858+
logging.CRITICAL,
859+
"uplift",
860+
{
861+
"firefox_android_clone_dir": firefox_android_clone_dir,
862+
"new_branch_name": new_branch_name,
863+
},
864+
"Could not fetch branch {new_branch_name}. This may be a network issue. If "
865+
"not, please go to {firefox_android_clone_dir}, inspect the branch and "
866+
"delete it (with `git branch -D {new_branch_name}`) if you don't have any "
867+
"use for it anymore",
868+
)
869+
raise
870+
871+
args = [
872+
str(which("git")),
873+
"checkout",
874+
new_branch_name,
875+
]
876+
subprocess.check_call(args, cwd=firefox_android_clone_dir)
877+
878+
879+
_MERCURIAL_REVISION_TO_GIT_COMMIT_TEMPLATE = """
880+
From 1234567890abcdef1234567890abcdef12345678 Sat Jan 1 00:00:00 2000
881+
From: {user}
882+
Date: {date|rfc822date}
883+
Subject: {desc}
884+
885+
"""
886+
887+
888+
def _export_and_apply_revset(command_context, revset, firefox_android_clone_dir):
889+
export_command, import_command = _get_export_import_commands(
890+
command_context, revset
891+
)
892+
893+
export_process = subprocess.Popen(export_command, stdout=subprocess.PIPE)
894+
try:
895+
subprocess.check_call(
896+
import_command, stdin=export_process.stdout, cwd=firefox_android_clone_dir
897+
)
898+
except subprocess.CalledProcessError:
899+
command_context.log(
900+
logging.CRITICAL,
901+
"uplift",
902+
{"firefox_android_clone_dir": firefox_android_clone_dir},
903+
"Could not run `git am`. Please go to {firefox_android_clone_dir} and fix "
904+
"the conflicts. Then run `git am --continue`.",
905+
)
906+
raise
907+
export_process.wait()
908+
909+
910+
def _get_export_import_commands(command_context, revset):
911+
if conditions.is_hg(command_context):
912+
export_command = [
913+
str(which("hg")),
914+
"log",
915+
"--rev",
916+
revset,
917+
"--patch",
918+
"--template",
919+
_MERCURIAL_REVISION_TO_GIT_COMMIT_TEMPLATE,
920+
"--include",
921+
"mobile/android",
922+
]
923+
import_command = [str(which("git")), "am", "-p3"]
924+
else:
925+
raise NotImplementedError()
926+
927+
return export_command, import_command

0 commit comments

Comments
 (0)