Repository: easybuilders/easybuild-framework

Issue:
--merge-pr causes a traceback when the PR is already merged
```
Adding comment to easybuild-easyconfigs issue #11679: 'Going in, thanks @boegel!'
Traceback (most recent call last):
  File "/usr/lib64/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib64/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/mnt/ds918plus/eb/nuc-fedora32/software/EasyBuild/4.3.1/lib/python3.8/site-packages/easybuild/main.py", line 533, in <module>
    main()
  File "/mnt/ds918plus/eb/nuc-fedora32/software/EasyBuild/4.3.1/lib/python3.8/site-packages/easybuild/main.py", line 251, in main
    merge_pr(options.merge_pr)
  File "/mnt/ds918plus/eb/nuc-fedora32/software/EasyBuild/4.3.1/lib/python3.8/site-packages/easybuild/tools/github.py", line 1313, in merge_pr
    github_api_put_request(merge_url, github_user, body=body)
  File "/mnt/ds918plus/eb/nuc-fedora32/software/EasyBuild/4.3.1/lib/python3.8/site-packages/easybuild/tools/github.py", line 281, in github_api_put_request
    status, data = url.put(**kwargs)
  File "/mnt/ds918plus/eb/nuc-fedora32/software/EasyBuild/4.3.1/lib/python3.8/site-packages/easybuild/base/rest.py", line 145, in put
    return self.request(self.PUT, url, json.dumps(body), headers, content_type='application/json')
  File "/mnt/ds918plus/eb/nuc-fedora32/software/EasyBuild/4.3.1/lib/python3.8/site-packages/easybuild/base/rest.py", line 174, in request
    conn = self.get_connection(method, url, body, headers)
  File "/mnt/ds918plus/eb/nuc-fedora32/software/EasyBuild/4.3.1/lib/python3.8/site-packages/easybuild/base/rest.py", line 215, in get_connection
    connection = self.opener.open(request)
  File "/usr/lib64/python3.8/urllib/request.py", line 531, in open
    response = meth(req, response)
  File "/usr/lib64/python3.8/urllib/request.py", line 640, in http_response
    response = self.parent.error(
  File "/usr/lib64/python3.8/urllib/request.py", line 569, in error
    return self._call_chain(*args)
  File "/usr/lib64/python3.8/urllib/request.py", line 502, in _call_chain
    result = func(*args)
  File "/usr/lib64/python3.8/urllib/request.py", line 649, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 405: Method Not Allowed
```


Discussion:

Resolving patch:
diff --git a/easybuild/tools/github.py b/easybuild/tools/github.py
--- a/easybuild/tools/github.py
+++ b/easybuild/tools/github.py
@@ -1331,6 +1331,12 @@ def merge_pr(pr):
     force = build_option('force')
     dry_run = build_option('dry_run') or build_option('extended_dry_run')
 
+    if not dry_run:
+        if pr_data['merged']:
+            raise EasyBuildError("This PR is already merged.")
+        elif pr_data['state'] == GITHUB_STATE_CLOSED:
+            raise EasyBuildError("This PR is closed.")
+
     def merge_url(gh):
         """Utility function to fetch merge URL for a specific PR."""
         return gh.repos[pr_target_account][pr_target_repo].pulls[pr].merge


Test patch:
diff --git a/test/framework/options.py b/test/framework/options.py
--- a/test/framework/options.py
+++ b/test/framework/options.py
@@ -3985,6 +3985,29 @@ def test_merge_pr(self):
             print("Skipping test_merge_pr, no GitHub token available?")
             return
 
+        # start by making sure --merge-pr without dry-run errors out for a closed PR
+        args = [
+            '--merge-pr',
+            '11753',  # closed PR
+            '--github-user=%s' % GITHUB_TEST_ACCOUNT,
+        ]
+        error_msg = r"This PR is closed."
+        self.mock_stdout(True)
+        self.assertErrorRegex(EasyBuildError, error_msg, self.eb_main, args, raise_error=True)
+        self.mock_stdout(False)
+
+        # and also for an already merged PR
+        args = [
+            '--merge-pr',
+            '11769',  # already merged PR
+            '--github-user=%s' % GITHUB_TEST_ACCOUNT,
+        ]
+        error_msg = r"This PR is already merged."
+        self.mock_stdout(True)
+        self.assertErrorRegex(EasyBuildError, error_msg, self.eb_main, args, raise_error=True)
+        self.mock_stdout(False)
+
+        # merged PR for EasyBuild-3.3.0.eb, is missing approved review
         args = [
             '--merge-pr',
             '4781',  # PR for easyconfig for EasyBuild-3.3.0.eb
@@ -3993,7 +4016,6 @@ def test_merge_pr(self):
             '--pr-target-branch=some_branch',
         ]
 
-        # merged PR for EasyBuild-3.3.0.eb, is missing approved review
         stdout, stderr = self._run_mock_eb(args, do_build=True, raise_error=True, testing=False)
 
         expected_stdout = '\n'.join([