We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bitbucket cloud status update returns 400 for status_key longer than 40 characters
When using a status_key for reporters.BitbucketStatusPush, there is no check whether it is longer than 40 characters, which appears to be the limit that Bitbucket cloud accepts.
This error appears in the log file in cases where status_key exceeds 40 characters:
`400: unable to upload Bitbucket status b'{"type": "error", "error": {"message": "key: Ensure this value has at most 40 characters (it has 48).", "fields": {"key": ["Ensure this value has at most 40 characters (it has 48)."]}}}'`

Checking the length for this field might be difficult during configuration checking time, since it might exceed 40 characters only after a certain amount of builds have been done ( e.g. with the value Interpolate("%(prop:buildername)s/%(prop:buildnumber)s") )

I would propose hashing the status_key in order to get predictable key lengths. sha1 happens to be 20 bytes long which is 40 characters in hexadecimal notation.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/reporters/bitbucket.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 from urllib.parse import urlparse
17 
18 from twisted.internet import defer
19 from twisted.python import log
20 
21 from buildbot import config
22 from buildbot.process.properties import Properties
23 from buildbot.process.properties import Property
24 from buildbot.process.results import SUCCESS
25 from buildbot.reporters.base import ReporterBase
26 from buildbot.reporters.generators.build import BuildStartEndStatusGenerator
27 from buildbot.reporters.message import MessageFormatter
28 from buildbot.util import httpclientservice
29 
30 # Magic words understood by Butbucket REST API
31 BITBUCKET_INPROGRESS = 'INPROGRESS'
32 BITBUCKET_SUCCESSFUL = 'SUCCESSFUL'
33 BITBUCKET_FAILED = 'FAILED'
34 
35 _BASE_URL = 'https://api.bitbucket.org/2.0/repositories'
36 _OAUTH_URL = 'https://bitbucket.org/site/oauth2/access_token'
37 _GET_TOKEN_DATA = {
38     'grant_type': 'client_credentials'
39 }
40 
41 
42 class BitbucketStatusPush(ReporterBase):
43     name = "BitbucketStatusPush"
44 
45     def checkConfig(self, oauth_key=None, oauth_secret=None, auth=None, base_url=_BASE_URL,
46                     oauth_url=_OAUTH_URL, debug=None, verify=None, status_key=None,
47                     status_name=None, generators=None, **kwargs):
48         if auth is not None and (oauth_key is not None or oauth_secret is not None):
49             config.error('Either App Passwords or OAuth can be specified, not both')
50 
51         if generators is None:
52             generators = self._create_default_generators()
53 
54         super().checkConfig(generators=generators, **kwargs)
55         httpclientservice.HTTPClientService.checkAvailable(self.__class__.__name__)
56 
57     @defer.inlineCallbacks
58     def reconfigService(self, oauth_key=None, oauth_secret=None, auth=None, base_url=_BASE_URL,
59                         oauth_url=_OAUTH_URL, debug=None, verify=None, status_key=None,
60                         status_name=None, generators=None, **kwargs):
61         oauth_key, oauth_secret = yield self.renderSecrets(oauth_key, oauth_secret)
62         self.auth = yield self.renderSecrets(auth)
63         self.base_url = base_url
64         self.debug = debug
65         self.verify = verify
66         self.status_key = status_key or Property('buildername')
67         self.status_name = status_name or Property('buildername')
68 
69         if generators is None:
70             generators = self._create_default_generators()
71 
72         yield super().reconfigService(generators=generators, **kwargs)
73 
74         base_url = base_url.rstrip('/')
75 
76         self._http = yield httpclientservice.HTTPClientService.getService(
77             self.master, base_url,
78             debug=self.debug, verify=self.verify, auth=self.auth)
79 
80         self.oauthhttp = None
81         if self.auth is None:
82             self.oauthhttp = yield httpclientservice.HTTPClientService.getService(
83                 self.master, oauth_url, auth=(oauth_key, oauth_secret),
84                 debug=self.debug, verify=self.verify)
85 
86     def _create_default_generators(self):
87         return [
88             BuildStartEndStatusGenerator(
89                 start_formatter=MessageFormatter(subject="", template=''),
90                 end_formatter=MessageFormatter(subject="", template='')
91             )
92         ]
93 
94     @defer.inlineCallbacks
95     def sendMessage(self, reports):
96         # Only use OAuth if basic auth has not been specified
97         if not self.auth:
98             request = yield self.oauthhttp.post("", data=_GET_TOKEN_DATA)
99             if request.code != 200:
100                 content = yield request.content()
101                 log.msg(f"{request.code}: unable to authenticate to Bitbucket {content}")
102                 return
103             token = (yield request.json())['access_token']
104             self._http.updateHeaders({'Authorization': f'Bearer {token}'})
105 
106         build = reports[0]['builds'][0]
107         if build['complete']:
108             status = BITBUCKET_SUCCESSFUL if build['results'] == SUCCESS else BITBUCKET_FAILED
109         else:
110             status = BITBUCKET_INPROGRESS
111 
112         props = Properties.fromDict(build['properties'])
113         props.master = self.master
114 
115         body = {
116             'state': status,
117             'key': (yield props.render(self.status_key)),
118             'name': (yield props.render(self.status_name)),
119             'description': reports[0]['subject'],
120             'url': build['url']
121         }
122 
123         for sourcestamp in build['buildset']['sourcestamps']:
124             if not sourcestamp['repository']:
125                 log.msg(f"Empty repository URL for Bitbucket status {body}")
126                 continue
127             owner, repo = self.get_owner_and_repo(sourcestamp['repository'])
128 
129             endpoint = (owner, repo, 'commit', sourcestamp['revision'], 'statuses', 'build')
130             bitbucket_uri = f"/{'/'.join(endpoint)}"
131 
132             if self.debug:
133                 log.msg(f"Bitbucket status {bitbucket_uri} {body}")
134 
135             response = yield self._http.post(bitbucket_uri, json=body)
136             if response.code not in (200, 201):
137                 content = yield response.content()
138                 log.msg(f"{response.code}: unable to upload Bitbucket status {content}")
139 
140     def get_owner_and_repo(self, repourl):
141         """
142         Takes a git repository URL from Bitbucket and tries to determine the owner and repository
143         name
144         :param repourl: Bitbucket git repo in the form of
145                     git@bitbucket.org:OWNER/REPONAME.git
146                     https://bitbucket.org/OWNER/REPONAME.git
147                     ssh://git@bitbucket.org/OWNER/REPONAME.git
148                     https://api.bitbucket.org/2.0/repositories/OWNER/REPONAME
149         :return: owner, repo: The owner of the repository and the repository name
150         """
151         parsed = urlparse(repourl)
152 
153         base_parsed = urlparse(self.base_url)
154         if parsed.path.startswith(base_parsed.path):
155             path = parsed.path.replace(base_parsed.path, "")
156         elif parsed.scheme:
157             path = parsed.path
158         else:
159             # we assume git@host:owner/repo.git here
160             path = parsed.path.split(':', 1)[-1]
161 
162         path = path.lstrip('/')
163         if path.endswith('.git'):
164             path = path[:-4]
165         path = path.rstrip('/')
166 
167         parts = path.split('/')
168 
169         assert len(parts) == 2, 'OWNER/REPONAME is expected'
170 
171         return parts
172
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/master/buildbot/reporters/bitbucket.py b/master/buildbot/reporters/bitbucket.py
--- a/master/buildbot/reporters/bitbucket.py
+++ b/master/buildbot/reporters/bitbucket.py
@@ -13,6 +13,7 @@
 #
 # Copyright Buildbot Team Members
 
