diff --git a/config/dev/sync.ini b/config/dev/sync.ini index 7597892e3..bc70f8584 100644 --- a/config/dev/sync.ini +++ b/config/dev/sync.ini @@ -57,4 +57,5 @@ listener.interval = 60 [lando] api_token=%SECRET% +api_url = https://lando.moz.tools/api user_email=wptsync@mozilla.com diff --git a/sync/commit.py b/sync/commit.py index 0d74a93f1..1c4228747 100644 --- a/sync/commit.py +++ b/sync/commit.py @@ -11,6 +11,7 @@ from . import log from .env import Environment from .errors import AbortError +from .lando import git2hg from .repos import cinnabar, cinnabar_map, pygit2_get from typing import Dict @@ -544,8 +545,16 @@ def has_wpt_changes(self) -> bool: @property def is_backout(self) -> bool: + return self.is_hg_backout or self.is_git_revert + + @property + def is_hg_backout(self) -> bool: return commitparser.is_backout(self.msg) + @property + def is_git_revert(self) -> bool: + return commitparser.parse_reverts(self.msg) is not None + @property def is_downstream(self) -> bool: from . import downstream @@ -563,7 +572,13 @@ def commits_backed_out(self) -> tuple[list[GeckoCommit], set[int]]: commits: list[GeckoCommit] = [] bugs: list[int] = [] if self.is_backout: - nodes_bugs = commitparser.parse_backouts(self.msg) + nodes_bugs = None + + if self.is_hg_backout: + nodes_bugs = commitparser.parse_backouts(self.msg) + elif self.is_git_revert: + nodes_bugs = commitparser.parse_reverts(self.msg) + if nodes_bugs is None: # We think this a backout, but have no idea what it backs out # it's not clear how to handle that case so for now we pretend it isn't @@ -573,7 +588,11 @@ def commits_backed_out(self) -> tuple[list[GeckoCommit], set[int]]: nodes, bugs = nodes_bugs # Assuming that all commits are listed. for node in nodes: - git_sha = cinnabar(self.repo).hg2git(node.decode("ascii")) + hg_revision = node.decode("ascii") + if self.is_git_revert: + # Convert original git hash to mercurial + hg_revision = git2hg(hg_revision) + git_sha = cinnabar(self.repo).hg2git(hg_revision) commits.append(GeckoCommit(self.repo, git_sha)) return commits, set(bugs) diff --git a/sync/lando.py b/sync/lando.py new file mode 100644 index 000000000..075663a03 --- /dev/null +++ b/sync/lando.py @@ -0,0 +1,21 @@ +import json +import urllib.request + +from .env import Environment +from . import log + +env = Environment() + +logger = log.get_logger(__name__) + + +def git2hg(git_hash: str) -> str: + response = urllib.request.urlopen( + env.config["lando"]["api_url"] + "/git2hg/firefox/" + git_hash + ) # nosec B310 + data = response.read() + map = json.loads(data.decode("utf-8")) + assert isinstance(map, dict) + assert isinstance(map["hg_hash"], str) + + return map["hg_hash"] diff --git a/sync_prod.ini b/sync_prod.ini index 6436ebfaa..ca10bad67 100644 --- a/sync_prod.ini +++ b/sync_prod.ini @@ -107,4 +107,5 @@ repo.url = https://github.com/web-platform-tests/wpt-metadata [lando] api_token=%SECRET% +api_url = https://lando.moz.tools/api user_email=wptsync@mozilla.com diff --git a/test/config/sync.ini b/test/config/sync.ini index 331928718..c41c82094 100644 --- a/test/config/sync.ini +++ b/test/config/sync.ini @@ -63,4 +63,5 @@ listener.interval = 60 [lando] api_token="%SECRET%" +api_url = https://lando.moz.tools/api user_email="wptsync@mozilla.com" diff --git a/test/conftest.py b/test/conftest.py index bd347342e..3fa89b435 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,5 +1,6 @@ import copy import glob +import hashlib import io import json import os @@ -383,6 +384,20 @@ def inner(revs, bugs, message=None, bookmarks="mozilla/autoland"): return inner +@pytest.fixture +def upstream_gecko_revert(env, hg_gecko_upstream): + def inner(message, rev, bookmarks="mozilla/autoland"): + # Git hash has to be converted to hg hash with API. + # Since we going to mock this API call, this git hash + # can have a random value that just looks like git hash. + random_hash = hashlib.sha1(os.urandom(20)).hexdigest() + commit_message = f"""Revert \"{message}\"\nThis reverts commit {random_hash}.""".encode() + hg_gecko_upstream.backout("--no-commit", rev) + return hg_commit(hg_gecko_upstream, commit_message, bookmarks) + + return inner + + @pytest.fixture def gecko_worktree(env, git_gecko): path = os.path.join(env.config["root"], env.config["paths"]["worktrees"], "gecko", "autoland") diff --git a/test/test_upstream.py b/test/test_upstream.py index 5ae8f1300..a662a8b6b 100644 --- a/test/test_upstream.py +++ b/test/test_upstream.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + from sync import commit as sync_commit, upstream from sync.gitutils import update_repositories from sync.lock import SyncLock @@ -84,6 +86,44 @@ def test_create_pr_backout(git_gecko, git_wpt, upstream_gecko_commit, upstream_g assert backout_commit.upstream_sync(git_gecko, git_wpt) == sync +def test_create_pr_revert(git_gecko, git_wpt, upstream_gecko_commit, upstream_gecko_revert): + bug = 1234 + test_changes = {"README": b"Change README\n"} + message = f"Bug {bug} - Change README" + rev = upstream_gecko_commit(test_changes=test_changes, bug=bug, message=message.encode()) + + update_repositories(git_gecko, git_wpt, wait_gecko_commit=rev) + upstream.gecko_push(git_gecko, git_wpt, "autoland", rev, raise_on_error=True) + + syncs = upstream.UpstreamSync.for_bug(git_gecko, git_wpt, bug) + assert list(syncs.keys()) == ["open"] + assert len(syncs["open"]) == 1 + sync = syncs["open"].pop() + assert sync.bug == 1234 + assert sync.status == "open" + assert len(sync.gecko_commits) == 1 + assert len(sync.wpt_commits) == 1 + assert sync.pr + + backout_rev = upstream_gecko_revert(message, rev) + + update_repositories(git_gecko, git_wpt, wait_gecko_commit=backout_rev) + with patch("sync.commit.git2hg", return_value=rev): + upstream.gecko_push(git_gecko, git_wpt, "autoland", backout_rev, raise_on_error=True) + syncs = upstream.UpstreamSync.for_bug(git_gecko, git_wpt, bug) + assert list(syncs.keys()) == ["incomplete"] + assert len(syncs["incomplete"]) == 1 + sync = syncs["incomplete"].pop() + assert sync.bug == 1234 + with patch("sync.commit.git2hg", return_value=rev): + assert len(sync.gecko_commits) == 0 + assert len(sync.wpt_commits) == 1 + assert len(sync.upstreamed_gecko_commits) == 1 + assert sync.status == "incomplete" + backout_commit = sync_commit.GeckoCommit(git_gecko, cinnabar(git_gecko).hg2git(rev)) + assert backout_commit.upstream_sync(git_gecko, git_wpt) == sync + + def test_create_pr_backout_reland( git_gecko, git_wpt, upstream_gecko_commit, upstream_gecko_backout ):