From 7b318698f17b3801bd2da24dd09b63cb623f6db6 Mon Sep 17 00:00:00 2001 From: Malte Juergens Date: Tue, 10 Mar 2026 15:22:23 +0100 Subject: [PATCH] Migrate from Mercurial to Git --- apis/phabricator.py | 10 +++++----- automation.py | 21 +++------------------ components/{hg.py => git.py} | 28 +++++++++++++--------------- docs/new_job.md | 2 +- localconfig.py.example | 3 +-- tasktypes/vendoring.py | 14 +++++++------- tests/automation_configuration.py | 11 +++++------ tests/bugzilla.py | 1 - tests/functionality_all_platforms.py | 8 +++----- tests/functionality_commitalert.py | 9 ++------- tests/functionality_two_platforms.py | 8 +++----- tests/functionality_utilities.py | 8 +------- 12 files changed, 44 insertions(+), 79 deletions(-) rename components/{hg.py => git.py} (58%) diff --git a/apis/phabricator.py b/apis/phabricator.py index a12e42d4..18436a3f 100644 --- a/apis/phabricator.py +++ b/apis/phabricator.py @@ -71,17 +71,17 @@ def submit_to_phabricator(rev_id, retry_attempt=None): # arc diff will squash all commits into a single commit, so we need to jump through some hoops. # Conceptually, we are only commiting the top-most commit in the repo (and not any subsequent commits) - # If we have two commits, we'll go backwards and grab only the first commit, then go back to tip + # If we have two commits, we'll go backwards and grab only the first commit, then go back to HEAD if has_patches: # Checkout to the first patch - self.run(["hg", "checkout", "tip^"]) + self.run(["git", "checkout", "HEAD~1"]) # Tell phabricator to submit from the base to the current working tree phab_revisions.append(submit_to_phabricator("")) - # Ask hg to evolve the original second patch on top of the rewritten first patch - self.run(["hg", "next"]) + # Return to the original branch tip + self.run(["git", "checkout", "-"]) # Submit only a single patch - phab_revisions.append(submit_to_phabricator("tip^")) + phab_revisions.append(submit_to_phabricator("HEAD~1")) # Chain revisions together if needed @retry diff --git a/automation.py b/automation.py index 6f736c73..9dff64a7 100755 --- a/automation.py +++ b/automation.py @@ -5,7 +5,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import os -import re import sys import time from components.logging import LoggingProvider, SimpleLogger, LogLevel @@ -15,7 +14,7 @@ from components.mach_vendor import VendorProvider from components.bugzilla import BugzillaProvider from components.scmprovider import SCMProvider -from components.hg import MercurialProvider, reset_repository +from components.git import GitProvider, reset_repository from apis.taskcluster import TaskclusterProvider from apis.phabricator import PhabricatorProvider from tasktypes.vendoring import VendorTaskRunner @@ -28,7 +27,7 @@ 'Vendor': VendorProvider, 'Bugzilla': BugzillaProvider, 'Library': LibraryProvider, - 'Mercurial': MercurialProvider, + 'Git': GitProvider, 'Taskcluster': TaskclusterProvider, 'Phabricator': PhabricatorProvider, 'SCM': SCMProvider, @@ -119,7 +118,7 @@ def getOr(name): 'vendorProvider': getOr('Vendor'), 'bugzillaProvider': getOr('Bugzilla'), 'libraryProvider': getOr('Library'), - 'mercurialProvider': getOr('Mercurial'), + 'gitProvider': getOr('Git'), 'taskclusterProvider': getOr('Taskcluster'), 'phabricatorProvider': getOr('Phabricator'), 'scmProvider': getOr('SCM'), @@ -180,20 +179,6 @@ def _validate(self, config_dictionary): sys.exit(1) config_dictionary['General']['ff-version'] = ff_version - if 'GECKO_HEAD_REPOSITORY' not in os.environ and 'repo' not in config_dictionary['General']: - self.logger.log("I cannot tell what repository I'm running from. Add 'repo' to the config dictionary or ensure GECKO_HEAD_REPOSITORY is in the environment.", level=LogLevel.Fatal) - sys.exit(1) - elif 'GECKO_HEAD_REPOSITORY' in os.environ: - config_dictionary['General']['repo'] = os.environ['GECKO_HEAD_REPOSITORY'] - - if re.match(r"https://hg.mozilla.org/projects/(\w+)", config_dictionary['General']['repo']): - config_dictionary['General']['repo'] = config_dictionary['General']['repo'].replace("https://hg.mozilla.org/projects/", "") - elif re.match(r"https://hg.mozilla.org/mozilla-(\w+)", config_dictionary['General']['repo']): - config_dictionary['General']['repo'] = config_dictionary['General']['repo'].replace("https://hg.mozilla.org/", "") - else: - self.logger.log("The repository specified in the config dictionary was not of the form https://hg.mozilla.org/mozilla-foo.", level=LogLevel.Fatal) - sys.exit(1) - return config_dictionary def run(self, library_filter=""): diff --git a/components/hg.py b/components/git.py similarity index 58% rename from components/hg.py rename to components/git.py index e45ab769..8bb2bfe2 100644 --- a/components/hg.py +++ b/components/git.py @@ -9,40 +9,38 @@ def reset_repository(cmdrunner): - cmdrunner.run(["hg", "checkout", "-C", "."]) - cmdrunner.run(["hg", "purge", "."]) + head = os.environ.get("GECKO_HEAD_REV", "origin/master") - original_revision = os.environ.get("GECKO_HEAD_REV", "") - if original_revision: - ret = cmdrunner.run(["hg", "update", original_revision]) - else: - ret = cmdrunner.run(["hg", "strip", "roots(outgoing())", "--no-backup"], clean_return=False) - if ret.returncode == 255: - if "abort: empty revision set" not in ret.stderr.decode(): - ret.check_returncode() + # Clean staging area and reset HEAD + cmdrunner.run(["git", "reset", "--hard", head]) + # Clean untracked filed + cmdrunner.run(["git", "clean", "-fdx"]) -class MercurialProvider(BaseProvider, INeedsCommandProvider, INeedsLoggingProvider): +class GitProvider(BaseProvider, INeedsCommandProvider, INeedsLoggingProvider): def __init__(self, config): pass @logEntryExit def commit(self, library, bug_id, new_release_version): + self.run(["git", "add", "-all"]) # Note that this commit message format changes, one must also edit the # Updatebot Verify job in mozilla-central ( verify-updatebot.py ) bug_id = "Bug {0}".format(bug_id) - self.run(["hg", "commit", "-m", "%s - Update %s to %s" % + self.run(["git", "commit", "-m", "%s - Update %s to %s" % (bug_id, library.name, new_release_version)]) @logEntryExit def commit_patches(self, library, bug_id, new_release_version): + self.run(["git", "add", "-all"]) # Note that this commit message format changes, one must also edit the # Updatebot Verify job in mozilla-central ( verify-updatebot.py ) bug_id = "Bug {0}".format(bug_id) - self.run(["hg", "commit", "-m", "%s - Apply mozilla patches for %s" % + self.run(["git", "commit", "-m", "%s - Apply mozilla patches for %s" % (bug_id, library.name)]) @logEntryExit def diff_stats(self): - ret = self.run(["hg", "diff", "--stat"]) - return ret.stdout.decode().rstrip() + ret = self.run(["git", "diff", "--stat"]) + out = ret.stdout.decode() + return out.rstrip() if out else "" diff --git a/docs/new_job.md b/docs/new_job.md index b5b6e833..baa0ee9d 100644 --- a/docs/new_job.md +++ b/docs/new_job.md @@ -44,7 +44,7 @@ graph TD CommentMozBuild[Comment on
the bug about error] --> Commit - Commit[Mercurial Commit] -->HasPatches + Commit[Git Commit] -->HasPatches HasPatches{Are there
patches to
apply?} HasPatches --> |Yes| ApplyPatches[Apply Patches] --> CommitPatches[Commit Patches] --> PushToTry diff --git a/localconfig.py.example b/localconfig.py.example index f8f5b4ba..1bbadccc 100644 --- a/localconfig.py.example +++ b/localconfig.py.example @@ -3,8 +3,7 @@ localconfig = { 'General': { 'env': 'dev', - 'gecko-path': '/path/to/mozilla-central', - 'repo': 'https://hg.mozilla.org/mozilla-central' + 'gecko-path': '/path/to/firefox', }, 'Logging': { 'level': 5, diff --git a/tasktypes/vendoring.py b/tasktypes/vendoring.py index 132845d9..84815202 100644 --- a/tasktypes/vendoring.py +++ b/tasktypes/vendoring.py @@ -14,7 +14,7 @@ from components.mach_vendor import VendorResult from components.dbmodels import JOBSTATUS, JOBOUTCOME, JOBTYPE from components.logging import LogLevel, logEntryExit, logEntryExitNoArgs -from components.hg import reset_repository +from components.git import reset_repository class VendorTaskRunner(BaseTaskRunner): @@ -87,7 +87,7 @@ def _reset_for_new_job(self): self.logger.log("Removing any outgoing commits before moving on.", level=LogLevel.Info) # If we are on TC, update to the HEAD commit to avoid stripping WIP commits on holly - self.cmdProvider.run(["hg", "status"]) + self.cmdProvider.run(["git", "status"]) reset_repository(self.cmdProvider) # ==================================================================== @@ -137,7 +137,7 @@ def _process_new_job(self, library, task, new_version, timestamp, most_recent_jo # File the bug ------------------------ all_upstream_commits, unseen_upstream_commits = self.scmProvider.check_for_update(library, task, new_version, most_recent_job.version if most_recent_job else None) - commit_stats = self.mercurialProvider.diff_stats() + commit_stats = self.gitProvider.diff_stats() commit_details = self.scmProvider.build_bug_description(all_upstream_commits, 65534 - len(commit_stats) - 220) if library.should_show_commit_details else "" created_job.bugzilla_id = self.bugzillaProvider.file_bug(library, CommentTemplates.UPDATE_SUMMARY(library, new_version, timestamp), CommentTemplates.UPDATE_DETAILS(len(all_upstream_commits), len(unseen_upstream_commits), commit_stats, commit_details), task.cc, blocks=task.blocking) @@ -166,7 +166,7 @@ def _process_new_job(self, library, task, new_version, timestamp, most_recent_jo # Commit ------------------------------ try: - self.mercurialProvider.commit(library, created_job.bugzilla_id, new_version) + self.gitProvider.commit(library, created_job.bugzilla_id, new_version) except Exception as e: self.dbProvider.update_job_status(created_job, JOBSTATUS.DONE, JOBOUTCOME.COULD_NOT_COMMIT) self.bugzillaProvider.comment_on_bug(created_job.bugzilla_id, CommentTemplates.COULD_NOT_GENERAL_ERROR("commit the updated library."), needinfo=library.maintainer_bz) @@ -187,7 +187,7 @@ def _process_new_job(self, library, task, new_version, timestamp, most_recent_jo return # Commit Patches ------------------ try: - self.mercurialProvider.commit_patches(library, created_job.bugzilla_id, new_version) + self.gitProvider.commit_patches(library, created_job.bugzilla_id, new_version) except Exception as e: self.dbProvider.update_job_status(created_job, JOBSTATUS.DONE, JOBOUTCOME.COULD_NOT_COMMIT_PATCHES) self.bugzillaProvider.comment_on_bug(created_job.bugzilla_id, CommentTemplates.COULD_NOT_GENERAL_ERROR("commit after applying mozilla patches."), needinfo=library.maintainer_bz) @@ -500,7 +500,7 @@ def _process_job_details_for_awaiting_initial_platform_results(self, library, ta # Re-Commit ------------------- try: - self.mercurialProvider.commit(library, existing_job.bugzilla_id, existing_job.version) + self.gitProvider.commit(library, existing_job.bugzilla_id, existing_job.version) except Exception as e: self.dbProvider.update_job_status(existing_job, JOBSTATUS.DONE, JOBOUTCOME.COULD_NOT_COMMIT) self.bugzillaProvider.comment_on_bug(existing_job.bugzilla_id, CommentTemplates.COULD_NOT_GENERAL_ERROR("commit the updated library."), needinfo=library.maintainer_bz) @@ -521,7 +521,7 @@ def _process_job_details_for_awaiting_initial_platform_results(self, library, ta return # Commit Patches ------------------ try: - self.mercurialProvider.commit_patches(library, existing_job.bugzilla_id, existing_job.version) + self.gitProvider.commit_patches(library, existing_job.bugzilla_id, existing_job.version) except Exception as e: self.dbProvider.update_job_status(existing_job, JOBSTATUS.DONE, JOBOUTCOME.COULD_NOT_COMMIT_PATCHES) self.bugzillaProvider.comment_on_bug(existing_job.bugzilla_id, CommentTemplates.COULD_NOT_GENERAL_ERROR("commit after applying mozilla patches."), needinfo=library.maintainer_bz) diff --git a/tests/automation_configuration.py b/tests/automation_configuration.py index 677a02eb..bd91eb92 100755 --- a/tests/automation_configuration.py +++ b/tests/automation_configuration.py @@ -49,10 +49,10 @@ def _update_config(self, config): self.also_expected = "Made it!" -class TestConfigMercurialProvider(BaseTestConfigProvider): +class TestConfigGitProvider(BaseTestConfigProvider): def __init__(self, config): - self.expected = 'mercurial!' - super(TestConfigMercurialProvider, self).__init__(config) + self.expected = 'git!' + super(TestConfigGitProvider, self).__init__(config) def _update_config(self, config): self.also_expected = "Made it!" @@ -122,12 +122,11 @@ def testConfigurationPassing(self): 'env': 'dev', 'gecko-path': 'nowhere', 'ff-version': 87, - 'repo': 'https://hg.mozilla.org/mozilla-central' }, 'Database': {'specialkey': 'database!'}, 'Vendor': {'specialkey': 'vendor!'}, 'Bugzilla': {'specialkey': 'bugzilla!'}, - 'Mercurial': {'specialkey': 'mercurial!'}, + 'Git': {'specialkey': 'git!'}, 'Taskcluster': {'specialkey': 'taskcluster!'}, 'Phabricator': {'specialkey': 'phab!'}, 'Command': {'specialkey': 'command!'}, @@ -139,7 +138,7 @@ def testConfigurationPassing(self): 'Database': TestConfigDatabaseProvider, 'Vendor': TestConfigVendorProvider, 'Bugzilla': TestConfigBugzillaProvider, - 'Mercurial': TestConfigMercurialProvider, + 'Git': TestConfigGitProvider, 'Taskcluster': TestConfigTaskclusterProvider, 'Phabricator': TestConfigPhabricatorProvider, 'Logging': TestConfigLoggingProvider, diff --git a/tests/bugzilla.py b/tests/bugzilla.py index d4f74f0f..49beb358 100755 --- a/tests/bugzilla.py +++ b/tests/bugzilla.py @@ -159,7 +159,6 @@ def setUpClass(cls): 'General': { 'env': 'dev', 'ff-version': 88, - 'repo': 'https://hg.mozilla.org/mozilla-central' }, 'apikey': 'bob', 'url': 'http://localhost:27489/', diff --git a/tests/functionality_all_platforms.py b/tests/functionality_all_platforms.py index 82d33513..89f3ed93 100755 --- a/tests/functionality_all_platforms.py +++ b/tests/functionality_all_platforms.py @@ -27,7 +27,7 @@ from components.dbc import DatabaseProvider from components.dbmodels import JOBSTATUS, JOBOUTCOME from components.mach_vendor import VendorProvider -from components.hg import MercurialProvider +from components.git import GitProvider from components.scmprovider import SCMProvider from apis.taskcluster import TaskclusterProvider from apis.phabricator import PhabricatorProvider @@ -79,7 +79,7 @@ def COMMAND_MAPPINGS(expected_values, command_callbacks): # bugzilla server which provides no additional logic coverage 'Bugzilla': MockedBugzillaProvider, # Not Mocked At All - 'Mercurial': MercurialProvider, + 'Git': GitProvider, # Not Mocked At All, but does point to a fake server 'Taskcluster': TaskclusterProvider, # Not Mocked At All @@ -113,7 +113,6 @@ def _setup(self, 'env': 'dev', 'gecko-path': '.', 'ff-version': 87, - 'repo': 'https://hg.mozilla.org/mozilla-central' }, 'Command': {'test_mappings': None}, 'Logging': localconfig['Logging'], @@ -126,7 +125,7 @@ def _setup(self, 'assert_assignee_func': assert_assignee_func, 'assert_prior_bug_reference': assert_prior_bug_reference }, - 'Mercurial': {}, + 'Git': {}, 'Taskcluster': { 'url_treeherder': 'http://localhost:27490/', 'url_taskcluster': 'http://localhost:27490/', @@ -1680,7 +1679,6 @@ def abandon_callback(cmd): config_dictionary = copy.deepcopy(u.config_dictionary) config_dictionary['Database']['keep_tmp_db'] = False config_dictionary['General']['ff-version'] += 1 - config_dictionary['General']['repo'] = "https://hg.mozilla.org/mozilla-beta" u = Updatebot(config_dictionary, PROVIDERS) diff --git a/tests/functionality_commitalert.py b/tests/functionality_commitalert.py index 79504b60..4b7bed22 100755 --- a/tests/functionality_commitalert.py +++ b/tests/functionality_commitalert.py @@ -128,7 +128,7 @@ def bug_has_landing_link(self, bug_id): 'Library': MockLibraryProvider, # Not mocked 'SCM': SCMProvider, - 'Mercurial': NeverUseMeClass, + 'Git': NeverUseMeClass, 'Taskcluster': NeverUseMeClass, 'Vendor': NeverUseMeClass, 'Phabricator': NeverUseMeClass, @@ -159,7 +159,6 @@ def _setup(current_library_version_func, 'env': 'dev', 'gecko-path': '.', 'ff-version': 87, - 'repo': 'https://hg.mozilla.org/mozilla-central' }, 'Command': { 'test_mappings': None, @@ -174,7 +173,7 @@ def _setup(current_library_version_func, 'filed_bug_ids_func': filed_bug_ids_func, 'assert_affected_func': assert_affected_func }, - 'Mercurial': {}, + 'Git': {}, 'Taskcluster': {}, 'Phabricator': {}, 'Library': { @@ -606,7 +605,6 @@ def assert_affected(bug_id, ff_version, affected): config_dictionary = copy.deepcopy(u.config_dictionary) config_dictionary['Database']['keep_tmp_db'] = False config_dictionary['General']['ff-version'] += 1 - config_dictionary['General']['repo'] = "https://hg.mozilla.org/mozilla-beta" call_counter += 1 @@ -710,7 +708,6 @@ def assert_affected(bug_id, ff_version, affected): config_dictionary = copy.deepcopy(u.config_dictionary) config_dictionary['Database']['keep_tmp_db'] = False config_dictionary['General']['ff-version'] += 1 - config_dictionary['General']['repo'] = "https://hg.mozilla.org/mozilla-beta" call_counter += 1 @@ -813,7 +810,6 @@ def assert_affected(bug_id, ff_version, affected): config_dictionary = copy.deepcopy(u.config_dictionary) config_dictionary['Database']['keep_tmp_db'] = False config_dictionary['General']['ff-version'] += 1 - config_dictionary['General']['repo'] = "https://hg.mozilla.org/mozilla-beta" call_counter += 1 @@ -880,7 +876,6 @@ def assert_affected(bug_id, ff_version, affected): config_dictionary = copy.deepcopy(u.config_dictionary) config_dictionary['Database']['keep_tmp_db'] = False config_dictionary['General']['ff-version'] += 1 - config_dictionary['General']['repo'] = "https://hg.mozilla.org/mozilla-beta" call_counter += 1 diff --git a/tests/functionality_two_platforms.py b/tests/functionality_two_platforms.py index a24677c7..06fd4204 100755 --- a/tests/functionality_two_platforms.py +++ b/tests/functionality_two_platforms.py @@ -27,7 +27,7 @@ from components.dbc import DatabaseProvider from components.dbmodels import JOBSTATUS, JOBOUTCOME from components.mach_vendor import VendorProvider -from components.hg import MercurialProvider +from components.git import GitProvider from components.scmprovider import SCMProvider from apis.taskcluster import TaskclusterProvider from apis.phabricator import PhabricatorProvider @@ -86,7 +86,7 @@ def COMMAND_MAPPINGS(expected_values, command_callbacks): # bugzilla server which provides no additional logic coverage 'Bugzilla': MockedBugzillaProvider, # Not Mocked At All - 'Mercurial': MercurialProvider, + 'Git': GitProvider, # Not Mocked At All, but does point to a fake server 'Taskcluster': TaskclusterProvider, # Not Mocked At All @@ -121,7 +121,6 @@ def _setup(self, 'env': 'dev', 'gecko-path': '.', 'ff-version': 87, - 'repo': 'https://hg.mozilla.org/mozilla-central', 'separate-platforms': True }, 'Command': {'test_mappings': None}, @@ -135,7 +134,7 @@ def _setup(self, 'assert_assignee_func': assert_assignee_func, 'assert_prior_bug_reference': assert_prior_bug_reference }, - 'Mercurial': {}, + 'Git': {}, 'Taskcluster': { 'url_treeherder': 'http://localhost:27490/', 'url_taskcluster': 'http://localhost:27490/', @@ -1713,7 +1712,6 @@ def abandon_callback(cmd): config_dictionary = copy.deepcopy(u.config_dictionary) config_dictionary['Database']['keep_tmp_db'] = False config_dictionary['General']['ff-version'] += 1 - config_dictionary['General']['repo'] = "https://hg.mozilla.org/mozilla-beta" u = Updatebot(config_dictionary, PROVIDERS) diff --git a/tests/functionality_utilities.py b/tests/functionality_utilities.py index 5ad07cb0..7f384409 100644 --- a/tests/functionality_utilities.py +++ b/tests/functionality_utilities.py @@ -69,13 +69,7 @@ def default_phab_submit(): ("./mach vendor --patch-mode only", command_callbacks.get('patch', AssertFalse)), ("./mach vendor --check-for-update", command_callbacks.get('check_for_update', lambda: expected_values.library_new_version_id() + " 2020-08-21T15:13:49.000+02:00")), ("./mach vendor ", command_callbacks.get('vendor', lambda: "")), - ("hg commit", command_callbacks.get('commit', lambda: "")), - ("hg checkout -C .", lambda: ""), - ("hg purge .", lambda: ""), - ("hg status", lambda: ""), - ("hg strip", lambda: ""), - ("hg log -r tip --template", lambda: "a61d07a72763"), - ("hg diff --stat", lambda: " accessible/interfaces/ia2/moz.build | 6 +++---\n 1 files changed, 3 insertions(+), 3 deletions(-)\n"), + ("git commit", command_callbacks.get('commit', lambda: "")), ("arc diff --verbatim", command_callbacks.get('phab_submit', default_phab_submit)), ("arcanist diff --verbatim", command_callbacks.get('phab_submit', default_phab_submit)), (echo_str("echo {\"constraints\""), lambda: CONDUIT_USERNAME_SEARCH_OUTPUT), # This is also used for differential.revision.search, that's alright