diff --git a/miss_islington/tasks.py b/miss_islington/tasks.py index 6f1cd745..89d1d6d9 100644 --- a/miss_islington/tasks.py +++ b/miss_islington/tasks.py @@ -1,4 +1,5 @@ import asyncio +import logging import os import ssl import subprocess @@ -15,6 +16,7 @@ from . import util +logger = logging.getLogger(__name__) app = celery.Celery("backport_cpython") @@ -116,16 +118,39 @@ async def backport_task_asyncio( # Ensure that we don't have any changes lying around subprocess.check_output(['git', 'reset', '--hard']) subprocess.check_output(['git', 'clean', '-fxd']) - - cp = cherry_picker.CherryPicker( - "origin", - commit_hash, - [branch], - config=CHERRY_PICKER_CONFIG, - prefix_commit=False, + # Clear any leftover cherry-picker state from a previously interrupted + # run; otherwise CherryPicker() init raises InvalidRepoException and + # every subsequent backport on this worker silently fails. + subprocess.run( + ['git', 'config', '--local', '--remove-section', 'cherry-picker'], + stderr=subprocess.DEVNULL, + check=False, ) + try: + cp = cherry_picker.CherryPicker( + "origin", + commit_hash, + [branch], + config=CHERRY_PICKER_CONFIG, + prefix_commit=False, + ) cp.backport() + except cherry_picker.InvalidRepoException as ire: + await util.comment_on_pr( + gh, + issue_number, + f"""\ + Sorry {util.get_participants(created_by, merged_by)}, I had trouble backporting to `{branch}`. + Please retry by removing and re-adding the "needs backport to {branch}" label. + Alternatively, you can backport using [cherry_picker](https://pypi.org/project/cherry-picker/) on the command line. + ``` + cherry_picker {commit_hash} {branch} + ``` + """, + ) + await util.assign_pr_to_core_dev(gh, issue_number, merged_by) + logger.exception("InvalidRepoException while backporting to %s", branch) except cherry_picker.BranchCheckoutException as bce: await util.comment_on_pr( gh, diff --git a/tests/test_tasks.py b/tests/test_tasks.py new file mode 100644 index 00000000..ae7256fe --- /dev/null +++ b/tests/test_tasks.py @@ -0,0 +1,60 @@ +import os +import subprocess +from unittest import mock + +from cherry_picker import cherry_picker + +os.environ.setdefault("HEROKU_REDIS_MAROON_URL", "someurl") + +from miss_islington import tasks + + +async def test_invalid_repo_exception_posts_comment_and_clears_state(): + """A stuck cherry-picker.state in git config used to wedge every + subsequent backport: CherryPicker() init raised InvalidRepoException + and the task crashed with no comment posted. The task now wipes + stale state pre-flight and posts an error comment if init still fails. + """ + with ( + mock.patch("miss_islington.tasks.aiohttp.ClientSession"), + mock.patch( + "miss_islington.tasks.apps.get_installation_access_token", + new=mock.AsyncMock(return_value={"token": "test-token"}), + ), + mock.patch("miss_islington.tasks.util.is_cpython_repo", return_value=True), + mock.patch("miss_islington.tasks.subprocess.check_output"), + mock.patch("miss_islington.tasks.subprocess.run") as run_mock, + mock.patch( + "miss_islington.tasks.cherry_picker.CherryPicker", + side_effect=cherry_picker.InvalidRepoException("stuck state"), + ), + mock.patch( + "miss_islington.tasks.util.comment_on_pr", new=mock.AsyncMock() + ) as comment_mock, + mock.patch( + "miss_islington.tasks.util.assign_pr_to_core_dev", new=mock.AsyncMock() + ) as assign_mock, + ): + await tasks.backport_task_asyncio( + "7a4c6dfb8839eb05fb87baf70364680e45001dd4", + "3.15", + issue_number=130749, + created_by="medmunds", + merged_by="bitdancer", + installation_id=42958231, + ) + + run_mock.assert_any_call( + ["git", "config", "--local", "--remove-section", "cherry-picker"], + stderr=subprocess.DEVNULL, + check=False, + ) + + comment_mock.assert_awaited_once() + posted_message = comment_mock.await_args.args[2] + assert "3.15" in posted_message + assert "@bitdancer" in posted_message + + assign_mock.assert_awaited_once() + assert assign_mock.await_args.args[1] == 130749 + assert assign_mock.await_args.args[2] == "bitdancer"