+import hashlib
 from urllib.parse import urlparse
 
 from twisted.internet import defer
@@ -112,9 +113,16 @@
         props = Properties.fromDict(build['properties'])
         props.master = self.master
 
+        def key_hash(key):
+            sha_obj = hashlib.sha1()
+            sha_obj.update(key.encode('utf-8'))
+            return sha_obj.hexdigest()
+
+        status_key = yield props.render(self.status_key)
+
         body = {
             'state': status,
-            'key': (yield props.render(self.status_key)),
+            'key': key_hash(status_key),
             'name': (yield props.render(self.status_name)),
             'description': reports[0]['subject'],
             'url': build['url']

{"golden_diff": "diff --git a/master/buildbot/reporters/bitbucket.py b/master/buildbot/reporters/bitbucket.py\n--- a/master/buildbot/reporters/bitbucket.py\n+++ b/master/buildbot/reporters/bitbucket.py\n@@ -13,6 +13,7 @@\n #\n # Copyright Buildbot Team Members\n \n+import hashlib\n from urllib.parse import urlparse\n \n from twisted.internet import defer\n@@ -112,9 +113,16 @@\n         props = Properties.fromDict(build['properties'])\n         props.master = self.master\n \n+        def key_hash(key):\n+            sha_obj = hashlib.sha1()\n+            sha_obj.update(key.encode('utf-8'))\n+            return sha_obj.hexdigest()\n+\n+        status_key = yield props.render(self.status_key)\n+\n         body = {\n             'state': status,\n-            'key': (yield props.render(self.status_key)),\n+            'key': key_hash(status_key),\n             'name': (yield props.render(self.status_name)),\n             'description': reports[0]['subject'],\n             'url': build['url']\n", "issue": "Bitbucket cloud status update returns 400 for status_key longer than 40 characters\nWhen using a status_key for reporters.BitbucketStatusPush, there is no check whether it is longer than 40 characters, which appears to be the limit that Bitbucket cloud accepts.\r\nThis error appears in the log file in cases where status_key exceeds 40 characters:\r\n`400: unable to upload Bitbucket status b'{\"type\": \"error\", \"error\": {\"message\": \"key: Ensure this value has at most 40 characters (it has 48).\", \"fields\": {\"key\": [\"Ensure this value has at most 40 characters (it has 48).\"]}}}'`\r\n\r\nChecking the length for this field might be difficult during configuration checking time, since it might exceed 40 characters only after a certain amount of builds have been done ( e.g. with the value Interpolate(\"%(prop:buildername)s/%(prop:buildnumber)s\") )\r\n\r\nI would propose hashing the status_key in order to get predictable key lengths. sha1 happens to be 20 bytes long which is 40 characters in hexadecimal notation.\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom urllib.parse import urlparse\n\nfrom twisted.internet import defer\nfrom twisted.python import log\n\nfrom buildbot import config\nfrom buildbot.process.properties import Properties\nfrom buildbot.process.properties import Property\nfrom buildbot.process.results import SUCCESS\nfrom buildbot.reporters.base import ReporterBase\nfrom buildbot.reporters.generators.build import BuildStartEndStatusGenerator\nfrom buildbot.reporters.message import MessageFormatter\nfrom buildbot.util import httpclientservice\n\n# Magic words understood by Butbucket REST API\nBITBUCKET_INPROGRESS = 'INPROGRESS'\nBITBUCKET_SUCCESSFUL = 'SUCCESSFUL'\nBITBUCKET_FAILED = 'FAILED'\n\n_BASE_URL = 'https://api.bitbucket.org/2.0/repositories'\n_OAUTH_URL = 'https://bitbucket.org/site/oauth2/access_token'\n_GET_TOKEN_DATA = {\n    'grant_type': 'client_credentials'\n}\n\n\nclass BitbucketStatusPush(ReporterBase):\n    name = \"BitbucketStatusPush\"\n\n    def checkConfig(self, oauth_key=None, oauth_secret=None, auth=None, base_url=_BASE_URL,\n                    oauth_url=_OAUTH_URL, debug=None, verify=None, status_key=None,\n                    status_name=None, generators=None, **kwargs):\n        if auth is not None and (oauth_key is not None or oauth_secret is not None):\n            config.error('Either App Passwords or OAuth can be specified, not both')\n\n        if generators is None:\n            generators = self._create_default_generators()\n\n        super().checkConfig(generators=generators, **kwargs)\n        httpclientservice.HTTPClientService.checkAvailable(self.__class__.__name__)\n\n    @defer.inlineCallbacks\n    def reconfigService(self, oauth_key=None, oauth_secret=None, auth=None, base_url=_BASE_URL,\n                        oauth_url=_OAUTH_URL, debug=None, verify=None, status_key=None,\n                        status_name=None, generators=None, **kwargs):\n        oauth_key, oauth_secret = yield self.renderSecrets(oauth_key, oauth_secret)\n        self.auth = yield self.renderSecrets(auth)\n        self.base_url = base_url\n        self.debug = debug\n        self.verify = verify\n        self.status_key = status_key or Property('buildername')\n        self.status_name = status_name or Property('buildername')\n\n        if generators is None:\n            generators = self._create_default_generators()\n\n        yield super().reconfigService(generators=generators, **kwargs)\n\n        base_url = base_url.rstrip('/')\n\n        self._http = yield httpclientservice.HTTPClientService.getService(\n            self.master, base_url,\n            debug=self.debug, verify=self.verify, auth=self.auth)\n\n        self.oauthhttp = None\n        if self.auth is None:\n            self.oauthhttp = yield httpclientservice.HTTPClientService.getService(\n                self.master, oauth_url, auth=(oauth_key, oauth_secret),\n                debug=self.debug, verify=self.verify)\n\n    def _create_default_generators(self):\n        return [\n            BuildStartEndStatusGenerator(\n                start_formatter=MessageFormatter(subject=\"\", template=''),\n                end_formatter=MessageFormatter(subject=\"\", template='')\n            )\n        ]\n\n    @defer.inlineCallbacks\n    def sendMessage(self, reports):\n        # Only use OAuth if basic auth has not been specified\n        if not self.auth:\n            request = yield self.oauthhttp.post(\"\", data=_GET_TOKEN_DATA)\n            if request.code != 200:\n                content = yield request.content()\n                log.msg(f\"{request.code}: unable to authenticate to Bitbucket {content}\")\n                return\n            token = (yield request.json())['access_token']\n            self._http.updateHeaders({'Authorization': f'Bearer {token}'})\n\n        build = reports[0]['builds'][0]\n        if build['complete']:\n            status = BITBUCKET_SUCCESSFUL if build['results'] == SUCCESS else BITBUCKET_FAILED\n        else:\n            status = BITBUCKET_INPROGRESS\n\n        props = Properties.fromDict(build['properties'])\n        props.master = self.master\n\n        body = {\n            'state': status,\n            'key': (yield props.render(self.status_key)),\n            'name': (yield props.render(self.status_name)),\n            'description': reports[0]['subject'],\n            'url': build['url']\n        }\n\n        for sourcestamp in build['buildset']['sourcestamps']:\n            if not sourcestamp['repository']:\n                log.msg(f\"Empty repository URL for Bitbucket status {body}\")\n                continue\n            owner, repo = self.get_owner_and_repo(sourcestamp['repository'])\n\n            endpoint = (owner, repo, 'commit', sourcestamp['revision'], 'statuses', 'build')\n            bitbucket_uri = f\"/{'/'.join(endpoint)}\"\n\n            if self.debug:\n                log.msg(f\"Bitbucket status {bitbucket_uri} {body}\")\n\n            response = yield self._http.post(bitbucket_uri, json=body)\n            if response.code not in (200, 201):\n                content = yield response.content()\n                log.msg(f\"{response.code}: unable to upload Bitbucket status {content}\")\n\n    def get_owner_and_repo(self, repourl):\n        \"\"\"\n        Takes a git repository URL from Bitbucket and tries to determine the owner and repository\n        name\n        :param repourl: Bitbucket git repo in the form of\n                    git@bitbucket.org:OWNER/REPONAME.git\n                    https://bitbucket.org/OWNER/REPONAME.git\n                    ssh://git@bitbucket.org/OWNER/REPONAME.git\n                    https://api.bitbucket.org/2.0/repositories/OWNER/REPONAME\n        :return: owner, repo: The owner of the repository and the repository name\n        \"\"\"\n        parsed = urlparse(repourl)\n\n        base_parsed = urlparse(self.base_url)\n        if parsed.path.startswith(base_parsed.path):\n            path = parsed.path.replace(base_parsed.path, \"\")\n        elif parsed.scheme:\n            path = parsed.path\n        else:\n            # we assume git@host:owner/repo.git here\n            path = parsed.path.split(':', 1)[-1]\n\n        path = path.lstrip('/')\n        if path.endswith('.git'):\n            path = path[:-4]\n        path = path.rstrip('/')\n\n        parts = path.split('/')\n\n        assert len(parts) == 2, 'OWNER/REPONAME is expected'\n\n        return parts\n", "path": "master/buildbot/reporters/bitbucket.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nimport hashlib\nfrom urllib.parse import urlparse\n\nfrom twisted.internet import defer\nfrom twisted.python import log\n\nfrom buildbot import config\nfrom buildbot.process.properties import Properties\nfrom buildbot.process.properties import Property\nfrom buildbot.process.results import SUCCESS\nfrom buildbot.reporters.base import ReporterBase\nfrom buildbot.reporters.generators.build import BuildStartEndStatusGenerator\nfrom buildbot.reporters.message import MessageFormatter\nfrom buildbot.util import httpclientservice\n\n# Magic words understood by Butbucket REST API\nBITBUCKET_INPROGRESS = 'INPROGRESS'\nBITBUCKET_SUCCESSFUL = 'SUCCESSFUL'\nBITBUCKET_FAILED = 'FAILED'\n\n_BASE_URL = 'https://api.bitbucket.org/2.0/repositories'\n_OAUTH_URL = 'https://bitbucket.org/site/oauth2/access_token'\n_GET_TOKEN_DATA = {\n    'grant_type': 'client_credentials'\n}\n\n\nclass BitbucketStatusPush(ReporterBase):\n    name = \"BitbucketStatusPush\"\n\n    def checkConfig(self, oauth_key=None, oauth_secret=None, auth=None, base_url=_BASE_URL,\n                    oauth_url=_OAUTH_URL, debug=None, verify=None, status_key=None,\n                    status_name=None, generators=None, **kwargs):\n        if auth is not None and (oauth_key is not None or oauth_secret is not None):\n            config.error('Either App Passwords or OAuth can be specified, not both')\n\n        if generators is None:\n            generators = self._create_default_generators()\n\n        super().checkConfig(generators=generators, **kwargs)\n        httpclientservice.HTTPClientService.checkAvailable(self.__class__.__name__)\n\n    @defer.inlineCallbacks\n    def reconfigService(self, oauth_key=None, oauth_secret=None, auth=None, base_url=_BASE_URL,\n                        oauth_url=_OAUTH_URL, debug=None, verify=None, status_key=None,\n                        status_name=None, generators=None, **kwargs):\n        oauth_key, oauth_secret = yield self.renderSecrets(oauth_key, oauth_secret)\n        self.auth = yield self.renderSecrets(auth)\n        self.base_url = base_url\n        self.debug = debug\n        self.verify = verify\n        self.status_key = status_key or Property('buildername')\n        self.status_name = status_name or Property('buildername')\n\n        if generators is None:\n            generators = self._create_default_generators()\n\n        yield super().reconfigService(generators=generators, **kwargs)\n\n        base_url = base_url.rstrip('/')\n\n        self._http = yield httpclientservice.HTTPClientService.getService(\n            self.master, base_url,\n            debug=self.debug, verify=self.verify, auth=self.auth)\n\n        self.oauthhttp = None\n        if self.auth is None:\n            self.oauthhttp = yield httpclientservice.HTTPClientService.getService(\n                self.master, oauth_url, auth=(oauth_key, oauth_secret),\n                debug=self.debug, verify=self.verify)\n\n    def _create_default_generators(self):\n        return [\n            BuildStartEndStatusGenerator(\n                start_formatter=MessageFormatter(subject=\"\", template=''),\n                end_formatter=MessageFormatter(subject=\"\", template='')\n            )\n        ]\n\n    @defer.inlineCallbacks\n    def sendMessage(self, reports):\n        # Only use OAuth if basic auth has not been specified\n        if not self.auth:\n            request = yield self.oauthhttp.post(\"\", data=_GET_TOKEN_DATA)\n            if request.code != 200:\n                content = yield request.content()\n                log.msg(f\"{request.code}: unable to authenticate to Bitbucket {content}\")\n                return\n            token = (yield request.json())['access_token']\n            self._http.updateHeaders({'Authorization': f'Bearer {token}'})\n\n        build = reports[0]['builds'][0]\n        if build['complete']:\n            status = BITBUCKET_SUCCESSFUL if build['results'] == SUCCESS else BITBUCKET_FAILED\n        else:\n            status = BITBUCKET_INPROGRESS\n\n        props = Properties.fromDict(build['properties'])\n        props.master = self.master\n\n        def key_hash(key):\n            sha_obj = hashlib.sha1()\n            sha_obj.update(key.encode('utf-8'))\n            return sha_obj.hexdigest()\n\n        status_key = yield props.render(self.status_key)\n\n        body = {\n            'state': status,\n            'key': key_hash(status_key),\n            'name': (yield props.render(self.status_name)),\n            'description': reports[0]['subject'],\n            'url': build['url']\n        }\n\n        for sourcestamp in build['buildset']['sourcestamps']:\n            if not sourcestamp['repository']:\n                log.msg(f\"Empty repository URL for Bitbucket status {body}\")\n                continue\n            owner, repo = self.get_owner_and_repo(sourcestamp['repository'])\n\n            endpoint = (owner, repo, 'commit', sourcestamp['revision'], 'statuses', 'build')\n            bitbucket_uri = f\"/{'/'.join(endpoint)}\"\n\n            if self.debug:\n                log.msg(f\"Bitbucket status {bitbucket_uri} {body}\")\n\n            response = yield self._http.post(bitbucket_uri, json=body)\n            if response.code not in (200, 201):\n                content = yield response.content()\n                log.msg(f\"{response.code}: unable to upload Bitbucket status {content}\")\n\n    def get_owner_and_repo(self, repourl):\n        \"\"\"\n        Takes a git repository URL from Bitbucket and tries to determine the owner and repository\n        name\n        :param repourl: Bitbucket git repo in the form of\n                    git@bitbucket.org:OWNER/REPONAME.git\n                    https://bitbucket.org/OWNER/REPONAME.git\n                    ssh://git@bitbucket.org/OWNER/REPONAME.git\n                    https://api.bitbucket.org/2.0/repositories/OWNER/REPONAME\n        :return: owner, repo: The owner of the repository and the repository name\n        \"\"\"\n        parsed = urlparse(repourl)\n\n        base_parsed = urlparse(self.base_url)\n        if parsed.path.startswith(base_parsed.path):\n            path = parsed.path.replace(base_parsed.path, \"\")\n        elif parsed.scheme:\n            path = parsed.path\n        else:\n            # we assume git@host:owner/repo.git here\n            path = parsed.path.split(':', 1)[-1]\n\n        path = path.lstrip('/')\n        if path.endswith('.git'):\n            path = path[:-4]\n        path = path.rstrip('/')\n\n        parts = path.split('/')\n\n        assert len(parts) == 2, 'OWNER/REPONAME is expected'\n\n        return parts\n", "path": "master/buildbot/reporters/bitbucket.py"}]}