Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F11012745
D4843.id17158.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
58 KB
Subscribers
None
D4843.id17158.diff
View Options
diff --git a/swh/lister/bitbucket/__init__.py b/swh/lister/bitbucket/__init__.py
--- a/swh/lister/bitbucket/__init__.py
+++ b/swh/lister/bitbucket/__init__.py
@@ -4,11 +4,10 @@
def register():
- from .lister import BitBucketLister
- from .models import BitBucketModel
+ from .lister import BitbucketLister
return {
- "models": [BitBucketModel],
- "lister": BitBucketLister,
+ "models": [],
+ "lister": BitbucketLister,
"task_modules": ["%s.tasks" % __name__],
}
diff --git a/swh/lister/bitbucket/lister.py b/swh/lister/bitbucket/lister.py
--- a/swh/lister/bitbucket/lister.py
+++ b/swh/lister/bitbucket/lister.py
@@ -3,83 +3,184 @@
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
-from datetime import datetime, timezone
+from dataclasses import asdict, dataclass
+from datetime import datetime
import logging
-from typing import Any, Dict, List, Optional
+import time
+from typing import Any, Dict, Iterator, List, Optional
from urllib import parse
import iso8601
-from requests import Response
+import requests
-from swh.lister.bitbucket.models import BitBucketModel
-from swh.lister.core.indexing_lister import IndexingHttpLister
+from swh.scheduler.interface import SchedulerInterface
+from swh.scheduler.model import ListedOrigin
+
+from .. import USER_AGENT
+from ..pattern import CredentialsType, Lister
logger = logging.getLogger(__name__)
-class BitBucketLister(IndexingHttpLister):
- PATH_TEMPLATE = "/repositories?after=%s"
- MODEL = BitBucketModel
+@dataclass
+class BitbucketListerState:
+ """State of my lister"""
+
+ last_repo_cdate: Optional[datetime] = None
+ """Date and time of the last repository listed on an incremental pass"""
+
+
+class BitbucketLister(Lister[BitbucketListerState, List[Dict[str, Any]]]):
+ """List origins from Bitbucket.
+
+ """
+
LISTER_NAME = "bitbucket"
- DEFAULT_URL = "https://api.bitbucket.org/2.0"
- instance = "bitbucket"
- default_min_bound = datetime.fromtimestamp(0, timezone.utc) # type: Any
+ INSTANCE = "bitbucket"
+
+ API_URL = "https://api.bitbucket.org/2.0"
+ QUERY_TEMPLATE = "/repositories?pagelen={}&after={{}}"
+ PAGE_SIZE = 100
+
+ INITIAL_BACKOFF = 10 # max anonymous 60 per hour, authenticated 1000 per hour
+ MAX_RETRIES = 5
def __init__(
- self, url: str = None, override_config=None, per_page: int = 100
- ) -> None:
- super().__init__(url=url, override_config=override_config)
- per_page = self.config.get("per_page", per_page)
-
- self.PATH_TEMPLATE = "%s&pagelen=%s" % (self.PATH_TEMPLATE, per_page)
-
- def get_model_from_repo(self, repo: Dict) -> Dict[str, Any]:
- return {
- "uid": repo["uuid"],
- "indexable": iso8601.parse_date(repo["created_on"]),
- "name": repo["name"],
- "full_name": repo["full_name"],
- "html_url": repo["links"]["html"]["href"],
- "origin_url": repo["links"]["clone"][0]["href"],
- "origin_type": repo["scm"],
- }
-
- def get_next_target_from_response(self, response: Response) -> Optional[datetime]:
- """This will read the 'next' link from the api response if any
- and return it as a datetime.
-
- Args:
- response (Response): requests' response from api call
-
- Returns:
- next date as a datetime
+ self,
+ scheduler: SchedulerInterface,
+ per_page: int = 100,
+ credentials: CredentialsType = None,
+ ):
+ super().__init__(
+ scheduler=scheduler,
+ credentials=credentials,
+ url=self.API_URL,
+ instance=self.INSTANCE,
+ )
+
+ self.url_template = f"{self.API_URL}{self.QUERY_TEMPLATE}".format(per_page)
+
+ self.session = requests.Session()
+ self.session.headers.update(
+ {"Accept": "application/json", "User-Agent": USER_AGENT}
+ )
+
+ # Support only one credential of type basic auth
+ if len(self.credentials) > 0:
+ cred = self.credentials[0]
+ if "username" in cred:
+ self.session.auth = (cred["username"], cred["password"])
+
+ self.backoff = self.INITIAL_BACKOFF
+ self.request_count = 0
+
+ def state_from_dict(self, d: Dict[str, Any]) -> BitbucketListerState:
+ last_repo_cdate = d.get("last_repo_cdate")
+ if last_repo_cdate is not None:
+ d["last_repo_cdate"] = iso8601.parse_date(last_repo_cdate)
+ return BitbucketListerState(**d)
+
+ def state_to_dict(self, state: BitbucketListerState) -> Dict[str, Any]:
+ d = asdict(state)
+ last_repo_cdate = d.get("last_repo_cdate")
+ if last_repo_cdate is not None:
+ d["last_repo_cdate"] = last_repo_cdate.isoformat()
+ return d
+
+ def get_pages(self) -> Iterator[List[Dict[str, Any]]]:
+
+ last_repo_cdate: Optional[str] = "1970-01-01"
+ if self.state is not None and self.state.last_repo_cdate is not None:
+ last_repo_cdate = self.state.last_repo_cdate.isoformat()
+
+ while last_repo_cdate is not None:
+ url = self.url_template.format(parse.quote(last_repo_cdate))
+ logger.debug("Page URL: %s", url)
+
+ response = self.session.get(url)
+
+ # handle HTTP errors
+ if response.status_code == 429:
+ if self.request_count >= self.MAX_RETRIES:
+ logger.warning(
+ "Max number of attempts hit (%s), giving up",
+ self.request_count,
+ )
+ break
+
+ logger.warning("Rate limit was hit, sleeping %ss", self.backoff)
+ time.sleep(self.backoff)
+
+ self.backoff *= 10
+ self.request_count += 1
+ continue
+
+ if response.status_code != 200:
+ logger.warning(
+ "Got unexpected status_code %s: %s",
+ response.status_code,
+ response.content,
+ )
+ break
+
+ self.request_count = 0
+
+ body = response.json()
+ next_page_url = body.get("next")
+ if next_page_url is not None:
+ next_page_url = parse.urlparse(next_page_url)
+ if not next_page_url.query:
+ logger.warning("Failed to parse url %s", next_page_url)
+ break
+ last_repo_cdate = parse.parse_qs(next_page_url.query)["after"][0]
+ else:
+ last_repo_cdate = None
+
+ yield body["values"]
+
+ def get_origins_from_page(
+ self, page: List[Dict[str, Any]]
+ ) -> Iterator[ListedOrigin]:
+ """Convert a page of Bitbucket repositories into a list of ListedOrigins.
"""
- body = response.json()
- next_ = body.get("next")
- if next_ is not None:
- next_ = parse.urlparse(next_)
- return iso8601.parse_date(parse.parse_qs(next_.query)["after"][0])
- return None
-
- def transport_response_simplified(self, response: Response) -> List[Dict[str, Any]]:
- repos = response.json()["values"]
- return [self.get_model_from_repo(repo) for repo in repos]
-
- def request_uri(self, identifier: datetime) -> str: # type: ignore
- identifier_str = parse.quote(identifier.isoformat())
- return super().request_uri(identifier_str or "1970-01-01")
-
- def is_within_bounds(
- self, inner: int, lower: Optional[int] = None, upper: Optional[int] = None
- ) -> bool:
- # values are expected to be datetimes
- if lower is None and upper is None:
- ret = True
- elif lower is None:
- ret = inner <= upper # type: ignore
- elif upper is None:
- ret = inner >= lower
- else:
- ret = lower <= inner <= upper
- return ret
+ assert self.lister_obj.id is not None
+
+ for repo in page:
+ last_update = iso8601.parse_date(repo["updated_on"])
+ origin_url = repo["links"]["clone"][0]["href"]
+ origin_type = repo["scm"]
+
+ yield ListedOrigin(
+ lister_id=self.lister_obj.id,
+ url=origin_url,
+ visit_type=origin_type,
+ last_update=last_update,
+ )
+
+ def commit_page(self, page: List[Dict[str, Any]]):
+ """Update the currently stored state using the latest listed page"""
+
+ last_repo = page[-1]
+ last_repo_cdate = iso8601.parse_date(last_repo["created_on"])
+
+ if (
+ self.state.last_repo_cdate is None
+ or last_repo_cdate > self.state.last_repo_cdate
+ ):
+ self.state.last_repo_cdate = last_repo_cdate
+
+ def finalize(self):
+
+ scheduler_state = self.get_state_from_scheduler()
+
+ if self.state.last_repo_cdate is None:
+ return
+
+ # Update the lister state in the backend only if the last seen id of
+ # the current run is higher than that stored in the database.
+ if (
+ scheduler_state.last_repo_cdate is None
+ or self.state.last_repo_cdate > scheduler_state.last_repo_cdate
+ ):
+ self.updated = True
diff --git a/swh/lister/bitbucket/tasks.py b/swh/lister/bitbucket/tasks.py
--- a/swh/lister/bitbucket/tasks.py
+++ b/swh/lister/bitbucket/tasks.py
@@ -6,32 +6,32 @@
from celery import group, shared_task
-from .lister import BitBucketLister
+from .lister import BitbucketLister
GROUP_SPLIT = 10000
@shared_task(name=__name__ + ".IncrementalBitBucketLister")
def list_bitbucket_incremental(**lister_args):
- """Incremental update of the BitBucket forge"""
- lister = BitBucketLister(**lister_args)
- return lister.run(min_bound=lister.db_last_index(), max_bound=None)
+ """Incremental update of the Bitbucket forge"""
+ lister = BitbucketLister.from_configfile()
+ return lister.run().dict()
@shared_task(name=__name__ + ".RangeBitBucketLister")
def _range_bitbucket_lister(start, end, **lister_args):
- lister = BitBucketLister(**lister_args)
+ lister = BitbucketLister(**lister_args)
return lister.run(min_bound=start, max_bound=end)
@shared_task(name=__name__ + ".FullBitBucketRelister", bind=True)
def list_bitbucket_full(self, split=None, **lister_args):
- """Full update of the BitBucket forge
+ """Full update of the Bitbucket forge
It's not to be called for an initial listing.
"""
- lister = BitBucketLister(**lister_args)
+ lister = BitbucketLister(**lister_args)
ranges = lister.db_partition_indices(split or GROUP_SPLIT)
if not ranges:
self.log.info("Nothing to list")
@@ -46,7 +46,6 @@
promise.save() # so that we can restore the GroupResult in tests
except (NotImplementedError, AttributeError):
self.log.info("Unable to call save_group with current result backend.")
- # FIXME: what to do in terms of return here?
return promise.id
diff --git a/swh/lister/bitbucket/tests/data/https_api.bitbucket.org/2.0_repositories,after=1970-01-01T00:00:00+00:00,pagelen=100 b/swh/lister/bitbucket/tests/data/https_api.bitbucket.org/2.0_repositories,pagelen=10,after=1970-01-01
rename from swh/lister/bitbucket/tests/data/https_api.bitbucket.org/2.0_repositories,after=1970-01-01T00:00:00+00:00,pagelen=100
rename to swh/lister/bitbucket/tests/data/https_api.bitbucket.org/2.0_repositories,pagelen=10,after=1970-01-01
diff --git a/swh/lister/bitbucket/tests/data/https_api.bitbucket.org/2.0_repositories,pagelen=10,after=2008-07-19T19:53:07.031845+00:00 b/swh/lister/bitbucket/tests/data/https_api.bitbucket.org/2.0_repositories,pagelen=10,after=2008-07-19T19:53:07.031845+00:00
new file mode 100644
--- /dev/null
+++ b/swh/lister/bitbucket/tests/data/https_api.bitbucket.org/2.0_repositories,pagelen=10,after=2008-07-19T19:53:07.031845+00:00
@@ -0,0 +1,1227 @@
+{
+ "pagelen": 10,
+ "values": [
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{3f630668-75f1-4903-ae5e-8ea37437e09e}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/opensymphony/xwork.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:opensymphony/xwork.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/xwork"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7B3f630668-75f1-4903-ae5e-8ea37437e09e%7D?ts=java"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/xwork/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "opensymphony/xwork",
+ "name": "xwork",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/opensymphony/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/opensymphony/projects/PROJ/avatar/32?ts=1543460518"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{57fac509-0df2-47ce-ad8e-27be013523fa}"
+ },
+ "language": "java",
+ "created_on": "2011-06-06T03:40:09.505792+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "opensymphony",
+ "type": "workspace",
+ "name": "opensymphony",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/opensymphony"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/opensymphony/avatar/?ts=1543460518"
+ }
+ },
+ "uuid": "{cedfd0d1-899f-49de-acf7-a2fa8e924b6f}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "opensymphony",
+ "uuid": "{cedfd0d1-899f-49de-acf7-a2fa8e924b6f}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7Bcedfd0d1-899f-49de-acf7-a2fa8e924b6f%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7Bcedfd0d1-899f-49de-acf7-a2fa8e924b6f%7D/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/opensymphony/avatar/"
+ }
+ },
+ "nickname": "opensymphony",
+ "type": "user",
+ "account_id": null
+ },
+ "updated_on": "2014-11-16T23:19:16.674082+00:00",
+ "size": 22877949,
+ "type": "repository",
+ "slug": "xwork",
+ "is_private": false,
+ "description": ""
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{ee2b6927-24b9-4b8d-a22a-e68d24e49dec}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/opensymphony/webwork.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:opensymphony/webwork.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/webwork"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7Bee2b6927-24b9-4b8d-a22a-e68d24e49dec%7D?ts=java"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/webwork/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "opensymphony/webwork",
+ "name": "webwork",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/opensymphony/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/opensymphony/projects/PROJ/avatar/32?ts=1543460518"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{57fac509-0df2-47ce-ad8e-27be013523fa}"
+ },
+ "language": "java",
+ "created_on": "2011-06-07T02:25:57.515877+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "opensymphony",
+ "type": "workspace",
+ "name": "opensymphony",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/opensymphony"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/opensymphony/avatar/?ts=1543460518"
+ }
+ },
+ "uuid": "{cedfd0d1-899f-49de-acf7-a2fa8e924b6f}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "opensymphony",
+ "uuid": "{cedfd0d1-899f-49de-acf7-a2fa8e924b6f}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7Bcedfd0d1-899f-49de-acf7-a2fa8e924b6f%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7Bcedfd0d1-899f-49de-acf7-a2fa8e924b6f%7D/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/opensymphony/avatar/"
+ }
+ },
+ "nickname": "opensymphony",
+ "type": "user",
+ "account_id": null
+ },
+ "updated_on": "2013-08-16T05:17:12.385393+00:00",
+ "size": 106935051,
+ "type": "repository",
+ "slug": "webwork",
+ "is_private": false,
+ "description": ""
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{1d497238-48c8-4016-8c0d-15c8a12fa9ed}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/opensymphony/propertyset.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:opensymphony/propertyset.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/propertyset"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7B1d497238-48c8-4016-8c0d-15c8a12fa9ed%7D?ts=java"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/propertyset/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "opensymphony/propertyset",
+ "name": "propertyset",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/opensymphony/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/opensymphony/projects/PROJ/avatar/32?ts=1543460518"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{57fac509-0df2-47ce-ad8e-27be013523fa}"
+ },
+ "language": "java",
+ "created_on": "2011-06-07T04:13:28.097554+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "opensymphony",
+ "type": "workspace",
+ "name": "opensymphony",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/opensymphony"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/opensymphony/avatar/?ts=1543460518"
+ }
+ },
+ "uuid": "{cedfd0d1-899f-49de-acf7-a2fa8e924b6f}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "opensymphony",
+ "uuid": "{cedfd0d1-899f-49de-acf7-a2fa8e924b6f}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7Bcedfd0d1-899f-49de-acf7-a2fa8e924b6f%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7Bcedfd0d1-899f-49de-acf7-a2fa8e924b6f%7D/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/opensymphony/avatar/"
+ }
+ },
+ "nickname": "opensymphony",
+ "type": "user",
+ "account_id": null
+ },
+ "updated_on": "2017-02-05T20:25:16.398281+00:00",
+ "size": 25747672,
+ "type": "repository",
+ "slug": "propertyset",
+ "is_private": false,
+ "description": ""
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{5aa7a0c5-73ae-4d2c-9c09-276866c16059}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/opensymphony/quartz.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:opensymphony/quartz.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/quartz"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7B5aa7a0c5-73ae-4d2c-9c09-276866c16059%7D?ts=java"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/opensymphony/quartz/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "opensymphony/quartz",
+ "name": "quartz",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/opensymphony/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/opensymphony/projects/PROJ/avatar/32?ts=1543460518"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{57fac509-0df2-47ce-ad8e-27be013523fa}"
+ },
+ "language": "java",
+ "created_on": "2011-06-07T04:15:47.909191+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "opensymphony",
+ "type": "workspace",
+ "name": "opensymphony",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/opensymphony"
+ },
+ "html": {
+ "href": "https://bitbucket.org/opensymphony/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/opensymphony/avatar/?ts=1543460518"
+ }
+ },
+ "uuid": "{cedfd0d1-899f-49de-acf7-a2fa8e924b6f}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "opensymphony",
+ "uuid": "{cedfd0d1-899f-49de-acf7-a2fa8e924b6f}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7Bcedfd0d1-899f-49de-acf7-a2fa8e924b6f%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7Bcedfd0d1-899f-49de-acf7-a2fa8e924b6f%7D/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/opensymphony/avatar/"
+ }
+ },
+ "nickname": "opensymphony",
+ "type": "user",
+ "account_id": null
+ },
+ "updated_on": "2012-07-06T23:05:13.437602+00:00",
+ "size": 12845056,
+ "type": "repository",
+ "slug": "quartz",
+ "is_private": false,
+ "description": ""
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{7e35bde1-67e7-4d8f-ae29-05dd10424072}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/jwalton/opup.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:jwalton/opup.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/jwalton/opup"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7B7e35bde1-67e7-4d8f-ae29-05dd10424072%7D?ts=java"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/opup/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "jwalton/opup",
+ "name": "opup",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/jwalton/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/jwalton/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/jwalton/projects/PROJ/avatar/32?ts=1543459298"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{6eed76e0-e831-48d0-83ac-cbbd8ee04173}"
+ },
+ "language": "java",
+ "created_on": "2011-06-16T09:16:27.957216+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "jwalton",
+ "type": "workspace",
+ "name": "Joseph Walton",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/jwalton"
+ },
+ "html": {
+ "href": "https://bitbucket.org/jwalton/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/jwalton/avatar/?ts=1543459298"
+ }
+ },
+ "uuid": "{c040300f-f69e-4a65-87a6-5a8f3ef1bbf1}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "Joseph Walton",
+ "uuid": "{c040300f-f69e-4a65-87a6-5a8f3ef1bbf1}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7Bc040300f-f69e-4a65-87a6-5a8f3ef1bbf1%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7Bc040300f-f69e-4a65-87a6-5a8f3ef1bbf1%7D/"
+ },
+ "avatar": {
+ "href": "https://secure.gravatar.com/avatar/cbed2f56195ed90bdebd4feec31ac054?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJW-1.png"
+ }
+ },
+ "nickname": "jwalton",
+ "type": "user",
+ "account_id": "557058:8679ff30-e82d-47b5-90f0-94127260782a"
+ },
+ "updated_on": "2018-02-06T04:36:52.369420+00:00",
+ "size": 1529686,
+ "type": "repository",
+ "slug": "opup",
+ "is_private": false,
+ "description": ""
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{9a355a32-dad9-4efd-9828-ccce80dd3109}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/jwalton/git-scripts.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:jwalton/git-scripts.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/jwalton/git-scripts"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7B9a355a32-dad9-4efd-9828-ccce80dd3109%7D?ts=default"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/jwalton/git-scripts/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "jwalton/git-scripts",
+ "name": "git-scripts",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/jwalton/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/jwalton/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/jwalton/projects/PROJ/avatar/32?ts=1543459298"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{6eed76e0-e831-48d0-83ac-cbbd8ee04173}"
+ },
+ "language": "shell",
+ "created_on": "2011-07-08T08:59:53.298617+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "jwalton",
+ "type": "workspace",
+ "name": "Joseph Walton",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/jwalton"
+ },
+ "html": {
+ "href": "https://bitbucket.org/jwalton/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/jwalton/avatar/?ts=1543459298"
+ }
+ },
+ "uuid": "{c040300f-f69e-4a65-87a6-5a8f3ef1bbf1}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "Joseph Walton",
+ "uuid": "{c040300f-f69e-4a65-87a6-5a8f3ef1bbf1}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7Bc040300f-f69e-4a65-87a6-5a8f3ef1bbf1%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7Bc040300f-f69e-4a65-87a6-5a8f3ef1bbf1%7D/"
+ },
+ "avatar": {
+ "href": "https://secure.gravatar.com/avatar/cbed2f56195ed90bdebd4feec31ac054?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJW-1.png"
+ }
+ },
+ "nickname": "jwalton",
+ "type": "user",
+ "account_id": "557058:8679ff30-e82d-47b5-90f0-94127260782a"
+ },
+ "updated_on": "2017-03-19T16:09:30.336053+00:00",
+ "size": 394778,
+ "type": "repository",
+ "slug": "git-scripts",
+ "is_private": false,
+ "description": ""
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{08725752-2ec2-41f8-ba6f-a7e4a9a55d07}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/evzijst/git-tests.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:evzijst/git-tests.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/evzijst/git-tests"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7B08725752-2ec2-41f8-ba6f-a7e4a9a55d07%7D?ts=default"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "evzijst/git-tests",
+ "name": "git-tests",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/evzijst/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/evzijst/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/evzijst/projects/PROJ/avatar/32?ts=1543458574"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{920e7a86-80d7-4310-828e-2ef2a486ba92}"
+ },
+ "language": "",
+ "created_on": "2011-08-10T00:42:35.509559+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "evzijst",
+ "type": "workspace",
+ "name": "Erik van Zijst",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/evzijst"
+ },
+ "html": {
+ "href": "https://bitbucket.org/evzijst/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/evzijst/avatar/?ts=1543458574"
+ }
+ },
+ "uuid": "{a288a0ab-e13b-43f0-a689-c4ef0a249875}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "Erik van Zijst",
+ "uuid": "{a288a0ab-e13b-43f0-a689-c4ef0a249875}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7Ba288a0ab-e13b-43f0-a689-c4ef0a249875%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7Ba288a0ab-e13b-43f0-a689-c4ef0a249875%7D/"
+ },
+ "avatar": {
+ "href": "https://secure.gravatar.com/avatar/f6bcbb4e3f665e74455bd8c0b4b3afba?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FEZ-4.png"
+ }
+ },
+ "nickname": "evzijst",
+ "type": "user",
+ "account_id": "557058:c0b72ad0-1cb5-4018-9cdc-0cde8492c443"
+ },
+ "updated_on": "2015-10-15T17:35:06.978690+00:00",
+ "size": 324796,
+ "type": "repository",
+ "slug": "git-tests",
+ "is_private": false,
+ "description": "Git repo used for testing with pull requests and difficult merge scenarios."
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{3cfaf49c-d6e8-4d09-80cc-a4ec9feebace}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/brodie/libgit2.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:brodie/libgit2.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/brodie/libgit2"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7B3cfaf49c-d6e8-4d09-80cc-a4ec9feebace%7D?ts=c"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/brodie/libgit2/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "brodie/libgit2",
+ "name": "libgit2",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/brodie/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/brodie/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/brodie/projects/PROJ/avatar/32?ts=1543457344"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{635ae7e0-a73c-4de5-884b-29fcd7f2b7b9}"
+ },
+ "language": "c",
+ "created_on": "2011-08-10T03:48:05.820933+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "brodie",
+ "type": "workspace",
+ "name": "Brodie Rao",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/brodie"
+ },
+ "html": {
+ "href": "https://bitbucket.org/brodie/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/brodie/avatar/?ts=1543457344"
+ }
+ },
+ "uuid": "{9484702e-c663-4afd-aefb-c93a8cd31c28}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "Brodie Rao",
+ "uuid": "{9484702e-c663-4afd-aefb-c93a8cd31c28}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D/"
+ },
+ "avatar": {
+ "href": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca/613070db-28b0-421f-8dba-ae8a87e2a5c7/128"
+ }
+ },
+ "nickname": "brodie",
+ "type": "user",
+ "account_id": "557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca"
+ },
+ "updated_on": "2013-07-17T23:08:05.997544+00:00",
+ "size": 4520824,
+ "type": "repository",
+ "slug": "libgit2",
+ "is_private": false,
+ "description": ""
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": false,
+ "uuid": "{e2c4ca61-b444-4af5-892a-da8afa64671d}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/evzijst/git.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:evzijst/git.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/evzijst/git"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7Be2c4ca61-b444-4af5-892a-da8afa64671d%7D?ts=default"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/downloads"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/evzijst/git/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "evzijst/git",
+ "name": "git",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/evzijst/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/evzijst/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/evzijst/projects/PROJ/avatar/32?ts=1543458574"
+ }
+ },
+ "type": "project",
+ "name": "Untitled project",
+ "key": "PROJ",
+ "uuid": "{920e7a86-80d7-4310-828e-2ef2a486ba92}"
+ },
+ "language": "",
+ "created_on": "2011-08-15T05:19:11.022316+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "evzijst",
+ "type": "workspace",
+ "name": "Erik van Zijst",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/evzijst"
+ },
+ "html": {
+ "href": "https://bitbucket.org/evzijst/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/evzijst/avatar/?ts=1543458574"
+ }
+ },
+ "uuid": "{a288a0ab-e13b-43f0-a689-c4ef0a249875}"
+ },
+ "has_issues": false,
+ "owner": {
+ "display_name": "Erik van Zijst",
+ "uuid": "{a288a0ab-e13b-43f0-a689-c4ef0a249875}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/%7Ba288a0ab-e13b-43f0-a689-c4ef0a249875%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7Ba288a0ab-e13b-43f0-a689-c4ef0a249875%7D/"
+ },
+ "avatar": {
+ "href": "https://secure.gravatar.com/avatar/f6bcbb4e3f665e74455bd8c0b4b3afba?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FEZ-4.png"
+ }
+ },
+ "nickname": "evzijst",
+ "type": "user",
+ "account_id": "557058:c0b72ad0-1cb5-4018-9cdc-0cde8492c443"
+ },
+ "updated_on": "2013-10-10T23:43:15.183665+00:00",
+ "size": 36467762,
+ "type": "repository",
+ "slug": "git",
+ "is_private": false,
+ "description": ""
+ },
+ {
+ "scm": "git",
+ "website": "",
+ "has_wiki": true,
+ "uuid": "{e1ff0acd-5fe0-4722-8d6f-0d9e7bb69436}",
+ "links": {
+ "watchers": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/watchers"
+ },
+ "branches": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/refs/branches"
+ },
+ "tags": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/refs/tags"
+ },
+ "commits": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/commits"
+ },
+ "clone": [
+ {
+ "href": "https://bitbucket.org/atlassian_tutorial/streams-jira-delete-issue-plugin.git",
+ "name": "https"
+ },
+ {
+ "href": "git@bitbucket.org:atlassian_tutorial/streams-jira-delete-issue-plugin.git",
+ "name": "ssh"
+ }
+ ],
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin"
+ },
+ "source": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/src"
+ },
+ "html": {
+ "href": "https://bitbucket.org/atlassian_tutorial/streams-jira-delete-issue-plugin"
+ },
+ "avatar": {
+ "href": "https://bytebucket.org/ravatar/%7Be1ff0acd-5fe0-4722-8d6f-0d9e7bb69436%7D?ts=java"
+ },
+ "hooks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/hooks"
+ },
+ "forks": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/forks"
+ },
+ "downloads": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/downloads"
+ },
+ "issues": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/issues"
+ },
+ "pullrequests": {
+ "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/streams-jira-delete-issue-plugin/pullrequests"
+ }
+ },
+ "fork_policy": "allow_forks",
+ "full_name": "atlassian_tutorial/streams-jira-delete-issue-plugin",
+ "name": "streams-jira-delete-issue-plugin",
+ "project": {
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PROJ"
+ },
+ "html": {
+ "href": "https://bitbucket.org/atlassian_tutorial/workspace/projects/PROJ"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/user/atlassian_tutorial/projects/PROJ/avatar/32?ts=1568941536"
+ }
+ },
+ "type": "project",
+ "name": "Tutorials",
+ "key": "PROJ",
+ "uuid": "{1605e801-929d-4a5a-8653-835b5778c315}"
+ },
+ "language": "java",
+ "created_on": "2011-08-18T00:17:00.862842+00:00",
+ "mainbranch": {
+ "type": "branch",
+ "name": "master"
+ },
+ "workspace": {
+ "slug": "atlassian_tutorial",
+ "type": "workspace",
+ "name": "Atlassian Tutorials",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial"
+ },
+ "html": {
+ "href": "https://bitbucket.org/atlassian_tutorial/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/workspaces/atlassian_tutorial/avatar/?ts=1543462696"
+ }
+ },
+ "uuid": "{38d7ea9c-d213-4366-bd21-3417324c520f}"
+ },
+ "has_issues": true,
+ "owner": {
+ "username": "atlassian_tutorial",
+ "display_name": "Atlassian Tutorials",
+ "type": "team",
+ "uuid": "{38d7ea9c-d213-4366-bd21-3417324c520f}",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/teams/%7B38d7ea9c-d213-4366-bd21-3417324c520f%7D"
+ },
+ "html": {
+ "href": "https://bitbucket.org/%7B38d7ea9c-d213-4366-bd21-3417324c520f%7D/"
+ },
+ "avatar": {
+ "href": "https://bitbucket.org/account/atlassian_tutorial/avatar/"
+ }
+ }
+ },
+ "updated_on": "2013-06-12T22:42:52.654728+00:00",
+ "size": 37629,
+ "type": "repository",
+ "slug": "streams-jira-delete-issue-plugin",
+ "is_private": false,
+ "description": ""
+ }
+ ]
+}
diff --git a/swh/lister/bitbucket/tests/test_lister.py b/swh/lister/bitbucket/tests/test_lister.py
--- a/swh/lister/bitbucket/tests/test_lister.py
+++ b/swh/lister/bitbucket/tests/test_lister.py
@@ -1,117 +1,18 @@
-# Copyright (C) 2017-2020 The Software Heritage developers
+# Copyright (C) 2017-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
-from datetime import timedelta
-import re
-import unittest
-from urllib.parse import unquote
+from swh.lister.bitbucket.lister import BitbucketLister
-import iso8601
-import requests_mock
-from swh.lister.bitbucket.lister import BitBucketLister
-from swh.lister.core.tests.test_lister import HttpListerTester
+def test_lister_bitbucket(swh_scheduler, requests_mock_datadir):
+ """Simple Bitbucket listing with two pages containing 10 origins"""
+ lister = BitbucketLister(scheduler=swh_scheduler, per_page=10)
-def _convert_type(req_index):
- """Convert the req_index to its right type according to the model's
- "indexable" column.
+ stats = lister.run()
- """
- return iso8601.parse_date(unquote(req_index))
-
-
-class BitBucketListerTester(HttpListerTester, unittest.TestCase):
- Lister = BitBucketLister
- test_re = re.compile(r"/repositories\?after=([^?&]+)")
- lister_subdir = "bitbucket"
- good_api_response_file = "data/https_api.bitbucket.org/response.json"
- bad_api_response_file = "data/https_api.bitbucket.org/empty_response.json"
- first_index = _convert_type("2008-07-12T07:44:01.476818+00:00")
- last_index = _convert_type("2008-07-19T06:16:43.044743+00:00")
- entries_per_page = 10
- convert_type = _convert_type
-
- def request_index(self, request):
- """(Override) This is needed to emulate the listing bootstrap
- when no min_bound is provided to run
- """
- m = self.test_re.search(request.path_url)
- idx = _convert_type(m.group(1))
- if idx == self.Lister.default_min_bound:
- idx = self.first_index
- return idx
-
- @requests_mock.Mocker()
- def test_fetch_none_nodb(self, http_mocker):
- """Overridden because index is not an integer nor a string
-
- """
- http_mocker.get(self.test_re, text=self.mock_response)
- fl = self.get_fl()
-
- self.disable_scheduler(fl)
- self.disable_db(fl)
-
- # stores no results
- fl.run(
- min_bound=self.first_index - timedelta(days=3), max_bound=self.first_index
- )
-
- def test_is_within_bounds(self):
- fl = self.get_fl()
- self.assertTrue(
- fl.is_within_bounds(
- iso8601.parse_date("2008-07-15"), self.first_index, self.last_index
- )
- )
- self.assertFalse(
- fl.is_within_bounds(
- iso8601.parse_date("2008-07-20"), self.first_index, self.last_index
- )
- )
- self.assertFalse(
- fl.is_within_bounds(
- iso8601.parse_date("2008-07-11"), self.first_index, self.last_index
- )
- )
-
-
-def test_lister_bitbucket(lister_bitbucket, requests_mock_datadir):
- """Simple bitbucket listing should create scheduled tasks (git, hg)
-
- """
- lister_bitbucket.run()
-
- r = lister_bitbucket.scheduler.search_tasks(task_type="load-hg")
- assert len(r) == 9
-
- for row in r:
- args = row["arguments"]["args"]
- kwargs = row["arguments"]["kwargs"]
-
- assert len(args) == 0
- assert len(kwargs) == 1
- url = kwargs["url"]
-
- assert url.startswith("https://bitbucket.org")
-
- assert row["policy"] == "recurring"
- assert row["priority"] is None
-
- r = lister_bitbucket.scheduler.search_tasks(task_type="load-git")
- assert len(r) == 1
-
- for row in r:
- args = row["arguments"]["args"]
- kwargs = row["arguments"]["kwargs"]
- assert len(args) == 0
- assert len(kwargs) == 1
- url = kwargs["url"]
-
- assert url.startswith("https://bitbucket.org")
-
- assert row["policy"] == "recurring"
- assert row["priority"] is None
+ assert stats.pages == 2
+ assert stats.origins == 20
+ assert len(swh_scheduler.get_listed_origins(lister.lister_obj.id).origins) == 20
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Sep 17, 4:31 AM (8 h, 55 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3232133
Attached To
D4843: Reimplement Bitbucket lister using new Lister API
Event Timeline
Log In to Comment