Repository: PyGithub/PyGithub

Issue:
Branch rename endpoint
Support for calling the branch rename endpoint would be helpful. I'm using this endpoint (through a raw API call) to mass-update an existing organization's repositories:
https://docs.github.com/en/rest/reference/repos#rename-a-branch


Discussion:

Resolving patch:
diff --git a/github/Repository.py b/github/Repository.py
--- a/github/Repository.py
+++ b/github/Repository.py
@@ -1644,6 +1644,27 @@ def get_branch(self, branch):
         )
         return github.Branch.Branch(self._requester, headers, data, completed=True)
 
+    def rename_branch(self, branch, new_name):
+        """
+        :calls: `POST /repos/{owner}/{repo}/branches/{branch}/rename <https://docs.github.com/en/rest/reference/repos#branches>`
+        :param branch: :class:`github.Branch.Branch` or string
+        :param new_name: string
+        :rtype: bool
+
+        NOTE: This method does not return the branch since it may take some
+        time to fully complete server-side.
+        """
+        is_branch = isinstance(branch, github.Branch.Branch)
+        assert isinstance(branch, str) or is_branch, branch
+        assert isinstance(new_name, str), new_name
+        if is_branch:
+            branch = branch.name
+        parameters = {"new_name": new_name}
+        status, _, _ = self._requester.requestJson(
+            "POST", f"{self.url}/branches/{branch}/rename", input=parameters
+        )
+        return status == 201
+
     def get_branches(self):
         """
         :calls: `GET /repos/{owner}/{repo}/branches <https://docs.github.com/en/rest/reference/repos>`_


Test patch:
diff --git a/tests/ReplayData/Repository.testRenameBranchObject.txt b/tests/ReplayData/Repository.testRenameBranchObject.txt
new file mode 100644
--- /dev/null
+++ b/tests/ReplayData/Repository.testRenameBranchObject.txt
@@ -0,0 +1,22 @@
+https
+GET
+api.github.com
+None
+/repos/jacquev6/PyGithub/branches/neat-new-feature
+{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+None
+200
+[('Server', 'GitHub.com'), ('Date', 'Sat, 23 Oct 2021 04:32:06 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With'), ('ETag', 'W/"7b4b9377dbba34db4d606bcfaadf8997f690d2fb7afc15982857269ba17e8a90"'), ('X-OAuth-Scopes', 'admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, repo, user, workflow, write:discussion, write:packages'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4998'), ('X-RateLimit-Reset', '1634967125'), ('X-RateLimit-Used', '2'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', '82FE:7C71:69CE0C:7107D9:61739046')]
+{"name":"neat-new-feature","commit":{"sha":"dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","node_id":"C_kwDOB7W4ZNoAKGRiZmJmN2YwN2I4MWQ2MTZkNGRhMDMyYmNmYTdjYTkwZGNkNzk1NzU","commit":{"author":{"name":"Steve Kowalik","email":"steven@wedontsleep.org","date":"2021-10-21T04:06:06Z"},"committer":{"name":"Steve Kowalik","email":"steven@wedontsleep.org","date":"2021-10-21T04:06:06Z"},"message":"WIP: branch rename","tree":{"sha":"81e30fe50ff2b1c8e8cb819d181b3a285e9a9c94","url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees/81e30fe50ff2b1c8e8cb819d181b3a285e9a9c94"},"url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/jacquev6/PyGithub/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","html_url":"https://github.com/jacquev6/PyGithub/commit/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575/comments","author":{"login":"jacquev6","id":15225059,"node_id":"MDQ6VXNlcjE1MjI1MDU5","avatar_url":"https://avatars.githubusercontent.com/u/15225059?v=4","gravatar_id":"","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","site_admin":false},"committer":{"login":"jacquev6","id":15225059,"node_id":"MDQ6VXNlcjE1MjI1MDU5","avatar_url":"https://avatars.githubusercontent.com/u/15225059?v=4","gravatar_id":"","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","site_admin":false},"parents":[{"sha":"93b92cd2fce560080dc66aef3b94427623046c5d","url":"https://api.github.com/repos/jacquev6/PyGithub/commits/93b92cd2fce560080dc66aef3b94427623046c5d","html_url":"https://github.com/jacquev6/PyGithub/commit/93b92cd2fce560080dc66aef3b94427623046c5d"}]},"_links":{"self":"https://api.github.com/repos/jacquev6/PyGithub/branches/neat-new-feature","html":"https://github.com/jacquev6/PyGithub/tree/neat-new-feature"},"protected":false,"protection":{"enabled":false,"required_status_checks":{"enforcement_level":"off","contexts":[]}},"protection_url":"https://api.github.com/repos/jacquev6/PyGithub/branches/neat-new-feature/protection"}
+
+https
+POST
+api.github.com
+None
+/repos/jacquev6/PyGithub/branches/neat-new-feature/rename
+{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
+{"new_name": "terrible-idea"}
+201
+[('Server', 'GitHub.com'), ('Date', 'Sat, 23 Oct 2021 04:32:07 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Content-Length', '3748'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With'), ('ETag', '"21b924f024389f2dad56ec7b61664d88fa432e59b6068797da15b3b1718799a1"'), ('X-OAuth-Scopes', 'admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, repo, user, workflow, write:discussion, write:packages'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4996'), ('X-RateLimit-Reset', '1634967125'), ('X-RateLimit-Used', '4'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-GitHub-Request-Id', '8302:7C71:69CE3F:710812:61739046')]
+{"name":"terrible-idea","commit":{"sha":"dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","node_id":"C_kwDOB7W4ZNoAKGRiZmJmN2YwN2I4MWQ2MTZkNGRhMDMyYmNmYTdjYTkwZGNkNzk1NzU","commit":{"author":{"name":"Steve Kowalik","email":"steven@wedontsleep.org","date":"2021-10-21T04:06:06Z"},"committer":{"name":"Steve Kowalik","email":"steven@wedontsleep.org","date":"2021-10-21T04:06:06Z"},"message":"WIP: branch rename","tree":{"sha":"81e30fe50ff2b1c8e8cb819d181b3a285e9a9c94","url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees/81e30fe50ff2b1c8e8cb819d181b3a285e9a9c94"},"url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/jacquev6/PyGithub/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","html_url":"https://github.com/jacquev6/PyGithub/commit/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575/comments","author":{"login":"jacquev6","id":15225059,"node_id":"MDQ6VXNlcjE1MjI1MDU5","avatar_url":"https://avatars.githubusercontent.com/u/15225059?v=4","gravatar_id":"","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","site_admin":false},"committer":{"login":"jacquev6","id":15225059,"node_id":"MDQ6VXNlcjE1MjI1MDU5","avatar_url":"https://avatars.githubusercontent.com/u/15225059?v=4","gravatar_id":"","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","site_admin":false},"parents":[{"sha":"93b92cd2fce560080dc66aef3b94427623046c5d","url":"https://api.github.com/repos/jacquev6/PyGithub/commits/93b92cd2fce560080dc66aef3b94427623046c5d","html_url":"https://github.com/jacquev6/PyGithub/commit/93b92cd2fce560080dc66aef3b94427623046c5d"}]},"_links":{"self":"https://api.github.com/repos/jacquev6/PyGithub/branches/terrible-idea","html":"https://github.com/jacquev6/PyGithub/tree/terrible-idea"},"protected":false,"protection":{"enabled":false,"required_status_checks":{"enforcement_level":"off","contexts":[]}},"protection_url":"https://api.github.com/repos/jacquev6/PyGithub/branches/terrible-idea/protection"}
+
diff --git a/tests/ReplayData/Repository.testRenameBranchString.txt b/tests/ReplayData/Repository.testRenameBranchString.txt
new file mode 100644
--- /dev/null
+++ b/tests/ReplayData/Repository.testRenameBranchString.txt
@@ -0,0 +1,11 @@
+https
+POST
+api.github.com
+None
+/repos/jacquev6/PyGithub/branches/neat-new-feature/rename
+{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
+{"new_name": "terrible-idea"}
+201
+[('Server', 'GitHub.com'), ('Date', 'Sat, 23 Oct 2021 04:32:07 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Content-Length', '3748'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With'), ('ETag', '"21b924f024389f2dad56ec7b61664d88fa432e59b6068797da15b3b1718799a1"'), ('X-OAuth-Scopes', 'admin:enterprise, admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, repo, user, workflow, write:discussion, write:packages'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4996'), ('X-RateLimit-Reset', '1634967125'), ('X-RateLimit-Used', '4'), ('X-RateLimit-Resource', 'core'), ('Access-Control-Expose-Headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '0'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-GitHub-Request-Id', '8302:7C71:69CE3F:710812:61739046')]
+{"name":"terrible-idea","commit":{"sha":"dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","node_id":"C_kwDOB7W4ZNoAKGRiZmJmN2YwN2I4MWQ2MTZkNGRhMDMyYmNmYTdjYTkwZGNkNzk1NzU","commit":{"author":{"name":"Steve Kowalik","email":"steven@wedontsleep.org","date":"2021-10-21T04:06:06Z"},"committer":{"name":"Steve Kowalik","email":"steven@wedontsleep.org","date":"2021-10-21T04:06:06Z"},"message":"WIP: branch rename","tree":{"sha":"81e30fe50ff2b1c8e8cb819d181b3a285e9a9c94","url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees/81e30fe50ff2b1c8e8cb819d181b3a285e9a9c94"},"url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/jacquev6/PyGithub/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","html_url":"https://github.com/jacquev6/PyGithub/commit/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/commits/dbfbf7f07b81d616d4da032bcfa7ca90dcd79575/comments","author":{"login":"jacquev6","id":15225059,"node_id":"MDQ6VXNlcjE1MjI1MDU5","avatar_url":"https://avatars.githubusercontent.com/u/15225059?v=4","gravatar_id":"","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","site_admin":false},"committer":{"login":"jacquev6","id":15225059,"node_id":"MDQ6VXNlcjE1MjI1MDU5","avatar_url":"https://avatars.githubusercontent.com/u/15225059?v=4","gravatar_id":"","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","site_admin":false},"parents":[{"sha":"93b92cd2fce560080dc66aef3b94427623046c5d","url":"https://api.github.com/repos/jacquev6/PyGithub/commits/93b92cd2fce560080dc66aef3b94427623046c5d","html_url":"https://github.com/jacquev6/PyGithub/commit/93b92cd2fce560080dc66aef3b94427623046c5d"}]},"_links":{"self":"https://api.github.com/repos/jacquev6/PyGithub/branches/terrible-idea","html":"https://github.com/jacquev6/PyGithub/tree/terrible-idea"},"protected":false,"protection":{"enabled":false,"required_status_checks":{"enforcement_level":"off","contexts":[]}},"protection_url":"https://api.github.com/repos/jacquev6/PyGithub/branches/terrible-idea/protection"}
+
diff --git a/tests/Repository.py b/tests/Repository.py
--- a/tests/Repository.py
+++ b/tests/Repository.py
@@ -1323,6 +1323,13 @@ def testGetBranch(self):
         branch = self.repo.get_branch("develop")
         self.assertEqual(branch.commit.sha, "03058a36164d2a7d946db205f25538434fa27d94")
 
+    def testRenameBranchObject(self):
+        branch = self.repo.get_branch("neat-new-feature")
+        self.assertTrue(self.repo.rename_branch(branch, "terrible-idea"))
+
+    def testRenameBranchString(self):
+        self.assertTrue(self.repo.rename_branch("neat-new-feature", "terrible-idea"))
+
     def testMergeWithoutMessage(self):
         commit = self.repo.merge("branchForBase", "branchForHead")
         self.assertEqual(