Diff
Modified: trunk/Tools/ChangeLog (273332 => 273333)
--- trunk/Tools/ChangeLog 2021-02-23 22:05:31 UTC (rev 273332)
+++ trunk/Tools/ChangeLog 2021-02-23 22:22:25 UTC (rev 273333)
@@ -1,3 +1,38 @@
+2021-02-23 Jonathan Bedard <jbed...@apple.com>
+
+ [webkitscmpy] Add remote BitBucket
+ https://bugs.webkit.org/show_bug.cgi?id=222213
+ <rdar://problem/74542626>
+
+ Rubber-stamped by Aakash Jain.
+
+ * Scripts/libraries/webkitscmpy/setup.py: Increment version.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py: Ditto.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/__init__.py: Export mock BitBucket.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py: Added.
+ (BitBucket): Mock limited set of BitBucket REST APIs.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/__init__.py: Export BitBucket class.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py: Added.
+ (BitBucket): Repository object interacting with BitBucket via REST API.
+ (BitBucket.is_webserver): Check if the provided URL is a bitbucket URL.
+ (BitBucket.__init__):
+ (BitBucket.is_git):
+ (BitBucket.request): Combine paginated requests into a single API call.
+ (BitBucket._distance): Preform binary search
+ (BitBucket._branches_for): Return branches for reference.
+ (BitBucket.default_branch): Return the default branch.
+ (BitBucket.branches): Return all branches for repository.
+ (BitBucket.tags): Return all tags for repository.
+ (BitBucket.commit): Convert hash, identifier or git ref to Commit object.
+ (BitBucket.find): Use git to match branches and tags to a hash instead of trying to do it ourselves.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py:
+ (Scm.from_url): Add BitBucket.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/test/git_unittest.py:
+ (TestGitHub.test_detection): Add bitbucket url.
+ (TestBitBucket): Added.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/test/svn_unittest.py:
+ (TestRemoteSvn.test_detection): Add bitbucket url.
+
2021-02-23 Don Olmstead <don.olmst...@sony.com>
Pass full environment when auto installing a Python module on Windows
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/__init__.py (273332 => 273333)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/__init__.py 2021-02-23 22:05:31 UTC (rev 273332)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/__init__.py 2021-02-23 22:22:25 UTC (rev 273333)
@@ -20,5 +20,6 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from webkitscmpy.mocks.remote.bitbucket import BitBucket
from webkitscmpy.mocks.remote.git_hub import GitHub
from webkitscmpy.mocks.remote.svn import Svn
Added: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py (0 => 273333)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py 2021-02-23 22:22:25 UTC (rev 273333)
@@ -0,0 +1,195 @@
+# Copyright (C) 2020 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import json
+
+from webkitcorepy import mocks
+from webkitscmpy import Commit, remote as scmremote
+
+
+class BitBucket(mocks.Requests):
+ top = None
+
+ def __init__(
+ self, remote='bitbucket.example.com/projects/WEBKIT/repos/webkit', datafile=None,
+ default_branch='main', git_svn=False,
+ ):
+ if not scmremote.BitBucket.is_webserver('https://{}'.format(remote)):
+ raise ValueError('"{}" is not a valid BitBucket remote'.format(remote))
+
+ self.default_branch = default_branch
+ self.remote = remote
+ self.project = '/'.join(remote.split('/')[1:])
+
+ super(BitBucket, self).__init__(self.remote.split('/')[0])
+
+ with open(datafile or os.path.join(os.path.dirname(os.path.dirname(__file__)), 'git-repo.json')) as file:
+ self.commits = json.load(file)
+ for key, commits in self.commits.items():
+ self.commits[key] = [Commit(**kwargs) for kwargs in commits]
+ if not git_svn:
+ for commit in self.commits[key]:
+ commit.revision = None
+
+ self.head = self.commits[self.default_branch][-1]
+ self.tags = {}
+
+ def commit(self, ref):
+ if ref in self.commits:
+ return self.commits[ref][-1]
+ if ref in self.tags:
+ return self.tags[ref]
+
+ for branch, commits in self.commits.items():
+ for commit in commits:
+ if commit.hash.startswith(ref):
+ return commit
+
+ if '~' not in ref:
+ return None
+ ref, delta = ref.split('~')
+ commit = self.commit(ref)
+ if not commit:
+ return None
+ delta = int(delta)
+
+ if delta < commit.identifier:
+ return self.commits[commit.branch][commit.identifier - delta - 1]
+ delta -= commit.identifier
+ if commit.branch_point and delta < commit.branch_point:
+ return self.commits[self.default_branch][commit.branch_point - delta - 1]
+ return None
+
+ def _branches_default(self, url):
+ recent = self.commit(self.default_branch)
+ return mocks.Response.fromJson(dict(
+ id='refs/heads/{}'.format(self.default_branch),
+ displayId=self.default_branch,
+ type='BRANCH',
+ latestCommit=recent.hash,
+ latestChangeset=recent.hash,
+ isDefault=True,
+ ), url=""
+
+ def _branches(self, url, params):
+ limit = params.get('limit', 25)
+ start = params.get('start', 0)
+ branches = [branch for branch in sorted(self.commits.keys())[start * limit: (start + 1) * limit]]
+
+ return mocks.Response.fromJson(dict(
+ size=len(branches),
+ limit=limit,
+ isLastPage=(start + 1) * limit > len(self.commits.keys()),
+ start=start,
+ nextPageStart=start + limit,
+ values=[
+ dict(
+ id='refs/heads/{}'.format(branch),
+ displayId=branch,
+ type='BRANCH',
+ isDefault=self.default_branch == branch,
+ latestCommit=self.commits[branch][-1].hash,
+ ) for branch in branches
+ ],
+ ), url=""
+
+ def _tags(self, url, params):
+ limit = params.get('limit', 25)
+ start = params.get('start', 0)
+ tags = [tag for tag in sorted(self.tags.keys())[start * limit: (start + 1) * limit]]
+
+ return mocks.Response.fromJson(dict(
+ size=len(tags),
+ limit=limit,
+ isLastPage=(start + 1) * limit > len(self.tags.keys()),
+ start=start,
+ nextPageStart=start + limit,
+ values=[
+ dict(
+ id='refs/tags/{}'.format(tag),
+ displayId=tag,
+ type='TAG',
+ latestCommit=self.tags[tag].hash,
+ ) for tag in tags
+ ],
+ ), url=""
+
+ def _branches_for(self, ref, url, params):
+ limit = params.get('limit', 25)
+ start = params.get('start', 0)
+ commit = self.commit(ref)
+ if not commit:
+ return mocks.Response.create404(url)
+
+ branches = [commit.branch][start * limit: (start + 1) * limit]
+ return mocks.Response.fromJson(dict(
+ size=len(branches),
+ limit=limit,
+ isLastPage=(start + 1) * limit > 1,
+ start=start,
+ nextPageStart=start + limit,
+ values=[
+ dict(
+ id='refs/tags/{}'.format(branch),
+ displayId=branch,
+ type='BRANCH',
+ ) for branch in branches
+ ],
+ ), url=""
+
+ def request(self, method, url, data="" params=None, **kwargs):
+ if not url.startswith('http://') and not url.startswith('https://'):
+ return mocks.Response.create404(url)
+
+ stripped_url = url.split('://')[-1]
+ if stripped_url == '{}/rest/api/1.0/{}/branches/default'.format(self.hosts[0], self.project):
+ return self._branches_default(url)
+
+ if stripped_url == '{}/rest/api/1.0/{}/branches'.format(self.hosts[0], self.project):
+ return self._branches(url, params or {})
+
+ if stripped_url == '{}/rest/api/1.0/{}/tags'.format(self.hosts[0], self.project):
+ return self._tags(url, params or {})
+
+ if stripped_url.startswith('{}/rest/api/1.0/{}/commits/'.format(self.hosts[0], self.project)):
+ commit = self.commit(stripped_url.split('/')[-1])
+ if not commit:
+ return mocks.Response.create404(url)
+ return mocks.Response.fromJson(dict(
+ id=commit.hash,
+ displayId=commit.hash[:12],
+ author=dict(
+ emailAddress=commit.author.email,
+ displayName=commit.author.name,
+ ), committer=dict(
+ emailAddress=commit.author.email,
+ displayName=commit.author.name,
+ ),
+ committerTimestamp=commit.timestamp * 100,
+ message=commit.message,
+ ))
+
+ if stripped_url.startswith('{}/rest/branch-utils/latest/{}/branches/info/'.format(self.hosts[0], self.project)):
+ return self._branches_for(stripped_url.split('/')[-1], url, params or {})
+
+ return mocks.Response.create404(url)
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/__init__.py (273332 => 273333)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/__init__.py 2021-02-23 22:05:31 UTC (rev 273332)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/__init__.py 2021-02-23 22:22:25 UTC (rev 273333)
@@ -23,3 +23,4 @@
from webkitscmpy.remote.scm import Scm
from webkitscmpy.remote.svn import Svn
from webkitscmpy.remote.git_hub import GitHub
+from webkitscmpy.remote.bitbucket import BitBucket
Added: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py (0 => 273333)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py 2021-02-23 22:22:25 UTC (rev 273333)
@@ -0,0 +1,265 @@
+# Copyright (C) 2021 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import re
+import requests
+import six
+import sys
+
+from webkitcorepy import decorators
+from webkitscmpy import Commit
+from webkitscmpy.remote.scm import Scm
+
+
+class BitBucket(Scm):
+ URL_RE = re.compile(r'\Ahttps?://(?P<domain>\S+)/projects/(?P<project>\S+)/repos/(?P<repository>\S+)\Z')
+
+ @classmethod
+ def is_webserver(cls, url):
+ return True if cls.URL_RE.match(url) else False
+
+ def __init__(self, url, dev_branches=None, prod_branches=None, contributors=None):
+ match = self.URL_RE.match(url)
+ if not match:
+ raise self.Exception("'{}' is not a valid BitBucket project".format(url))
+ self.domain = match.group('domain')
+ self.project = match.group('project')
+ self.name = match.group('repository')
+
+ super(BitBucket, self).__init__(url, dev_branches=dev_branches, prod_branches=prod_branches, contributors=contributors)
+
+ @property
+ def is_git(self):
+ return True
+
+ def request(self, path=None, params=None, headers=None, api=None, ignore_errors=False):
+ headers = {key: value for key, value in headers.items()} if headers else dict()
+
+ params = {key: value for key, value in params.items()} if params else dict()
+ params['limit'] = params.get('limit', 500)
+ params['start'] = 0
+ url = ''.format(
+ api=api or 'api/1.0',
+ domain=self.domain,
+ project=self.project,
+ name=self.name,
+ path='/{}'.format(path) if path else '',
+ )
+ response = requests.get(url, params=params, headers=headers)
+ if response.status_code != 200:
+ if not ignore_errors:
+ sys.stderr.write("Request to '{}' returned status code '{}'\n".format(url, response.status_code))
+ return None
+ response = response.json()
+ result = response.get('values', None)
+ if result is None:
+ return response
+
+ while not response.get('isLastPage', True):
+ params['start'] += params['limit']
+ response = requests.get(url, params=params, headers=headers)
+ if response.status_code != 200:
+ if ignore_errors:
+ break
+ raise self.Exception("Failed to assemble pagination requests for '{}', failed on start {}".format(url, params['start']))
+ response = response.json()
+ result.extend(response.get('values', []))
+ return result
+
+ @decorators.Memoize()
+ def _distance(self, ref, magnitude=None, condition=None):
+ bound = [0, magnitude if magnitude else 65536]
+ condition = condition or (lambda val: val)
+
+ branches = self._branches_for('{}~{}'.format(ref, bound[1]), ignore_errors=True)
+ while branches and condition(branches):
+ bound = [bound[1], bound[1] * 2]
+ branches = self._branches_for('{}~{}'.format(ref, bound[1]), ignore_errors=True)
+
+ while True:
+ current = bound[0] + int((bound[1] - bound[0]) / 2)
+
+ branches = self._branches_for('{}~{}'.format(ref, current), ignore_errors=True)
+ if branches and condition(branches):
+ if bound[1] - bound[0] <= 1:
+ return current + 1
+ bound = [current, bound[1]]
+ else:
+ if bound[1] - bound[0] <= 1:
+ return bound[1] + 1 if current == bound[0] else bound[0] + 1
+ bound = [bound[0], current]
+
+ def _branches_for(self, hash, ignore_errors=False):
+ response = self.request('branches/info/{}'.format(hash), api='branch-utils/latest', ignore_errors=ignore_errors)
+ if not response:
+ return []
+ return sorted([details.get('displayId') for details in response if details.get('displayId')])
+
+ @property
+ @decorators.Memoize()
+ def default_branch(self):
+ response = self.request('branches/default')
+ if not response:
+ raise self.Exception("Failed to query {} for {}'s default branch".format(self.domain, self.name))
+ return response.get('displayId')
+
+ @property
+ def branches(self):
+ response = self.request('branches')
+ if not response:
+ return [self.default_branch]
+ return sorted([details.get('displayId') for details in response if details.get('displayId')])
+
+ @property
+ def tags(self):
+ response = self.request('tags')
+ if not response:
+ return []
+ return sorted([details.get('displayId') for details in response if details.get('displayId')])
+
+ def commit(self, hash=None, revision=None, identifier=None, branch=None, tag=None, include_log=True):
+ if revision:
+ raise self.Exception('Cannot map revisions to commits on BitBucket')
+
+ if identifier is not None:
+ if revision:
+ raise ValueError('Cannot define both revision and identifier')
+ if hash:
+ raise ValueError('Cannot define both hash and identifier')
+ if tag:
+ raise ValueError('Cannot define both tag and identifier')
+
+ parsed_branch_point, identifier, parsed_branch = Commit._parse_identifier(identifier, do_assert=True)
+ if parsed_branch:
+ if branch and branch != parsed_branch:
+ raise ValueError(
+ "Caller passed both 'branch' and 'identifier', but specified different branches ({} and {})".format(
+ branch, parsed_branch,
+ ),
+ )
+ branch = parsed_branch
+
+ branch = branch or self.default_branch
+ is_default = branch == self.default_branch
+
+ if is_default and parsed_branch_point:
+ raise self.Exception('Cannot provide a branch point for a commit on the default branch')
+
+ commit_data = self.request('commits/{}'.format(branch), params=dict(limit=1))
+ if not commit_data:
+ raise self.Exception("Failed to retrieve commit information for '{}'".format(branch))
+ base_ref = commit_data['id']
+
+ if is_default:
+ base_count = self._distance(base_ref)
+ else:
+ base_count = self._distance(base_ref, magnitude=256, condition=lambda val: self.default_branch not in val)
+
+ if identifier > base_count:
+ raise self.Exception('Identifier {} cannot be found on {}'.format(identifier, branch))
+
+ # Negative identifiers are actually commits on the default branch, we will need to re-compute the identifier
+ if identifier < 0 and is_default:
+ raise self.Exception('Illegal negative identifier on the default branch')
+
+ commit_data = self.request('commits/{}~{}'.format(base_ref, base_count - identifier))
+ if not commit_data:
+ raise self.Exception("Failed to retrieve commit information for '{}@{}'".format(identifier, branch or 'HEAD'))
+
+ # If an identifier is negative, unset it so we re-compute before constructing the commit.
+ if identifier <= 0:
+ identifier = None
+
+ elif branch or tag:
+ if hash:
+ raise ValueError('Cannot define both tag/branch and hash')
+ if branch and tag:
+ raise ValueError('Cannot define both tag and branch')
+ commit_data = self.request('commits/{}'.format(branch or tag))
+ if not commit_data:
+ raise self.Exception("Failed to retrieve commit information for '{}'".format(branch or tag))
+
+ else:
+ hash = Commit._parse_hash(hash, do_assert=True)
+ commit_data = self.request('commits/{}'.format(hash or self.default_branch))
+ if not commit_data:
+ raise self.Exception("Failed to retrieve commit information for '{}'".format(hash or 'HEAD'))
+
+ branches = self._branches_for(commit_data['id'])
+ if branches:
+ branch = self.prioritize_branches(branches)
+
+ else:
+ # A commit not on any branches cannot have an identifier
+ identifier = None
+ branch = None
+
+ branch_point = None
+ if branch and branch == self.default_branch:
+ if not identifier:
+ identifier = self._distance(commit_data['id'])
+
+ elif branch:
+ if not identifier:
+ identifier = self._distance(commit_data['id'], magnitude=256, condition=lambda val: self.default_branch not in val)
+ branch_point = self._distance(commit_data['id']) - identifier
+
+ matches = self.GIT_SVN_REVISION.findall(commit_data['message'])
+ revision = int(matches[-1].split('@')[0]) if matches else None
+
+ return Commit(
+ hash=commit_data['id'],
+ revision=revision,
+ branch_point=branch_point,
+ identifier=identifier,
+ branch=branch,
+ timestamp=int(commit_data['committerTimestamp'] / 100),
+ author=self.contributors.create(
+ commit_data.get('committer', {}).get('displayName', None),
+ commit_data.get('committer', {}).get('emailAddress', None),
+ ), message=commit_data['message'] if include_log else None,
+ )
+
+ def find(self, argument, include_log=True):
+ if not isinstance(argument, six.string_types):
+ raise ValueError("Expected 'argument' to be a string, not '{}'".format(type(argument)))
+
+ if argument in self.DEFAULT_BRANCHES:
+ argument = self.default_branch
+
+ parsed_commit = Commit.parse(argument, do_assert=False)
+ if parsed_commit:
+ if parsed_commit.branch in self.DEFAULT_BRANCHES:
+ parsed_commit.branch = self.default_branch
+
+ return self.commit(
+ hash=parsed_commit.hash,
+ revision=parsed_commit.revision,
+ identifier=parsed_commit.identifier,
+ branch=parsed_commit.branch,
+ include_log=include_log,
+ )
+
+ commit_data = self.request('commits/{}'.format(argument))
+ if not commit_data:
+ raise ValueError("'{}' is not an argument recognized by git".format(argument))
+ return self.commit(hash=commit_data['id'], include_log=include_log)
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py (273332 => 273333)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py 2021-02-23 22:05:31 UTC (rev 273332)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py 2021-02-23 22:22:25 UTC (rev 273333)
@@ -30,10 +30,10 @@
def from_url(cls, url, contributors=None):
from webkitscmpy import remote
- if remote.Svn.is_webserver(url):
- return remote.Svn(url, contributors=contributors)
- if remote.GitHub.is_webserver(url):
- return remote.GitHub(url, contributors=contributors)
+ for candidate in [remote.Svn, remote.GitHub, remote.BitBucket]:
+ if candidate.is_webserver(url):
+ return candidate(url, contributors=contributors)
+
raise OSError("'{}' is not a known SCM server".format(url))
def __init__(self, url, dev_branches=None, prod_branches=None, contributors=None):
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/git_unittest.py (273332 => 273333)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/git_unittest.py 2021-02-23 22:05:31 UTC (rev 273332)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/git_unittest.py 2021-02-23 22:22:25 UTC (rev 273333)
@@ -279,6 +279,7 @@
self.assertEqual(remote.GitHub.is_webserver('https://github.example.com/WebKit/webkit'), True)
self.assertEqual(remote.GitHub.is_webserver('http://github.example.com/WebKit/webkit'), True)
self.assertEqual(remote.GitHub.is_webserver('https://svn.example.org/repository/webkit'), False)
+ self.assertEqual(remote.GitHub.is_webserver('https://bitbucket.example.com/projects/WebKit/repos/webkit'), False)
def test_branches(self):
with mocks.remote.GitHub():
@@ -378,3 +379,112 @@
with mocks.remote.GitHub():
self.assertEqual(str(remote.GitHub(self.remote).find('4@trunk')), '4@main')
self.assertEqual(str(remote.GitHub(self.remote).find('4@master')), '4@main')
+
+
+class TestBitBucket(unittest.TestCase):
+ remote = 'https://bitbucket.example.com/projects/WEBKIT/repos/webkit'
+
+ def test_detection(self):
+ self.assertEqual(remote.BitBucket.is_webserver('https://bitbucket.example.com/projects/WebKit/repos/webkit'), True)
+ self.assertEqual(remote.BitBucket.is_webserver('http://bitbucket.example.com/projects/WebKit/repos/webkit'), True)
+ self.assertEqual(remote.BitBucket.is_webserver('https://svn.example.org/repository/webkit'), False)
+ self.assertEqual(remote.BitBucket.is_webserver('https://github.example.com/WebKit/webkit'), False)
+
+ def test_branches(self):
+ with mocks.remote.BitBucket():
+ self.assertEqual(
+ remote.BitBucket(self.remote).branches,
+ ['branch-a', 'branch-b', 'main'],
+ )
+
+ def test_tags(self):
+ with mocks.remote.BitBucket() as mock:
+ mock.tags['tag-1'] = mock.commits['branch-a'][-1]
+ mock.tags['tag-2'] = mock.commits['branch-b'][-1]
+
+ self.assertEqual(
+ remote.BitBucket(self.remote).tags,
+ ['tag-1', 'tag-2'],
+ )
+
+ def test_scm_type(self):
+ self.assertFalse(remote.BitBucket(self.remote).is_svn)
+ self.assertTrue(remote.BitBucket(self.remote).is_git)
+
+ def test_commit_revision(self):
+ with mocks.remote.BitBucket():
+ self.assertEqual('1@main', str(remote.BitBucket(self.remote).commit(hash='9b8311f2')))
+ self.assertEqual('2@main', str(remote.BitBucket(self.remote).commit(hash='fff83bb2')))
+ self.assertEqual('2.1@branch-a', str(remote.BitBucket(self.remote).commit(hash='a30ce849')))
+ self.assertEqual('3@main', str(remote.BitBucket(self.remote).commit(hash='1abe25b4')))
+ self.assertEqual('2.2@branch-b', str(remote.BitBucket(self.remote).commit(hash='3cd32e35')))
+ self.assertEqual('4@main', str(remote.BitBucket(self.remote).commit(hash='bae5d1e9')))
+ self.assertEqual('2.2@branch-a', str(remote.BitBucket(self.remote).commit(hash='621652ad')))
+ self.assertEqual('2.3@branch-b', str(remote.BitBucket(self.remote).commit(hash='790725a6')))
+
+ def test_commit_from_branch(self):
+ with mocks.remote.BitBucket():
+ self.assertEqual('4@main', str(remote.BitBucket(self.remote).commit(branch='main')))
+ self.assertEqual('2.2@branch-a', str(remote.BitBucket(self.remote).commit(branch='branch-a')))
+ self.assertEqual('2.3@branch-b', str(remote.BitBucket(self.remote).commit(branch='branch-b')))
+
+ def test_identifier(self):
+ with mocks.remote.BitBucket():
+ self.assertEqual(
+ '9b8311f25a77ba14923d9d5a6532103f54abefcb',
+ remote.BitBucket(self.remote).commit(identifier='1@main').hash,
+ )
+ self.assertEqual(
+ 'fff83bb2d9171b4d9196e977eb0508fd57e7a08d',
+ remote.BitBucket(self.remote).commit(identifier='2@main').hash,
+ )
+ self.assertEqual(
+ 'a30ce8494bf1ac2807a69844f726be4a9843ca55',
+ remote.BitBucket(self.remote).commit(identifier='2.1@branch-a').hash,
+ )
+ self.assertEqual(
+ '1abe25b443e985f93b90d830e4a7e3731336af4d',
+ remote.BitBucket(self.remote).commit(identifier='3@main').hash,
+ )
+ self.assertEqual(
+ '3cd32e352410565bb543821fbf856a6d3caad1c4',
+ remote.BitBucket(self.remote).commit(identifier='2.2@branch-b').hash,
+ )
+ self.assertEqual(
+ 'bae5d1e90999d4f916a8a15810ccfa43f37a2fd6',
+ remote.BitBucket(self.remote).commit(identifier='4@main').hash,
+ )
+ self.assertEqual(
+ '621652add7fc416099bd2063366cc38ff61afe36',
+ remote.BitBucket(self.remote).commit(identifier='2.2@branch-a').hash,
+ )
+ self.assertEqual(
+ '790725a6d79e28db2ecdde29548d2262c0bd059d',
+ remote.BitBucket(self.remote).commit(identifier='2.3@branch-b').hash,
+ )
+
+ def test_non_cannonical_identifiers(self):
+ with mocks.remote.BitBucket():
+ self.assertEqual('2@main', str(remote.BitBucket(self.remote).commit(identifier='0@branch-a')))
+ self.assertEqual('1@main', str(remote.BitBucket(self.remote).commit(identifier='-1@branch-a')))
+
+ self.assertEqual('2@main', str(remote.BitBucket(self.remote).commit(identifier='0@branch-b')))
+ self.assertEqual('1@main', str(remote.BitBucket(self.remote).commit(identifier='-1@branch-b')))
+
+ def test_tag(self):
+ with mocks.remote.BitBucket() as mock:
+ mock.tags['tag-1'] = mock.commits['branch-a'][-1]
+
+ self.assertEqual(
+ '621652add7fc416099bd2063366cc38ff61afe36',
+ remote.BitBucket(self.remote).commit(tag='tag-1').hash,
+ )
+
+ def test_no_log(self):
+ with mocks.remote.BitBucket():
+ self.assertIsNone(remote.BitBucket(self.remote).commit(identifier='4@main', include_log=False).message)
+
+ def test_alternative_default_branch(self):
+ with mocks.remote.BitBucket():
+ self.assertEqual(str(remote.BitBucket(self.remote).find('4@trunk')), '4@main')
+ self.assertEqual(str(remote.BitBucket(self.remote).find('4@master')), '4@main')
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/svn_unittest.py (273332 => 273333)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/svn_unittest.py 2021-02-23 22:05:31 UTC (rev 273332)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/svn_unittest.py 2021-02-23 22:22:25 UTC (rev 273333)
@@ -235,6 +235,7 @@
self.assertEqual(remote.Svn.is_webserver('https://svn.example.org/repository/webkit'), True)
self.assertEqual(remote.Svn.is_webserver('http://svn.example.org/repository/webkit'), True)
self.assertEqual(remote.Svn.is_webserver('https://github.example.org/WebKit/webkit'), False)
+ self.assertEqual(remote.GitHub.is_webserver('https://bitbucket.example.com/projects/WebKit/repos/webkit'), False)
def test_branches(self):
with mocks.remote.Svn():