diff --git a/PKG-INFO b/PKG-INFO index 56a3b02..405d17e 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,212 +1,212 @@ Metadata-Version: 2.1 Name: swh.lister -Version: 0.0.21 +Version: 0.0.22 Summary: Software Heritage lister Home-page: https://forge.softwareheritage.org/diffusion/DLSGH/ Author: Software Heritage developers Author-email: swh-devel@inria.fr License: UNKNOWN Project-URL: Source, https://forge.softwareheritage.org/source/swh-lister -Project-URL: Funding, https://www.softwareheritage.org/donate Project-URL: Bug Reports, https://forge.softwareheritage.org/maniphest -Description: SWH-lister - ============ - - The Software Heritage Lister is both a library module to permit to - centralize lister behaviors, and to provide lister implementations. - - Actual lister implementations are: - - - swh-lister-bitbucket - - swh-lister-debian - - swh-lister-github - - swh-lister-gitlab - - swh-lister-pypi +Project-URL: Funding, https://www.softwareheritage.org/donate +Description: swh-lister + ========== - Licensing - ---------- + This component from the Software Heritage stack aims to produce listings + of software origins and their urls hosted on various public developer platforms + or package managers. As these operations are quite similar, it provides a set of + Python modules abstracting common software origins listing behaviors. - This program is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free Software - Foundation, either version 3 of the License, or (at your option) any later - version. - - This program is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - PARTICULAR PURPOSE. See the GNU General Public License for more details. - - See top-level LICENSE file for the full text of the GNU General Public License - along with this program. + It also provides several lister implementations, contained in the + following Python modules: + - `swh.lister.bitbucket` + - `swh.lister.debian` + - `swh.lister.github` + - `swh.lister.gitlab` + - `swh.lister.pypi` + - `swh.lister.npm` + - `swh.lister.phabricator` Dependencies ------------ - - python3 - - python3-requests - - python3-sqlalchemy - - More details in requirements*.txt - + All required dependencies can be found in the `requirements*.txt` files located + at the root of the repository. Local deployment - ----------- + ---------------- - ## lister-github + ## lister configuration - ### Preparation steps + Each lister implemented so far by Software Heritage (`github`, `gitlab`, `debian`, `pypi`, `npm`) + must be configured by following the instructions below (please note that you have to replace + `` by one of the lister name introduced above). - 1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) - 2. mkdir ~/.config/swh/ ~/.cache/swh/lister/github.com/ - 3. create configuration file ~/.config/swh/lister-github.com.yml - 4. Bootstrap the db instance schema - - $ createdb lister-github - $ python3 -m swh.lister.cli --db-url postgres:///lister-github github + ### Preparation steps - ### Configuration file sample + 1. `mkdir ~/.config/swh/ ~/.cache/swh/lister//` + 2. create configuration file `~/.config/swh/lister_.yml` + 3. Bootstrap the db instance schema - Minimalistic configuration: + ```lang=bash + $ createdb lister- + $ python3 -m swh.lister.cli --db-url postgres:///lister- + ``` - $ cat ~/.config/swh/lister-github.com.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-github - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/github.com + Note: This bootstraps a minimum data set needed for the lister to run. - Note: This expects storage (5002) and scheduler (5008) services to run locally + ### Configuration file sample - ### Run + Minimalistic configuration shared by all listers to add in file `~/.config/swh/lister_.yml`: - $ python3 - >>> import logging - >>> logging.basicConfig(level=logging.DEBUG) - >>> from swh.lister.github.tasks import range_github_lister; range_github_lister(364, 365) - INFO:root:listing repos starting at 364 - DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.github.com - DEBUG:urllib3.connectionpool:https://api.github.com:443 "GET /repositories?since=364 HTTP/1.1" 200 None - DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): localhost - DEBUG:urllib3.connectionpool:http://localhost:5002 "POST /origin/add HTTP/1.1" 200 1 + ```lang=yml + storage: + cls: 'remote' + args: + url: 'http://localhost:5002/' + scheduler: + cls: 'remote' + args: + url: 'http://localhost:5008/' - ## lister-gitlab + lister: + cls: 'local' + args: + # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls + db: 'postgresql:///lister-' - ### preparation steps + credentials: [] + cache_responses: True + cache_dir: /home/user/.cache/swh/lister// + ``` - 1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) - 2. mkdir ~/.config/swh/ ~/.cache/swh/lister/gitlab/ - 3. create configuration file ~/.config/swh/lister-gitlab.yml - 4. Bootstrap the db instance schema + Note: This expects storage (5002) and scheduler (5008) services to run locally - $ createdb lister-gitlab - $ python3 -m swh.lister.cli --db-url postgres:///lister-gitlab gitlab + ## lister-github - ### Configuration file sample + Once configured, you can execute a GitHub lister using the following instructions in a `python3` script: - $ cat ~/.config/swh/lister-gitlab.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-gitlab - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/gitlab + ```lang=python + import logging + from swh.lister.github.tasks import range_github_lister - Note: This expects storage (5002) and scheduler (5008) services to run locally + logging.basicConfig(level=logging.DEBUG) + range_github_lister(364, 365) + ... + ``` - ### Run + ## lister-gitlab - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> from swh.lister.gitlab.tasks import range_gitlab_lister; range_gitlab_lister(1, 2, - {'instance': 'debian', 'api_baseurl': 'https://salsa.debian.org/api/v4', 'sort': 'asc', 'per_page': 20}) - >>> from swh.lister.gitlab.tasks import full_gitlab_relister; full_gitlab_relister( - {'instance':'0xacab', 'api_baseurl':'https://0xacab.org/api/v4', 'sort': 'asc', 'per_page': 20}) - >>> from swh.lister.gitlab.tasks import incremental_gitlab_lister; incremental_gitlab_lister( - {'instance': 'freedesktop.org', 'api_baseurl': 'https://gitlab.freedesktop.org/api/v4', - 'sort': 'asc', 'per_page': 20}) + Once configured, you can execute a GitLab lister using the instructions detailed in the `python3` scripts below: + + ```lang=python + import logging + from swh.lister.gitlab.tasks import range_gitlab_lister + + logging.basicConfig(level=logging.DEBUG) + range_gitlab_lister(1, 2, { + 'instance': 'debian', + 'api_baseurl': 'https://salsa.debian.org/api/v4', + 'sort': 'asc', + 'per_page': 20 + }) + ``` + + ```lang=python + import logging + from swh.lister.gitlab.tasks import full_gitlab_relister + + logging.basicConfig(level=logging.DEBUG) + full_gitlab_relister({ + 'instance': '0xacab', + 'api_baseurl': 'https://0xacab.org/api/v4', + 'sort': 'asc', + 'per_page': 20 + }) + ``` + + ```lang=python + import logging + from swh.lister.gitlab.tasks import incremental_gitlab_lister + + logging.basicConfig(level=logging.DEBUG) + incremental_gitlab_lister({ + 'instance': 'freedesktop.org', + 'api_baseurl': 'https://gitlab.freedesktop.org/api/v4', + 'sort': 'asc', + 'per_page': 20 + }) + ``` ## lister-debian - ### preparation steps + Once configured, you can execute a Debian lister using the following instructions in a `python3` script: - 1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) - 2. mkdir ~/.config/swh/ ~/.cache/swh/lister/debian/ - 3. create configuration file ~/.config/swh/lister-debian.yml - 4. Bootstrap the db instance schema + ```lang=python + import logging + from swh.lister.debian.tasks import debian_lister - $ createdb lister-debian - $ python3 -m swh.lister.cli --db-url postgres:///lister-debian debian - - Note: This bootstraps a minimum data set needed for the debian - lister to run (for development) + logging.basicConfig(level=logging.DEBUG) + debian_lister('Debian') + ``` - ### Configuration file sample + ## lister-pypi - $ cat ~/.config/swh/lister-debian.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-debian - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/debian + Once configured, you can execute a PyPI lister using the following instructions in a `python3` script: - Note: This expects storage (5002) and scheduler (5008) services to run locally + ```lang=python + import logging + from swh.lister.pypi.tasks import pypi_lister - ### Run + logging.basicConfig(level=logging.DEBUG) + pypi_lister() + ``` - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> import logging; logging.basicConfig(level=logging.DEBUG); from swh.lister.debian.tasks import debian_lister; debian_lister('Debian') - DEBUG:root:Creating snapshot for distribution Distribution(Debian (deb) on http://deb.debian.org/debian/) on date 2018-07-27 09:22:50.461165+00:00 - DEBUG:root:Processing area Area(stretch/main of Debian) - DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): deb.debian.org - DEBUG:urllib3.connectionpool:http://deb.debian.org:80 "GET /debian//dists/stretch/main/source/Sources.xz HTTP/1.1" 302 325 - ... + ## lister-npm + Once configured, you can execute a npm lister using the following instructions in a `python3` REPL: - ## lister-pypi + ```lang=python + import logging + from swh.lister.npm.tasks import npm_lister - ### preparation steps + logging.basicConfig(level=logging.DEBUG) + npm_lister() + ``` - 1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) - 2. mkdir ~/.config/swh/ ~/.cache/swh/lister/pypi/ - 3. create configuration file ~/.config/swh/lister-pypi.yml - 4. Bootstrap the db instance schema + ## lister-phabricator - $ createdb lister-pypi - $ python3 -m swh.lister.cli --db-url postgres:///lister-pypi pypi + Once configured, you can execute a Phabricator lister using the following instructions in a `python3` script: - Note: This bootstraps a minimum data set needed for the pypi - lister to run (for development) + ```lang=python + import logging + from swh.lister.phabricator.tasks import incremental_phabricator_lister - ### Configuration file sample + logging.basicConfig(level=logging.DEBUG) + incremental_phabricator_lister(forge_url='https://forge.softwareheritage.org', api_token='XXXX') + ``` - $ cat ~/.config/swh/lister-pypi.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-pypi - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/pypi - - Note: This expects storage (5002) and scheduler (5008) services to run locally + Licensing + --------- - ### Run + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> from swh.lister.pypi.tasks import pypi_lister; pypi_lister() - >>> + This program is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU General Public License for more details. + See top-level LICENSE file for the full text of the GNU General Public License + along with this program. Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) Classifier: Operating System :: OS Independent Classifier: Development Status :: 5 - Production/Stable Description-Content-Type: text/markdown Provides-Extra: testing diff --git a/README.md b/README.md index d272eed..887b599 100644 --- a/README.md +++ b/README.md @@ -1,192 +1,193 @@ -SWH-lister -============ +swh-lister +========== -The Software Heritage Lister is both a library module to permit to -centralize lister behaviors, and to provide lister implementations. +This component from the Software Heritage stack aims to produce listings +of software origins and their urls hosted on various public developer platforms +or package managers. As these operations are quite similar, it provides a set of +Python modules abstracting common software origins listing behaviors. -Actual lister implementations are: - -- swh-lister-bitbucket -- swh-lister-debian -- swh-lister-github -- swh-lister-gitlab -- swh-lister-pypi - -Licensing ----------- - -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, either version 3 of the License, or (at your option) any later -version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -See top-level LICENSE file for the full text of the GNU General Public License -along with this program. +It also provides several lister implementations, contained in the +following Python modules: +- `swh.lister.bitbucket` +- `swh.lister.debian` +- `swh.lister.github` +- `swh.lister.gitlab` +- `swh.lister.pypi` +- `swh.lister.npm` +- `swh.lister.phabricator` Dependencies ------------ -- python3 -- python3-requests -- python3-sqlalchemy - -More details in requirements*.txt - +All required dependencies can be found in the `requirements*.txt` files located +at the root of the repository. Local deployment ------------ +---------------- -## lister-github - -### Preparation steps +## lister configuration -1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) -2. mkdir ~/.config/swh/ ~/.cache/swh/lister/github.com/ -3. create configuration file ~/.config/swh/lister-github.com.yml -4. Bootstrap the db instance schema +Each lister implemented so far by Software Heritage (`github`, `gitlab`, `debian`, `pypi`, `npm`) +must be configured by following the instructions below (please note that you have to replace +`` by one of the lister name introduced above). - $ createdb lister-github - $ python3 -m swh.lister.cli --db-url postgres:///lister-github github +### Preparation steps -### Configuration file sample +1. `mkdir ~/.config/swh/ ~/.cache/swh/lister//` +2. create configuration file `~/.config/swh/lister_.yml` +3. Bootstrap the db instance schema -Minimalistic configuration: +```lang=bash +$ createdb lister- +$ python3 -m swh.lister.cli --db-url postgres:///lister- +``` - $ cat ~/.config/swh/lister-github.com.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-github - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/github.com +Note: This bootstraps a minimum data set needed for the lister to run. -Note: This expects storage (5002) and scheduler (5008) services to run locally +### Configuration file sample -### Run +Minimalistic configuration shared by all listers to add in file `~/.config/swh/lister_.yml`: - $ python3 - >>> import logging - >>> logging.basicConfig(level=logging.DEBUG) - >>> from swh.lister.github.tasks import range_github_lister; range_github_lister(364, 365) - INFO:root:listing repos starting at 364 - DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.github.com - DEBUG:urllib3.connectionpool:https://api.github.com:443 "GET /repositories?since=364 HTTP/1.1" 200 None - DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): localhost - DEBUG:urllib3.connectionpool:http://localhost:5002 "POST /origin/add HTTP/1.1" 200 1 +```lang=yml +storage: + cls: 'remote' + args: + url: 'http://localhost:5002/' +scheduler: + cls: 'remote' + args: + url: 'http://localhost:5008/' -## lister-gitlab +lister: + cls: 'local' + args: + # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls + db: 'postgresql:///lister-' -### preparation steps +credentials: [] +cache_responses: True +cache_dir: /home/user/.cache/swh/lister// +``` -1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) -2. mkdir ~/.config/swh/ ~/.cache/swh/lister/gitlab/ -3. create configuration file ~/.config/swh/lister-gitlab.yml -4. Bootstrap the db instance schema +Note: This expects storage (5002) and scheduler (5008) services to run locally - $ createdb lister-gitlab - $ python3 -m swh.lister.cli --db-url postgres:///lister-gitlab gitlab +## lister-github -### Configuration file sample +Once configured, you can execute a GitHub lister using the following instructions in a `python3` script: - $ cat ~/.config/swh/lister-gitlab.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-gitlab - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/gitlab +```lang=python +import logging +from swh.lister.github.tasks import range_github_lister -Note: This expects storage (5002) and scheduler (5008) services to run locally +logging.basicConfig(level=logging.DEBUG) +range_github_lister(364, 365) +... +``` -### Run +## lister-gitlab - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> from swh.lister.gitlab.tasks import range_gitlab_lister; range_gitlab_lister(1, 2, - {'instance': 'debian', 'api_baseurl': 'https://salsa.debian.org/api/v4', 'sort': 'asc', 'per_page': 20}) - >>> from swh.lister.gitlab.tasks import full_gitlab_relister; full_gitlab_relister( - {'instance':'0xacab', 'api_baseurl':'https://0xacab.org/api/v4', 'sort': 'asc', 'per_page': 20}) - >>> from swh.lister.gitlab.tasks import incremental_gitlab_lister; incremental_gitlab_lister( - {'instance': 'freedesktop.org', 'api_baseurl': 'https://gitlab.freedesktop.org/api/v4', - 'sort': 'asc', 'per_page': 20}) +Once configured, you can execute a GitLab lister using the instructions detailed in the `python3` scripts below: + +```lang=python +import logging +from swh.lister.gitlab.tasks import range_gitlab_lister + +logging.basicConfig(level=logging.DEBUG) +range_gitlab_lister(1, 2, { + 'instance': 'debian', + 'api_baseurl': 'https://salsa.debian.org/api/v4', + 'sort': 'asc', + 'per_page': 20 +}) +``` + +```lang=python +import logging +from swh.lister.gitlab.tasks import full_gitlab_relister + +logging.basicConfig(level=logging.DEBUG) +full_gitlab_relister({ + 'instance': '0xacab', + 'api_baseurl': 'https://0xacab.org/api/v4', + 'sort': 'asc', + 'per_page': 20 +}) +``` + +```lang=python +import logging +from swh.lister.gitlab.tasks import incremental_gitlab_lister + +logging.basicConfig(level=logging.DEBUG) +incremental_gitlab_lister({ + 'instance': 'freedesktop.org', + 'api_baseurl': 'https://gitlab.freedesktop.org/api/v4', + 'sort': 'asc', + 'per_page': 20 +}) +``` ## lister-debian -### preparation steps +Once configured, you can execute a Debian lister using the following instructions in a `python3` script: -1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) -2. mkdir ~/.config/swh/ ~/.cache/swh/lister/debian/ -3. create configuration file ~/.config/swh/lister-debian.yml -4. Bootstrap the db instance schema +```lang=python +import logging +from swh.lister.debian.tasks import debian_lister - $ createdb lister-debian - $ python3 -m swh.lister.cli --db-url postgres:///lister-debian debian - - Note: This bootstraps a minimum data set needed for the debian - lister to run (for development) +logging.basicConfig(level=logging.DEBUG) +debian_lister('Debian') +``` -### Configuration file sample +## lister-pypi - $ cat ~/.config/swh/lister-debian.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-debian - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/debian +Once configured, you can execute a PyPI lister using the following instructions in a `python3` script: -Note: This expects storage (5002) and scheduler (5008) services to run locally +```lang=python +import logging +from swh.lister.pypi.tasks import pypi_lister -### Run +logging.basicConfig(level=logging.DEBUG) +pypi_lister() +``` - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> import logging; logging.basicConfig(level=logging.DEBUG); from swh.lister.debian.tasks import debian_lister; debian_lister('Debian') - DEBUG:root:Creating snapshot for distribution Distribution(Debian (deb) on http://deb.debian.org/debian/) on date 2018-07-27 09:22:50.461165+00:00 - DEBUG:root:Processing area Area(stretch/main of Debian) - DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): deb.debian.org - DEBUG:urllib3.connectionpool:http://deb.debian.org:80 "GET /debian//dists/stretch/main/source/Sources.xz HTTP/1.1" 302 325 - ... +## lister-npm +Once configured, you can execute a npm lister using the following instructions in a `python3` REPL: -## lister-pypi +```lang=python +import logging +from swh.lister.npm.tasks import npm_lister -### preparation steps +logging.basicConfig(level=logging.DEBUG) +npm_lister() +``` -1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) -2. mkdir ~/.config/swh/ ~/.cache/swh/lister/pypi/ -3. create configuration file ~/.config/swh/lister-pypi.yml -4. Bootstrap the db instance schema +## lister-phabricator - $ createdb lister-pypi - $ python3 -m swh.lister.cli --db-url postgres:///lister-pypi pypi +Once configured, you can execute a Phabricator lister using the following instructions in a `python3` script: - Note: This bootstraps a minimum data set needed for the pypi - lister to run (for development) +```lang=python +import logging +from swh.lister.phabricator.tasks import incremental_phabricator_lister -### Configuration file sample +logging.basicConfig(level=logging.DEBUG) +incremental_phabricator_lister(forge_url='https://forge.softwareheritage.org', api_token='XXXX') +``` - $ cat ~/.config/swh/lister-pypi.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-pypi - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/pypi +Licensing +--------- -Note: This expects storage (5002) and scheduler (5008) services to run locally +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later +version. -### Run +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> from swh.lister.pypi.tasks import pypi_lister; pypi_lister() - >>> +See top-level LICENSE file for the full text of the GNU General Public License +along with this program. \ No newline at end of file diff --git a/bin/ghlister b/bin/ghlister index 95b10b5..a18ab64 100755 --- a/bin/ghlister +++ b/bin/ghlister @@ -1,103 +1,101 @@ #!/usr/bin/env python3 # Copyright (C) 2015 Stefano Zacchiroli # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import argparse import logging import sys from swh.lister.github import models from swh.lister.github.lister import GitHubLister DEFAULT_CONF = { 'cache_dir': './cache', 'log_dir': './log', 'cache_json': 'False', } def int_interval(s): """parse an "N-M" string as an interval. Return an (N,M) int (or None) pair """ def not_an_interval(): raise argparse.ArgumentTypeError('not an interval: ' + s) def parse_int(s): if s: return int(s) else: return None if '-' not in s: not_an_interval() parts = s.split('-') if len(parts) > 2: not_an_interval() return tuple([parse_int(p) for p in parts]) def parse_args(): cli = argparse.ArgumentParser( description='list GitHub repositories and load them into a DB') cli.add_argument('--db-url', '-d', metavar='SQLALCHEMY_URL', help='SQLAlchemy DB URL (override conffile); see ' '') # NOQA cli.add_argument('--verbose', '-v', action='store_true', help='be verbose') subcli = cli.add_subparsers(dest='action') subcli.add_parser('createdb', help='initialize DB') subcli.add_parser('dropdb', help='destroy DB') list_cli = subcli.add_parser('list', help='list repositories') list_cli.add_argument('interval', type=int_interval, help='interval of repository IDs to list, ' 'in N-M format; either N or M can be omitted.') list_cli = subcli.add_parser('catchup', help='catchup with new repos since last time') args = cli.parse_args() if not args.action: cli.error('no action given') return args if __name__ == '__main__': logging.basicConfig(level=logging.INFO) # XXX args = parse_args() override_conf = {} - if args.db_url: - override_conf['lister_db_url'] = args.db_url lister = GitHubLister(lister_name='github.com', api_baseurl='https://api.github.com', override_config=override_conf) if args.action == 'createdb': models.ModelBase.metadata.create_all(lister.db_engine) elif args.action == 'dropdb': models.ModelBase.metadata.drop_all(lister.db_engine) elif args.action == 'list': lister.fetch(min_id=args.interval[0], max_id=args.interval[1]) elif args.action == 'catchup': last_known_id = lister.last_repo_id() if last_known_id is not None: logging.info('catching up from last known repo id: %d' % last_known_id) lister.fetch(min_id=last_known_id + 1, max_id=None) else: logging.error('Cannot catchup: no last known id found. Abort.') sys.exit(2) diff --git a/setup.py b/setup.py index 2d07fcc..521546c 100755 --- a/setup.py +++ b/setup.py @@ -1,70 +1,72 @@ #!/usr/bin/env python3 # Copyright (C) 2015-2018 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 setuptools import setup, find_packages from os import path from io import open here = path.abspath(path.dirname(__file__)) # Get the long description from the README file with open(path.join(here, 'README.md'), encoding='utf-8') as f: long_description = f.read() def parse_requirements(name=None): if name: reqf = 'requirements-%s.txt' % name else: reqf = 'requirements.txt' requirements = [] if not path.exists(reqf): return requirements with open(reqf) as f: for line in f.readlines(): line = line.strip() if not line or line.startswith('#'): continue requirements.append(line) return requirements setup( name='swh.lister', description='Software Heritage lister', long_description=long_description, long_description_content_type='text/markdown', author='Software Heritage developers', author_email='swh-devel@inria.fr', url='https://forge.softwareheritage.org/diffusion/DLSGH/', packages=find_packages(), scripts=['bin/ghlister'], install_requires=parse_requirements() + parse_requirements('swh'), tests_require=parse_requirements('test'), setup_requires=['vcversioner'], extras_require={'testing': parse_requirements('test')}, vcversioner={'version_module_paths': ['swh/lister/_version.py']}, include_package_data=True, entry_points=''' [console_scripts] swh-lister=swh.lister.cli:cli + [swh.cli.subcommands] + lister=swh.lister.cli:lister ''', classifiers=[ "Programming Language :: Python :: 3", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", "Development Status :: 5 - Production/Stable", ], project_urls={ 'Bug Reports': 'https://forge.softwareheritage.org/maniphest', 'Funding': 'https://www.softwareheritage.org/donate', 'Source': 'https://forge.softwareheritage.org/source/swh-lister', }, ) diff --git a/swh.lister.egg-info/PKG-INFO b/swh.lister.egg-info/PKG-INFO index 56a3b02..405d17e 100644 --- a/swh.lister.egg-info/PKG-INFO +++ b/swh.lister.egg-info/PKG-INFO @@ -1,212 +1,212 @@ Metadata-Version: 2.1 Name: swh.lister -Version: 0.0.21 +Version: 0.0.22 Summary: Software Heritage lister Home-page: https://forge.softwareheritage.org/diffusion/DLSGH/ Author: Software Heritage developers Author-email: swh-devel@inria.fr License: UNKNOWN Project-URL: Source, https://forge.softwareheritage.org/source/swh-lister -Project-URL: Funding, https://www.softwareheritage.org/donate Project-URL: Bug Reports, https://forge.softwareheritage.org/maniphest -Description: SWH-lister - ============ - - The Software Heritage Lister is both a library module to permit to - centralize lister behaviors, and to provide lister implementations. - - Actual lister implementations are: - - - swh-lister-bitbucket - - swh-lister-debian - - swh-lister-github - - swh-lister-gitlab - - swh-lister-pypi +Project-URL: Funding, https://www.softwareheritage.org/donate +Description: swh-lister + ========== - Licensing - ---------- + This component from the Software Heritage stack aims to produce listings + of software origins and their urls hosted on various public developer platforms + or package managers. As these operations are quite similar, it provides a set of + Python modules abstracting common software origins listing behaviors. - This program is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free Software - Foundation, either version 3 of the License, or (at your option) any later - version. - - This program is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - PARTICULAR PURPOSE. See the GNU General Public License for more details. - - See top-level LICENSE file for the full text of the GNU General Public License - along with this program. + It also provides several lister implementations, contained in the + following Python modules: + - `swh.lister.bitbucket` + - `swh.lister.debian` + - `swh.lister.github` + - `swh.lister.gitlab` + - `swh.lister.pypi` + - `swh.lister.npm` + - `swh.lister.phabricator` Dependencies ------------ - - python3 - - python3-requests - - python3-sqlalchemy - - More details in requirements*.txt - + All required dependencies can be found in the `requirements*.txt` files located + at the root of the repository. Local deployment - ----------- + ---------------- - ## lister-github + ## lister configuration - ### Preparation steps + Each lister implemented so far by Software Heritage (`github`, `gitlab`, `debian`, `pypi`, `npm`) + must be configured by following the instructions below (please note that you have to replace + `` by one of the lister name introduced above). - 1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) - 2. mkdir ~/.config/swh/ ~/.cache/swh/lister/github.com/ - 3. create configuration file ~/.config/swh/lister-github.com.yml - 4. Bootstrap the db instance schema - - $ createdb lister-github - $ python3 -m swh.lister.cli --db-url postgres:///lister-github github + ### Preparation steps - ### Configuration file sample + 1. `mkdir ~/.config/swh/ ~/.cache/swh/lister//` + 2. create configuration file `~/.config/swh/lister_.yml` + 3. Bootstrap the db instance schema - Minimalistic configuration: + ```lang=bash + $ createdb lister- + $ python3 -m swh.lister.cli --db-url postgres:///lister- + ``` - $ cat ~/.config/swh/lister-github.com.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-github - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/github.com + Note: This bootstraps a minimum data set needed for the lister to run. - Note: This expects storage (5002) and scheduler (5008) services to run locally + ### Configuration file sample - ### Run + Minimalistic configuration shared by all listers to add in file `~/.config/swh/lister_.yml`: - $ python3 - >>> import logging - >>> logging.basicConfig(level=logging.DEBUG) - >>> from swh.lister.github.tasks import range_github_lister; range_github_lister(364, 365) - INFO:root:listing repos starting at 364 - DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.github.com - DEBUG:urllib3.connectionpool:https://api.github.com:443 "GET /repositories?since=364 HTTP/1.1" 200 None - DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): localhost - DEBUG:urllib3.connectionpool:http://localhost:5002 "POST /origin/add HTTP/1.1" 200 1 + ```lang=yml + storage: + cls: 'remote' + args: + url: 'http://localhost:5002/' + scheduler: + cls: 'remote' + args: + url: 'http://localhost:5008/' - ## lister-gitlab + lister: + cls: 'local' + args: + # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls + db: 'postgresql:///lister-' - ### preparation steps + credentials: [] + cache_responses: True + cache_dir: /home/user/.cache/swh/lister// + ``` - 1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) - 2. mkdir ~/.config/swh/ ~/.cache/swh/lister/gitlab/ - 3. create configuration file ~/.config/swh/lister-gitlab.yml - 4. Bootstrap the db instance schema + Note: This expects storage (5002) and scheduler (5008) services to run locally - $ createdb lister-gitlab - $ python3 -m swh.lister.cli --db-url postgres:///lister-gitlab gitlab + ## lister-github - ### Configuration file sample + Once configured, you can execute a GitHub lister using the following instructions in a `python3` script: - $ cat ~/.config/swh/lister-gitlab.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-gitlab - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/gitlab + ```lang=python + import logging + from swh.lister.github.tasks import range_github_lister - Note: This expects storage (5002) and scheduler (5008) services to run locally + logging.basicConfig(level=logging.DEBUG) + range_github_lister(364, 365) + ... + ``` - ### Run + ## lister-gitlab - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> from swh.lister.gitlab.tasks import range_gitlab_lister; range_gitlab_lister(1, 2, - {'instance': 'debian', 'api_baseurl': 'https://salsa.debian.org/api/v4', 'sort': 'asc', 'per_page': 20}) - >>> from swh.lister.gitlab.tasks import full_gitlab_relister; full_gitlab_relister( - {'instance':'0xacab', 'api_baseurl':'https://0xacab.org/api/v4', 'sort': 'asc', 'per_page': 20}) - >>> from swh.lister.gitlab.tasks import incremental_gitlab_lister; incremental_gitlab_lister( - {'instance': 'freedesktop.org', 'api_baseurl': 'https://gitlab.freedesktop.org/api/v4', - 'sort': 'asc', 'per_page': 20}) + Once configured, you can execute a GitLab lister using the instructions detailed in the `python3` scripts below: + + ```lang=python + import logging + from swh.lister.gitlab.tasks import range_gitlab_lister + + logging.basicConfig(level=logging.DEBUG) + range_gitlab_lister(1, 2, { + 'instance': 'debian', + 'api_baseurl': 'https://salsa.debian.org/api/v4', + 'sort': 'asc', + 'per_page': 20 + }) + ``` + + ```lang=python + import logging + from swh.lister.gitlab.tasks import full_gitlab_relister + + logging.basicConfig(level=logging.DEBUG) + full_gitlab_relister({ + 'instance': '0xacab', + 'api_baseurl': 'https://0xacab.org/api/v4', + 'sort': 'asc', + 'per_page': 20 + }) + ``` + + ```lang=python + import logging + from swh.lister.gitlab.tasks import incremental_gitlab_lister + + logging.basicConfig(level=logging.DEBUG) + incremental_gitlab_lister({ + 'instance': 'freedesktop.org', + 'api_baseurl': 'https://gitlab.freedesktop.org/api/v4', + 'sort': 'asc', + 'per_page': 20 + }) + ``` ## lister-debian - ### preparation steps + Once configured, you can execute a Debian lister using the following instructions in a `python3` script: - 1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) - 2. mkdir ~/.config/swh/ ~/.cache/swh/lister/debian/ - 3. create configuration file ~/.config/swh/lister-debian.yml - 4. Bootstrap the db instance schema + ```lang=python + import logging + from swh.lister.debian.tasks import debian_lister - $ createdb lister-debian - $ python3 -m swh.lister.cli --db-url postgres:///lister-debian debian - - Note: This bootstraps a minimum data set needed for the debian - lister to run (for development) + logging.basicConfig(level=logging.DEBUG) + debian_lister('Debian') + ``` - ### Configuration file sample + ## lister-pypi - $ cat ~/.config/swh/lister-debian.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-debian - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/debian + Once configured, you can execute a PyPI lister using the following instructions in a `python3` script: - Note: This expects storage (5002) and scheduler (5008) services to run locally + ```lang=python + import logging + from swh.lister.pypi.tasks import pypi_lister - ### Run + logging.basicConfig(level=logging.DEBUG) + pypi_lister() + ``` - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> import logging; logging.basicConfig(level=logging.DEBUG); from swh.lister.debian.tasks import debian_lister; debian_lister('Debian') - DEBUG:root:Creating snapshot for distribution Distribution(Debian (deb) on http://deb.debian.org/debian/) on date 2018-07-27 09:22:50.461165+00:00 - DEBUG:root:Processing area Area(stretch/main of Debian) - DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): deb.debian.org - DEBUG:urllib3.connectionpool:http://deb.debian.org:80 "GET /debian//dists/stretch/main/source/Sources.xz HTTP/1.1" 302 325 - ... + ## lister-npm + Once configured, you can execute a npm lister using the following instructions in a `python3` REPL: - ## lister-pypi + ```lang=python + import logging + from swh.lister.npm.tasks import npm_lister - ### preparation steps + logging.basicConfig(level=logging.DEBUG) + npm_lister() + ``` - 1. git clone under $SWH_ENVIRONMENT_HOME/swh-lister (of your choosing) - 2. mkdir ~/.config/swh/ ~/.cache/swh/lister/pypi/ - 3. create configuration file ~/.config/swh/lister-pypi.yml - 4. Bootstrap the db instance schema + ## lister-phabricator - $ createdb lister-pypi - $ python3 -m swh.lister.cli --db-url postgres:///lister-pypi pypi + Once configured, you can execute a Phabricator lister using the following instructions in a `python3` script: - Note: This bootstraps a minimum data set needed for the pypi - lister to run (for development) + ```lang=python + import logging + from swh.lister.phabricator.tasks import incremental_phabricator_lister - ### Configuration file sample + logging.basicConfig(level=logging.DEBUG) + incremental_phabricator_lister(forge_url='https://forge.softwareheritage.org', api_token='XXXX') + ``` - $ cat ~/.config/swh/lister-pypi.yml - # see http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls - lister_db_url: postgres:///lister-pypi - credentials: [] - cache_responses: True - cache_dir: /home/user/.cache/swh/lister/pypi - - Note: This expects storage (5002) and scheduler (5008) services to run locally + Licensing + --------- - ### Run + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. - $ python3 - Python 3.6.6 (default, Jun 27 2018, 14:44:17) - [GCC 8.1.0] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> from swh.lister.pypi.tasks import pypi_lister; pypi_lister() - >>> + This program is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU General Public License for more details. + See top-level LICENSE file for the full text of the GNU General Public License + along with this program. Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) Classifier: Operating System :: OS Independent Classifier: Development Status :: 5 - Production/Stable Description-Content-Type: text/markdown Provides-Extra: testing diff --git a/swh.lister.egg-info/SOURCES.txt b/swh.lister.egg-info/SOURCES.txt index 15d1b85..de0f7d9 100644 --- a/swh.lister.egg-info/SOURCES.txt +++ b/swh.lister.egg-info/SOURCES.txt @@ -1,88 +1,99 @@ MANIFEST.in Makefile README.md requirements-swh.txt requirements-test.txt requirements.txt setup.py version.txt bin/ghlister swh/__init__.py swh.lister.egg-info/PKG-INFO swh.lister.egg-info/SOURCES.txt swh.lister.egg-info/dependency_links.txt swh.lister.egg-info/entry_points.txt swh.lister.egg-info/requires.txt swh.lister.egg-info/top_level.txt swh/lister/__init__.py swh/lister/_version.py swh/lister/cli.py swh/lister/utils.py swh/lister/bitbucket/__init__.py swh/lister/bitbucket/lister.py swh/lister/bitbucket/models.py swh/lister/bitbucket/tasks.py swh/lister/bitbucket/tests/__init__.py swh/lister/bitbucket/tests/api_empty_response.json swh/lister/bitbucket/tests/api_response.json swh/lister/bitbucket/tests/conftest.py swh/lister/bitbucket/tests/test_bb_lister.py swh/lister/bitbucket/tests/test_tasks.py swh/lister/core/__init__.py swh/lister/core/abstractattribute.py swh/lister/core/db_utils.py swh/lister/core/indexing_lister.py swh/lister/core/lister_base.py swh/lister/core/lister_transports.py swh/lister/core/models.py swh/lister/core/page_by_page_lister.py swh/lister/core/simple_lister.py swh/lister/core/tests/__init__.py swh/lister/core/tests/conftest.py swh/lister/core/tests/test_abstractattribute.py swh/lister/core/tests/test_lister.py swh/lister/core/tests/test_model.py swh/lister/debian/__init__.py swh/lister/debian/lister.py swh/lister/debian/tasks.py swh/lister/debian/utils.py swh/lister/debian/tests/__init__.py swh/lister/debian/tests/conftest.py swh/lister/debian/tests/test_tasks.py swh/lister/github/__init__.py swh/lister/github/lister.py swh/lister/github/models.py swh/lister/github/tasks.py swh/lister/github/tests/__init__.py swh/lister/github/tests/api_empty_response.json swh/lister/github/tests/api_response.json swh/lister/github/tests/conftest.py swh/lister/github/tests/test_gh_lister.py swh/lister/github/tests/test_tasks.py swh/lister/gitlab/__init__.py swh/lister/gitlab/lister.py swh/lister/gitlab/models.py swh/lister/gitlab/tasks.py swh/lister/gitlab/tests/__init__.py swh/lister/gitlab/tests/api_empty_response.json swh/lister/gitlab/tests/api_response.json swh/lister/gitlab/tests/conftest.py swh/lister/gitlab/tests/test_gitlab_lister.py swh/lister/gitlab/tests/test_tasks.py swh/lister/npm/__init__.py swh/lister/npm/lister.py swh/lister/npm/models.py swh/lister/npm/tasks.py swh/lister/npm/tests/api_empty_response.json swh/lister/npm/tests/api_inc_empty_response.json swh/lister/npm/tests/api_inc_response.json swh/lister/npm/tests/api_response.json +swh/lister/phabricator/__init__.py +swh/lister/phabricator/lister.py +swh/lister/phabricator/models.py +swh/lister/phabricator/tasks.py +swh/lister/phabricator/tests/__init__.py +swh/lister/phabricator/tests/api_empty_response.json +swh/lister/phabricator/tests/api_response.json +swh/lister/phabricator/tests/api_response_undefined_protocol.json +swh/lister/phabricator/tests/conftest.py +swh/lister/phabricator/tests/test_lister.py +swh/lister/phabricator/tests/test_tasks.py swh/lister/pypi/__init__.py swh/lister/pypi/lister.py swh/lister/pypi/models.py swh/lister/pypi/tasks.py swh/lister/pypi/tests/__init__.py swh/lister/pypi/tests/conftest.py swh/lister/pypi/tests/test_tasks.py swh/lister/tests/__init__.py swh/lister/tests/test_utils.py \ No newline at end of file diff --git a/swh.lister.egg-info/entry_points.txt b/swh.lister.egg-info/entry_points.txt index b08f0aa..cebbda5 100644 --- a/swh.lister.egg-info/entry_points.txt +++ b/swh.lister.egg-info/entry_points.txt @@ -1,4 +1,6 @@ [console_scripts] swh-lister=swh.lister.cli:cli + [swh.cli.subcommands] + lister=swh.lister.cli:lister \ No newline at end of file diff --git a/swh/lister/_version.py b/swh/lister/_version.py index 759f61b..bd20f67 100644 --- a/swh/lister/_version.py +++ b/swh/lister/_version.py @@ -1,5 +1,5 @@ # This file is automatically generated by setup.py. -__version__ = '0.0.21' -__sha__ = 'g0b8d1d4' -__revision__ = 'g0b8d1d4' +__version__ = '0.0.22' +__sha__ = 'gd6169c7' +__revision__ = 'gd6169c7' diff --git a/swh/lister/bitbucket/tasks.py b/swh/lister/bitbucket/tasks.py index 98be1ac..9985b48 100644 --- a/swh/lister/bitbucket/tasks.py +++ b/swh/lister/bitbucket/tasks.py @@ -1,48 +1,48 @@ # Copyright (C) 2017-2018 the Software Heritage developers # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import random from celery import group from swh.scheduler.celery_backend.config import app from .lister import BitBucketLister GROUP_SPLIT = 10000 def new_lister(api_baseurl='https://api.bitbucket.org/2.0'): return BitBucketLister(api_baseurl=api_baseurl) @app.task(name=__name__ + '.IncrementalBitBucketLister') def incremental_bitbucket_lister(**lister_args): lister = new_lister(**lister_args) lister.run(min_bound=lister.db_last_index(), max_bound=None) @app.task(name=__name__ + '.RangeBitBucketLister') def range_bitbucket_lister(start, end, **lister_args): lister = new_lister(**lister_args) lister.run(min_bound=start, max_bound=end) @app.task(name=__name__ + '.FullBitBucketRelister', bind=True) def full_bitbucket_relister(self, split=None, **lister_args): lister = new_lister(**lister_args) ranges = lister.db_partition_indices(split or GROUP_SPLIT) random.shuffle(ranges) promise = group(range_bitbucket_lister.s(minv, maxv, **lister_args) for minv, maxv in ranges)() self.log.debug('%s OK (spawned %s subtasks)' % (self.name, len(ranges))) try: promise.save() # so that we can restore the GroupResult in tests - except NotImplementedError: + except (NotImplementedError, AttributeError): self.log.info('Unable to call save_group with current result backend.') return promise.id @app.task(name=__name__ + '.ping') def ping(): return 'OK' diff --git a/swh/lister/cli.py b/swh/lister/cli.py index 0c16da0..e6563c9 100644 --- a/swh/lister/cli.py +++ b/swh/lister/cli.py @@ -1,122 +1,140 @@ # Copyright (C) 2018 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 import logging import click +from swh.core.cli import CONTEXT_SETTINGS + logger = logging.getLogger(__name__) -SUPPORTED_LISTERS = ['github', 'gitlab', 'bitbucket', 'debian', 'pypi', 'npm'] +SUPPORTED_LISTERS = ['github', 'gitlab', 'bitbucket', 'debian', 'pypi', + 'npm', 'phabricator'] + + +@click.group(name='lister', context_settings=CONTEXT_SETTINGS) +@click.pass_context +def lister(ctx): + '''Software Heritage Lister tools.''' + pass -@click.command() +@lister.command(name='db-init', context_settings=CONTEXT_SETTINGS) @click.option( '--db-url', '-d', default='postgres:///lister-gitlab.com', help='SQLAlchemy DB URL; see ' '') # noqa @click.argument('listers', required=1, nargs=-1, type=click.Choice(SUPPORTED_LISTERS + ['all'])) @click.option('--drop-tables', '-D', is_flag=True, default=False, help='Drop tables before creating the database schema') -def cli(db_url, listers, drop_tables): - """Initialize db model according to lister. +@click.pass_context +def cli(ctx, db_url, listers, drop_tables): + """Initialize the database model for given listers. """ override_conf = { - 'lister_db_url': db_url, 'lister': { 'cls': 'local', 'args': {'db': db_url} } } if 'all' in listers: listers = SUPPORTED_LISTERS for lister in listers: logger.info('Initializing lister %s', lister) insert_minimum_data = None if lister == 'github': from .github.models import IndexingModelBase as ModelBase from .github.lister import GitHubLister _lister = GitHubLister( api_baseurl='https://api.github.com', override_config=override_conf) elif lister == 'bitbucket': from .bitbucket.models import IndexingModelBase as ModelBase from .bitbucket.lister import BitBucketLister _lister = BitBucketLister( api_baseurl='https://api.bitbucket.org/2.0', override_config=override_conf) elif lister == 'gitlab': from .gitlab.models import ModelBase from .gitlab.lister import GitLabLister _lister = GitLabLister( api_baseurl='https://gitlab.com/api/v4/', override_config=override_conf) elif lister == 'debian': from .debian.lister import DebianLister ModelBase = DebianLister.MODEL # noqa _lister = DebianLister(override_config=override_conf) def insert_minimum_data(lister): from swh.storage.schemata.distribution import ( Distribution, Area) d = Distribution( name='Debian', type='deb', mirror_uri='http://deb.debian.org/debian/') lister.db_session.add(d) areas = [] for distribution_name in ['stretch']: for area_name in ['main', 'contrib', 'non-free']: areas.append(Area( name='%s/%s' % (distribution_name, area_name), distribution=d, )) lister.db_session.add_all(areas) lister.db_session.commit() elif lister == 'pypi': from .pypi.models import ModelBase from .pypi.lister import PyPILister _lister = PyPILister(override_config=override_conf) elif lister == 'npm': from .npm.models import IndexingModelBase as ModelBase from .npm.models import NpmVisitModel from .npm.lister import NpmLister _lister = NpmLister(override_config=override_conf) if drop_tables: NpmVisitModel.metadata.drop_all(_lister.db_engine) NpmVisitModel.metadata.create_all(_lister.db_engine) + elif lister == 'phabricator': + from .phabricator.models import IndexingModelBase as ModelBase + from .phabricator.lister import PhabricatorLister + _lister = PhabricatorLister( + forge_url='https://forge.softwareheritage.org', + api_token='', + override_config=override_conf) + else: raise ValueError( 'Invalid lister %s: only supported listers are %s' % (lister, SUPPORTED_LISTERS)) if drop_tables: logger.info('Dropping tables for %s', lister) ModelBase.metadata.drop_all(_lister.db_engine) logger.info('Creating tables for %s', lister) ModelBase.metadata.create_all(_lister.db_engine) if insert_minimum_data: logger.info('Inserting minimal data for %s', lister) try: insert_minimum_data(_lister) except Exception: logger.warning( 'Failed to insert minimum data in %s', lister) if __name__ == '__main__': cli() diff --git a/swh/lister/core/lister_base.py b/swh/lister/core/lister_base.py index 24d6c4a..b215810 100644 --- a/swh/lister/core/lister_base.py +++ b/swh/lister/core/lister_base.py @@ -1,551 +1,552 @@ # Copyright (C) 2015-2018 the Software Heritage developers # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import abc import datetime import gzip import json import logging import os import re import time from sqlalchemy import create_engine, func from sqlalchemy.orm import sessionmaker from swh.core import config from swh.scheduler import get_scheduler, utils from swh.storage import get_storage from .abstractattribute import AbstractAttribute logger = logging.getLogger(__name__) def utcnow(): return datetime.datetime.now(tz=datetime.timezone.utc) class FetchError(RuntimeError): def __init__(self, response): self.response = response def __str__(self): return repr(self.response) class SWHListerBase(abc.ABC, config.SWHConfig): """Lister core base class. Generally a source code hosting service provides an API endpoint for listing the set of stored repositories. A Lister is the discovery service responsible for finding this list, all at once or sequentially by parts, and queueing local tasks to fetch and ingest the referenced repositories. The core method in this class is ingest_data. Any subclasses should be calling this method one or more times to fetch and ingest data from API endpoints. See swh.lister.core.lister_base.SWHIndexingLister for example usage. This class cannot be instantiated. Any instantiable Lister descending from SWHListerBase must provide at least the required overrides. (see member docstrings for details): Required Overrides: MODEL def transport_request def transport_response_to_string def transport_response_simplified def transport_quota_check Optional Overrides: def filter_before_inject def is_within_bounds """ MODEL = AbstractAttribute('Subclass type (not instance)' ' of swh.lister.core.models.ModelBase' ' customized for a specific service.') LISTER_NAME = AbstractAttribute("Lister's name") @abc.abstractmethod def transport_request(self, identifier): """Given a target endpoint identifier to query, try once to request it. Implementation of this method determines the network request protocol. Args: identifier (string): unique identifier for an endpoint query. e.g. If the service indexes lists of repositories by date and time of creation, this might be that as a formatted string. Or it might be an integer UID. Or it might be nothing. It depends on what the service needs. Returns: the entire request response Raises: Will catch internal transport-dependent connection exceptions and raise swh.lister.core.lister_base.FetchError instead. Other non-connection exceptions should propagate unchanged. """ pass @abc.abstractmethod def transport_response_to_string(self, response): """Convert the server response into a formatted string for logging. Implementation of this method depends on the shape of the network response object returned by the transport_request method. Args: response: the server response Returns: a pretty string of the response """ pass @abc.abstractmethod def transport_response_simplified(self, response): """Convert the server response into list of a dict for each repo in the response, mapping columns in the lister's MODEL class to repo data. Implementation of this method depends on the server API spec and the shape of the network response object returned by the transport_request method. Args: response: response object from the server. Returns: list of repo MODEL dicts ( eg. [{'uid': r['id'], etc.} for r in response.json()] ) """ pass @abc.abstractmethod def transport_quota_check(self, response): """Check server response to see if we're hitting request rate limits. Implementation of this method depends on the server communication protocol and API spec and the shape of the network response object returned by the transport_request method. Args: response (session response): complete API query response Returns: 1) must retry request? True/False 2) seconds to delay if True """ pass def filter_before_inject(self, models_list): """Function run after transport_response_simplified but before injection into the local db and creation of workers. Can be used to eliminate some of the results if necessary. MAY BE OVERRIDDEN if an intermediate Lister class needs to filter results before injection without requiring every child class to do so. Args: models_list: list of dicts returned by transport_response_simplified. Returns: models_list with entries changed according to custom logic. """ return models_list def do_additional_checks(self, models_list): """Execute some additional checks on the model list. For example, to check for existing repositories in the db. MAY BE OVERRIDDEN if an intermediate Lister class needs to check some more the results before injection. Checks are fine by default, returns the models_list as is by default. Args: models_list: list of dicts returned by transport_response_simplified. Returns: models_list with entries if checks ok, False otherwise """ return models_list def is_within_bounds(self, inner, lower=None, upper=None): """See if a sortable value is inside the range [lower,upper]. MAY BE OVERRIDDEN, for example if the server indexable* key is technically sortable but not automatically so. * - ( see: swh.lister.core.indexing_lister.SWHIndexingLister ) Args: inner (sortable type): the value being checked lower (sortable type): optional lower bound upper (sortable type): optional upper bound Returns: whether inner is confined by the optional lower and upper bounds """ try: if lower is None and upper is None: return True elif lower is None: ret = inner <= upper elif upper is None: ret = inner >= lower else: ret = lower <= inner <= upper self.string_pattern_check(inner, lower, upper) except Exception as e: logger.error(str(e) + ': %s, %s, %s' % (('inner=%s%s' % (type(inner), inner)), ('lower=%s%s' % (type(lower), lower)), ('upper=%s%s' % (type(upper), upper))) ) raise return ret # You probably don't need to override anything below this line. DEFAULT_CONFIG = { 'storage': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://localhost:5002/' }, }), 'scheduler': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://localhost:5008/' }, }), 'lister': ('dict', { 'cls': 'local', 'args': { 'db': 'postgresql:///lister', }, }), } @property def CONFIG_BASE_FILENAME(self): # noqa: N802 return 'lister_%s' % self.LISTER_NAME @property def ADDITIONAL_CONFIG(self): # noqa: N802 return { 'credentials': ('list[dict]', []), 'cache_responses': ('bool', False), 'cache_dir': ('str', '~/.cache/swh/lister/%s' % self.LISTER_NAME), } INITIAL_BACKOFF = 10 MAX_RETRIES = 7 CONN_SLEEP = 10 def __init__(self, override_config=None): self.backoff = self.INITIAL_BACKOFF logger.debug('Loading config from %s' % self.CONFIG_BASE_FILENAME) self.config = self.parse_config_file( base_filename=self.CONFIG_BASE_FILENAME, additional_configs=[self.ADDITIONAL_CONFIG] ) self.config['cache_dir'] = os.path.expanduser(self.config['cache_dir']) if self.config['cache_responses']: config.prepare_folders(self.config, 'cache_dir') if override_config: self.config.update(override_config) logger.debug('%s CONFIG=%s' % (self, self.config)) self.storage = get_storage(**self.config['storage']) self.scheduler = get_scheduler(**self.config['scheduler']) self.db_engine = create_engine(self.config['lister']['args']['db']) self.mk_session = sessionmaker(bind=self.db_engine) self.db_session = self.mk_session() def reset_backoff(self): """Reset exponential backoff timeout to initial level.""" self.backoff = self.INITIAL_BACKOFF def back_off(self): """Get next exponential backoff timeout.""" ret = self.backoff self.backoff *= 10 return ret def safely_issue_request(self, identifier): """Make network request with retries, rate quotas, and response logs. Protocol is handled by the implementation of the transport_request method. Args: identifier: resource identifier Returns: server response """ retries_left = self.MAX_RETRIES do_cache = self.config['cache_responses'] r = None while retries_left > 0: try: r = self.transport_request(identifier) except FetchError: # network-level connection error, try again logger.warning( 'connection error on %s: sleep for %d seconds' % (identifier, self.CONN_SLEEP)) time.sleep(self.CONN_SLEEP) retries_left -= 1 continue if do_cache: self.save_response(r) # detect throttling must_retry, delay = self.transport_quota_check(r) if must_retry: logger.warning( 'rate limited on %s: sleep for %f seconds' % (identifier, delay)) time.sleep(delay) else: # request ok break retries_left -= 1 if not retries_left: logger.warning( 'giving up on %s: max retries exceeded' % identifier) return r def db_query_equal(self, key, value): """Look in the db for a row with key == value Args: key: column key to look at value: value to look for in that column Returns: sqlalchemy.ext.declarative.declarative_base object with the given key == value """ if isinstance(key, str): key = self.MODEL.__dict__[key] return self.db_session.query(self.MODEL) \ .filter(key == value).first() def winnow_models(self, mlist, key, to_remove): """Given a list of models, remove any with matching some member of a list of values. Args: mlist (list of model rows): the initial list of models key (column): the column to filter on to_remove (list): if anything in mlist has column equal to one of the values in to_remove, it will be removed from the result Returns: A list of model rows starting from mlist minus any matching rows """ if isinstance(key, str): key = self.MODEL.__dict__[key] if to_remove: return mlist.filter(~key.in_(to_remove)).all() else: return mlist.all() def db_num_entries(self): """Return the known number of entries in the lister db""" return self.db_session.query(func.count('*')).select_from(self.MODEL) \ .scalar() def db_inject_repo(self, model_dict): """Add/update a new repo to the db and mark it last_seen now. Args: model_dict: dictionary mapping model keys to values Returns: new or updated sqlalchemy.ext.declarative.declarative_base object associated with the injection """ sql_repo = self.db_query_equal('uid', model_dict['uid']) if not sql_repo: sql_repo = self.MODEL(**model_dict) self.db_session.add(sql_repo) else: for k in model_dict: setattr(sql_repo, k, model_dict[k]) sql_repo.last_seen = utcnow() return sql_repo def origin_dict(self, origin_type, origin_url, **kwargs): """Return special dict format for the origins list Args: origin_type (string) origin_url (string) Returns: the same information in a different form """ return { 'type': origin_type, 'url': origin_url, } def task_dict(self, origin_type, origin_url, **kwargs): """Return special dict format for the tasks list Args: origin_type (string) origin_url (string) Returns: the same information in a different form """ - _type = 'origin-update-%s' % origin_type + _type = 'load-%s' % origin_type _policy = 'recurring' return utils.create_task_dict(_type, _policy, origin_url) def string_pattern_check(self, a, b, c=None): """When comparing indexable types in is_within_bounds, complex strings may not be allowed to differ in basic structure. If they do, it could be a sign of not understanding the data well. For instance, an ISO 8601 time string cannot be compared against its urlencoded equivalent, but this is an easy mistake to accidentally make. This method acts as a friendly sanity check. Args: a (string): inner component of the is_within_bounds method b (string): lower component of the is_within_bounds method c (string): upper component of the is_within_bounds method Returns: nothing Raises: TypeError if strings a, b, and c don't conform to the same basic pattern. """ if isinstance(a, str): a_pattern = re.sub('[a-zA-Z0-9]', '[a-zA-Z0-9]', re.escape(a)) if (isinstance(b, str) and (re.match(a_pattern, b) is None) or isinstance(c, str) and (re.match(a_pattern, c) is None)): logger.debug(a_pattern) raise TypeError('incomparable string patterns detected') def inject_repo_data_into_db(self, models_list): """Inject data into the db. Args: models_list: list of dicts mapping keys from the db model for each repo to be injected Returns: dict of uid:sql_repo pairs """ injected_repos = {} for m in models_list: injected_repos[m['uid']] = self.db_inject_repo(m) return injected_repos def create_missing_origins_and_tasks(self, models_list, injected_repos): """Find any newly created db entries that don't yet have tasks or origin objects assigned. Args: models_list: a list of dicts mapping keys in the db model for each repo injected_repos: dict of uid:sql_repo pairs that have just been created Returns: Nothing. Modifies injected_repos. """ origins = {} tasks = {} def _origin_key(m): _type = m.get('origin_type', m.get('type')) _url = m.get('origin_url', m.get('url')) return '%s-%s' % (_type, _url) def _task_key(m): - return '%s-%s' % (m['type'], json.dumps(m['arguments'])) + return '%s-%s' % (m['type'], + json.dumps(m['arguments'], sort_keys=True)) for m in models_list: ir = injected_repos[m['uid']] if not ir.origin_id: origin_dict = self.origin_dict(**m) origins[_origin_key(m)] = (ir, m, origin_dict) if not ir.task_id: task_dict = self.task_dict(**m) tasks[_task_key(task_dict)] = (ir, m, task_dict) new_origins = self.storage.origin_add( (origin_dicts for (_, _, origin_dicts) in origins.values())) for origin in new_origins: ir, m, _ = origins[_origin_key(origin)] ir.origin_id = origin['id'] new_tasks = self.scheduler.create_tasks( (task_dicts for (_, _, task_dicts) in tasks.values())) for task in new_tasks: ir, m, _ = tasks[_task_key(task)] ir.task_id = task['id'] def ingest_data(self, identifier, checks=False): """The core data fetch sequence. Request server endpoint. Simplify and filter response list of repositories. Inject repo information into local db. Queue loader tasks for linked repositories. Args: identifier: Resource identifier. checks (bool): Additional checks required """ # Request (partial?) list of repositories info response = self.safely_issue_request(identifier) if not response: return response, [] models_list = self.transport_response_simplified(response) models_list = self.filter_before_inject(models_list) if checks: models_list = self.do_additional_checks(models_list) if not models_list: return response, [] # inject into local db injected = self.inject_repo_data_into_db(models_list) # queue workers self.create_missing_origins_and_tasks(models_list, injected) return response, injected def save_response(self, response): """Log the response from a server request to a cache dir. Args: response: full server response cache_dir: system path for cache dir Returns: nothing """ datepath = utcnow().isoformat() fname = os.path.join( self.config['cache_dir'], datepath + '.gz', ) with gzip.open(fname, 'w') as f: f.write(bytes( self.transport_response_to_string(response), 'UTF-8' )) diff --git a/swh/lister/core/tests/conftest.py b/swh/lister/core/tests/conftest.py index bdbfa02..17ce8f2 100644 --- a/swh/lister/core/tests/conftest.py +++ b/swh/lister/core/tests/conftest.py @@ -1,14 +1,15 @@ import pytest from swh.scheduler.tests.conftest import * # noqa @pytest.fixture(scope='session') def celery_includes(): return [ 'swh.lister.bitbucket.tasks', 'swh.lister.debian.tasks', 'swh.lister.github.tasks', 'swh.lister.gitlab.tasks', 'swh.lister.npm.tasks', 'swh.lister.pypi.tasks', + 'swh.lister.phabricator.tasks', ] diff --git a/swh/lister/github/tasks.py b/swh/lister/github/tasks.py index 97dfad5..7d91dc9 100644 --- a/swh/lister/github/tasks.py +++ b/swh/lister/github/tasks.py @@ -1,49 +1,48 @@ # Copyright (C) 2017-2018 the Software Heritage developers # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import random from celery import group from swh.scheduler.celery_backend.config import app from swh.lister.github.lister import GitHubLister GROUP_SPLIT = 10000 def new_lister(api_baseurl='https://api.github.com', **kw): return GitHubLister(api_baseurl=api_baseurl, **kw) @app.task(name=__name__ + '.IncrementalGitHubLister') def incremental_github_lister(**lister_args): lister = new_lister(**lister_args) lister.run(min_bound=lister.db_last_index(), max_bound=None) @app.task(name=__name__ + '.RangeGitHubLister') def range_github_lister(start, end, **lister_args): lister = new_lister(**lister_args) lister.run(min_bound=start, max_bound=end) @app.task(name=__name__ + '.FullGitHubRelister', bind=True) def full_github_relister(self, split=None, **lister_args): lister = new_lister(**lister_args) ranges = lister.db_partition_indices(split or GROUP_SPLIT) random.shuffle(ranges) promise = group(range_github_lister.s(minv, maxv, **lister_args) for minv, maxv in ranges)() self.log.debug('%s OK (spawned %s subtasks)' % (self.name, len(ranges))) try: promise.save() # so that we can restore the GroupResult in tests - except NotImplementedError: + except (NotImplementedError, AttributeError): self.log.info('Unable to call save_group with current result backend.') - raise return promise.id @app.task(name=__name__ + '.ping') def ping(): return 'OK' diff --git a/swh/lister/gitlab/tasks.py b/swh/lister/gitlab/tasks.py index 9e57081..aee2c19 100644 --- a/swh/lister/gitlab/tasks.py +++ b/swh/lister/gitlab/tasks.py @@ -1,57 +1,57 @@ # Copyright (C) 2018 the Software Heritage developers # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import random from celery import group from swh.scheduler.celery_backend.config import app from .. import utils from .lister import GitLabLister NBPAGES = 10 def new_lister(api_baseurl='https://gitlab.com/api/v4', instance=None, sort='asc', per_page=20): return GitLabLister( api_baseurl=api_baseurl, instance=instance, sort=sort, per_page=per_page) @app.task(name=__name__ + '.IncrementalGitLabLister') def incremental_gitlab_lister(**lister_args): lister_args['sort'] = 'desc' lister = new_lister(**lister_args) total_pages = lister.get_pages_information()[1] # stopping as soon as existing origins for that instance are detected lister.run(min_bound=1, max_bound=total_pages, check_existence=True) @app.task(name=__name__ + '.RangeGitLabLister') def range_gitlab_lister(start, end, **lister_args): lister = new_lister(**lister_args) lister.run(min_bound=start, max_bound=end) @app.task(name=__name__ + '.FullGitLabRelister', bind=True) def full_gitlab_relister(self, **lister_args): lister = new_lister(**lister_args) _, total_pages, _ = lister.get_pages_information() ranges = list(utils.split_range(total_pages, NBPAGES)) random.shuffle(ranges) promise = group(range_gitlab_lister.s(minv, maxv, **lister_args) for minv, maxv in ranges)() self.log.debug('%s OK (spawned %s subtasks)' % (self.name, len(ranges))) try: promise.save() - except NotImplementedError: + except (NotImplementedError, AttributeError): self.log.info('Unable to call save_group with current result backend.') return promise.id @app.task(name=__name__ + '.ping') def ping(): return 'OK' diff --git a/swh/lister/phabricator/__init__.py b/swh/lister/phabricator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/swh/lister/phabricator/lister.py b/swh/lister/phabricator/lister.py new file mode 100644 index 0000000..dfb77e3 --- /dev/null +++ b/swh/lister/phabricator/lister.py @@ -0,0 +1,138 @@ +# Copyright (C) 2019 the Software Heritage developers +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + + +from swh.lister.core.indexing_lister import SWHIndexingHttpLister +from swh.lister.phabricator.models import PhabricatorModel +from collections import defaultdict + + +class PhabricatorLister(SWHIndexingHttpLister): + + PATH_TEMPLATE = '&order=oldest&attachments[uris]=1&after=%s' + MODEL = PhabricatorModel + LISTER_NAME = 'phabricator' + + def __init__(self, forge_url, api_token, override_config=None): + if forge_url.endswith("/"): + forge_url = forge_url[:-1] + self.forge_url = forge_url + api_endpoint = ('api/diffusion.repository.' + 'search?api.token=%s') % api_token + api_baseurl = '%s/%s' % (forge_url, api_endpoint) + super().__init__(api_baseurl=api_baseurl, + override_config=override_config) + + def request_headers(self): + """ + (Override) Set requests headers to send when querying the + Phabricator API + """ + return {'User-Agent': 'Software Heritage phabricator lister', + 'Accept': 'application/json'} + + def get_model_from_repo(self, repo): + url = get_repo_url(repo['attachments']['uris']['uris']) + if url is None: + return None + return { + 'uid': self.forge_url + str(repo['id']), + 'indexable': repo['id'], + 'name': repo['fields']['shortName'], + 'full_name': repo['fields']['name'], + 'html_url': url, + 'origin_url': url, + 'description': None, + 'origin_type': repo['fields']['vcs'] + } + + def get_next_target_from_response(self, response): + body = response.json()['result']['cursor'] + if body['after'] != 'null': + return body['after'] + else: + return None + + def transport_response_simplified(self, response): + repos = response.json() + if repos['result'] is None: + raise ValueError( + 'Problem during information fetch: %s' % repos['error_code']) + repos = repos['result']['data'] + return [self.get_model_from_repo(repo) for repo in repos] + + def filter_before_inject(self, models_list): + """ + (Overrides) SWHIndexingLister.filter_before_inject + Bounds query results by this Lister's set max_index. + """ + models_list = [m for m in models_list if m is not None] + return super().filter_before_inject(models_list) + + def _bootstrap_repositories_listing(self): + """ + Method called when no min_bound value has been provided + to the lister. Its purpose is to: + + 1. get the first repository data hosted on the Phabricator + instance + + 2. inject them into the lister database + + 3. return the first repository index to start the listing + after that value + + Returns: + int: The first repository index + """ + params = '&order=oldest&limit=1' + response = self.safely_issue_request(params) + models_list = self.transport_response_simplified(response) + self.max_index = models_list[0]['indexable'] + models_list = self.filter_before_inject(models_list) + injected = self.inject_repo_data_into_db(models_list) + self.create_missing_origins_and_tasks(models_list, injected) + return self.max_index + + def run(self, min_bound=None, max_bound=None): + """ + (Override) Run the lister on the specified Phabricator instance + + Args: + min_bound (int): Optional repository index to start the listing + after it + max_bound (int): Optional repository index to stop the listing + after it + """ + # initial call to the lister, we need to bootstrap it in that case + if min_bound is None: + min_bound = self._bootstrap_repositories_listing() + super().run(min_bound, max_bound) + + +def get_repo_url(attachments): + """ + Return url for a hosted repository from its uris attachments according + to the following priority lists: + * protocol: https > http + * identifier: shortname > callsign > id + """ + processed_urls = defaultdict(dict) + for uri in attachments: + protocol = uri['fields']['builtin']['protocol'] + url = uri['fields']['uri']['effective'] + identifier = uri['fields']['builtin']['identifier'] + if protocol in ('http', 'https'): + processed_urls[protocol][identifier] = url + elif protocol is None: + for protocol in ('https', 'http'): + if url.startswith(protocol): + processed_urls[protocol]['undefined'] = url + break + for protocol in ['https', 'http']: + for identifier in ['shortname', 'callsign', 'id', 'undefined']: + if (protocol in processed_urls and + identifier in processed_urls[protocol]): + return processed_urls[protocol][identifier] + return None diff --git a/swh/lister/phabricator/models.py b/swh/lister/phabricator/models.py new file mode 100644 index 0000000..ba7ee73 --- /dev/null +++ b/swh/lister/phabricator/models.py @@ -0,0 +1,15 @@ +# Copyright (C) 2019 the Software Heritage developers +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from sqlalchemy import Column, String, Integer + +from swh.lister.core.models import IndexingModelBase + + +class PhabricatorModel(IndexingModelBase): + """a Phabricator repository""" + __tablename__ = 'phabricator_repos' + + uid = Column(String, primary_key=True) + indexable = Column(Integer, index=True) diff --git a/swh/lister/phabricator/tasks.py b/swh/lister/phabricator/tasks.py new file mode 100644 index 0000000..83ecd67 --- /dev/null +++ b/swh/lister/phabricator/tasks.py @@ -0,0 +1,28 @@ +# Copyright (C) 2019 the Software Heritage developers +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from swh.scheduler.celery_backend.config import app +from swh.lister.phabricator.lister import PhabricatorLister + + +def new_lister( + forge_url='https://forge.softwareheritage.org', api_token='', **kw): + return PhabricatorLister(forge_url=forge_url, api_token=api_token, **kw) + + +@app.task(name=__name__ + '.IncrementalPhabricatorLister') +def incremental_phabricator_lister(**lister_args): + lister = new_lister(**lister_args) + lister.run(min_bound=lister.db_last_index()) + + +@app.task(name=__name__ + '.FullPhabricatorLister') +def full_phabricator_lister(**lister_args): + lister = new_lister(**lister_args) + lister.run() + + +@app.task(name=__name__ + '.ping') +def ping(): + return 'OK' diff --git a/swh/lister/phabricator/tests/__init__.py b/swh/lister/phabricator/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/swh/lister/phabricator/tests/api_empty_response.json b/swh/lister/phabricator/tests/api_empty_response.json new file mode 100644 index 0000000..ea459bf --- /dev/null +++ b/swh/lister/phabricator/tests/api_empty_response.json @@ -0,0 +1,8 @@ +{ + "result": { + "data": [], + "cursor": { + "after": null + } + } +} diff --git a/swh/lister/phabricator/tests/api_response.json b/swh/lister/phabricator/tests/api_response.json new file mode 100644 index 0000000..df86db4 --- /dev/null +++ b/swh/lister/phabricator/tests/api_response.json @@ -0,0 +1,2538 @@ +{ + "result": { + "data": [ + { + "id": 3, + "type": "REPO", + "phid": "PHID-REPO-cjukcpw6hvovij27fq26", + "fields": { + "name": "Puppet Environment", + "vcs": "git", + "callsign": "SENV", + "shortName": "puppet-environment", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877163, + "dateModified": 1519668896, + "policy": { + "view": "public", + "edit": "PHID-PROJ-w6dmg2vssgiimpjpduga", + "diffusion.push": "PHID-PROJ-w6dmg2vssgiimpjpduga" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "819", + "type": "RURI", + "phid": "PHID-RURI-zwot2up6krkp3jcovcae", + "fields": { + "repositoryPHID": "PHID-REPO-cjukcpw6hvovij27fq26", + "uri": { + "raw": "git@github.com:SoftwareHeritage/puppet-environment.git", + "display": "git@github.com:SoftwareHeritage/puppet-environment.git", + "effective": "git@github.com:SoftwareHeritage/puppet-environment.git", + "normalized": "github.com/SoftwareHeritage/puppet-environment" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1486571136", + "dateModified": "1486571136" + } + }, + { + "id": "6", + "type": "RURI", + "phid": "PHID-RURI-keaejv5qus3jmx2abpj5", + "fields": { + "repositoryPHID": "PHID-REPO-cjukcpw6hvovij27fq26", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/3/puppet-environment.git", + "effective": "https://forge.softwareheritage.org/diffusion/3/puppet-environment.git", + "normalized": "forge.softwareheritage.org/diffusion/3" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "5", + "type": "RURI", + "phid": "PHID-RURI-qiudbgz27drv4mkesmvj", + "fields": { + "repositoryPHID": "PHID-REPO-cjukcpw6hvovij27fq26", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/puppet-environment.git", + "effective": "https://forge.softwareheritage.org/source/puppet-environment.git", + "normalized": "forge.softwareheritage.org/source/puppet-environment" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "4", + "type": "RURI", + "phid": "PHID-RURI-zetkryxi2ibq66yl3pb5", + "fields": { + "repositoryPHID": "PHID-REPO-cjukcpw6hvovij27fq26", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/SENV/puppet-environment.git", + "effective": "https://forge.softwareheritage.org/diffusion/SENV/puppet-environment.git", + "normalized": "forge.softwareheritage.org/diffusion/SENV" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "3", + "type": "RURI", + "phid": "PHID-RURI-yzg3kzoqfbh4zd5buvij", + "fields": { + "repositoryPHID": "PHID-REPO-cjukcpw6hvovij27fq26", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/3/puppet-environment.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/3/puppet-environment.git", + "normalized": "forge.softwareheritage.org/diffusion/3" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "2", + "type": "RURI", + "phid": "PHID-RURI-2gvaroqipsieeje5z3sc", + "fields": { + "repositoryPHID": "PHID-REPO-cjukcpw6hvovij27fq26", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/puppet-environment.git", + "effective": "ssh://git@forge.softwareheritage.org/source/puppet-environment.git", + "normalized": "forge.softwareheritage.org/source/puppet-environment" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "1", + "type": "RURI", + "phid": "PHID-RURI-ro4bvq6y4kzcj66hcaym", + "fields": { + "repositoryPHID": "PHID-REPO-cjukcpw6hvovij27fq26", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/SENV/puppet-environment.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/SENV/puppet-environment.git", + "normalized": "forge.softwareheritage.org/diffusion/SENV" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 4, + "type": "REPO", + "phid": "PHID-REPO-qlaiewlsf3plx563upbk", + "fields": { + "name": "Git cloner", + "vcs": "git", + "callsign": "DCLG", + "shortName": "swh-cloner-git", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877362, + "dateModified": 1497281975, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "no-one" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "15", + "type": "RURI", + "phid": "PHID-RURI-77knwcyarzlxm7scz26b", + "fields": { + "repositoryPHID": "PHID-REPO-qlaiewlsf3plx563upbk", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/4/swh-cloner-git.git", + "effective": "https://forge.softwareheritage.org/diffusion/4/swh-cloner-git.git", + "normalized": "forge.softwareheritage.org/diffusion/4" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "14", + "type": "RURI", + "phid": "PHID-RURI-kt6nsyous73u7zifz4q3", + "fields": { + "repositoryPHID": "PHID-REPO-qlaiewlsf3plx563upbk", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/swh-cloner-git.git", + "effective": "https://forge.softwareheritage.org/source/swh-cloner-git.git", + "normalized": "forge.softwareheritage.org/source/swh-cloner-git" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "13", + "type": "RURI", + "phid": "PHID-RURI-nqw5ydn7f2iweydcpx4j", + "fields": { + "repositoryPHID": "PHID-REPO-qlaiewlsf3plx563upbk", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/DCLG/swh-cloner-git.git", + "effective": "https://forge.softwareheritage.org/diffusion/DCLG/swh-cloner-git.git", + "normalized": "forge.softwareheritage.org/diffusion/DCLG" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "12", + "type": "RURI", + "phid": "PHID-RURI-m4dywkvx3rgqqognyifk", + "fields": { + "repositoryPHID": "PHID-REPO-qlaiewlsf3plx563upbk", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/4/swh-cloner-git.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/4/swh-cloner-git.git", + "normalized": "forge.softwareheritage.org/diffusion/4" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "11", + "type": "RURI", + "phid": "PHID-RURI-nyty74ac2wwbm2pa43xi", + "fields": { + "repositoryPHID": "PHID-REPO-qlaiewlsf3plx563upbk", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/swh-cloner-git.git", + "effective": "ssh://git@forge.softwareheritage.org/source/swh-cloner-git.git", + "normalized": "forge.softwareheritage.org/source/swh-cloner-git" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "10", + "type": "RURI", + "phid": "PHID-RURI-jycms67trm5d7cnw2yus", + "fields": { + "repositoryPHID": "PHID-REPO-qlaiewlsf3plx563upbk", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DCLG/swh-cloner-git.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DCLG/swh-cloner-git.git", + "normalized": "forge.softwareheritage.org/diffusion/DCLG" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 5, + "type": "REPO", + "phid": "PHID-REPO-rwxybwflua3hdbvuefxn", + "fields": { + "name": "Foundations and core functionalities", + "vcs": "git", + "callsign": "DCORE", + "shortName": "swh-core", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877840, + "dateModified": 1543276355, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "users" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "682", + "type": "RURI", + "phid": "PHID-RURI-qk67i7b7zx25q4ziyd7v", + "fields": { + "repositoryPHID": "PHID-REPO-rwxybwflua3hdbvuefxn", + "uri": { + "raw": "git@github.com:SoftwareHeritage/swh-core.git", + "display": "git@github.com:SoftwareHeritage/swh-core.git", + "effective": "git@github.com:SoftwareHeritage/swh-core.git", + "normalized": "github.com/SoftwareHeritage/swh-core" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1474544586", + "dateModified": "1519746825" + } + }, + { + "id": "24", + "type": "RURI", + "phid": "PHID-RURI-p3v5va73gn262dokrtqk", + "fields": { + "repositoryPHID": "PHID-REPO-rwxybwflua3hdbvuefxn", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/5/swh-core.git", + "effective": "https://forge.softwareheritage.org/diffusion/5/swh-core.git", + "normalized": "forge.softwareheritage.org/diffusion/5" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "23", + "type": "RURI", + "phid": "PHID-RURI-rqmdebjngj7sm5smdx5v", + "fields": { + "repositoryPHID": "PHID-REPO-rwxybwflua3hdbvuefxn", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/swh-core.git", + "effective": "https://forge.softwareheritage.org/source/swh-core.git", + "normalized": "forge.softwareheritage.org/source/swh-core" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "22", + "type": "RURI", + "phid": "PHID-RURI-u3qrap3qfq4h56lxpycr", + "fields": { + "repositoryPHID": "PHID-REPO-rwxybwflua3hdbvuefxn", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/DCORE/swh-core.git", + "effective": "https://forge.softwareheritage.org/diffusion/DCORE/swh-core.git", + "normalized": "forge.softwareheritage.org/diffusion/DCORE" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "21", + "type": "RURI", + "phid": "PHID-RURI-b3cedaogb477uumj4dbw", + "fields": { + "repositoryPHID": "PHID-REPO-rwxybwflua3hdbvuefxn", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/5/swh-core.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/5/swh-core.git", + "normalized": "forge.softwareheritage.org/diffusion/5" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "20", + "type": "RURI", + "phid": "PHID-RURI-ohkxvptjxmvjrcllvqx6", + "fields": { + "repositoryPHID": "PHID-REPO-rwxybwflua3hdbvuefxn", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/swh-core.git", + "effective": "ssh://git@forge.softwareheritage.org/source/swh-core.git", + "normalized": "forge.softwareheritage.org/source/swh-core" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "19", + "type": "RURI", + "phid": "PHID-RURI-rbsryj7opmgiruolvwtt", + "fields": { + "repositoryPHID": "PHID-REPO-rwxybwflua3hdbvuefxn", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DCORE/swh-core.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DCORE/swh-core.git", + "normalized": "forge.softwareheritage.org/diffusion/DCORE" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 6, + "type": "REPO", + "phid": "PHID-REPO-ehgm4jpvhhzxycwucv7j", + "fields": { + "name": "Development environment", + "vcs": "git", + "callsign": "DENV", + "shortName": "swh-environment", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877840, + "dateModified": 1486563625, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "PHID-PROJ-zqzomfxtpkd5jgltioiy" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "805", + "type": "RURI", + "phid": "PHID-RURI-yyh5h5zf6tz7t4irmkdn", + "fields": { + "repositoryPHID": "PHID-REPO-ehgm4jpvhhzxycwucv7j", + "uri": { + "raw": "git@github.com:SoftwareHeritage/swh-environment.git", + "display": "git@github.com:SoftwareHeritage/swh-environment.git", + "effective": "git@github.com:SoftwareHeritage/swh-environment.git", + "normalized": "github.com/SoftwareHeritage/swh-environment" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1486563625", + "dateModified": "1486563625" + } + }, + { + "id": "33", + "type": "RURI", + "phid": "PHID-RURI-554w3bgme6f2w4ucy6hg", + "fields": { + "repositoryPHID": "PHID-REPO-ehgm4jpvhhzxycwucv7j", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/6/swh-environment.git", + "effective": "https://forge.softwareheritage.org/diffusion/6/swh-environment.git", + "normalized": "forge.softwareheritage.org/diffusion/6" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "32", + "type": "RURI", + "phid": "PHID-RURI-w7rukym3vek35buik6pj", + "fields": { + "repositoryPHID": "PHID-REPO-ehgm4jpvhhzxycwucv7j", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/swh-environment.git", + "effective": "https://forge.softwareheritage.org/source/swh-environment.git", + "normalized": "forge.softwareheritage.org/source/swh-environment" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "31", + "type": "RURI", + "phid": "PHID-RURI-tburtpmgzfiffppz25gl", + "fields": { + "repositoryPHID": "PHID-REPO-ehgm4jpvhhzxycwucv7j", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/DENV/swh-environment.git", + "effective": "https://forge.softwareheritage.org/diffusion/DENV/swh-environment.git", + "normalized": "forge.softwareheritage.org/diffusion/DENV" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "30", + "type": "RURI", + "phid": "PHID-RURI-5mmjtixnhlhjg3fnxm4i", + "fields": { + "repositoryPHID": "PHID-REPO-ehgm4jpvhhzxycwucv7j", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/6/swh-environment.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/6/swh-environment.git", + "normalized": "forge.softwareheritage.org/diffusion/6" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "29", + "type": "RURI", + "phid": "PHID-RURI-hbf6gm4izewpmxckgn5h", + "fields": { + "repositoryPHID": "PHID-REPO-ehgm4jpvhhzxycwucv7j", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/swh-environment.git", + "effective": "ssh://git@forge.softwareheritage.org/source/swh-environment.git", + "normalized": "forge.softwareheritage.org/source/swh-environment" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "28", + "type": "RURI", + "phid": "PHID-RURI-vgawokhxonab42if7dhm", + "fields": { + "repositoryPHID": "PHID-REPO-ehgm4jpvhhzxycwucv7j", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DENV/swh-environment.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DENV/swh-environment.git", + "normalized": "forge.softwareheritage.org/diffusion/DENV" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 7, + "type": "REPO", + "phid": "PHID-REPO-gkyta7fyiybtuqd4sg2x", + "fields": { + "name": "Debian package loader", + "vcs": "git", + "callsign": "DLDDEB", + "shortName": "swh-loader-debian", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877840, + "dateModified": 1547058787, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "users" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "808", + "type": "RURI", + "phid": "PHID-RURI-7dew7hh2u6rczzrudbw7", + "fields": { + "repositoryPHID": "PHID-REPO-gkyta7fyiybtuqd4sg2x", + "uri": { + "raw": "git@github.com:SoftwareHeritage/swh-loader-debian.git", + "display": "git@github.com:SoftwareHeritage/swh-loader-debian.git", + "effective": "git@github.com:SoftwareHeritage/swh-loader-debian.git", + "normalized": "github.com/SoftwareHeritage/swh-loader-debian" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1486571106", + "dateModified": "1486571106" + } + }, + { + "id": "42", + "type": "RURI", + "phid": "PHID-RURI-2t2qxl4xiqz76vutaqbt", + "fields": { + "repositoryPHID": "PHID-REPO-gkyta7fyiybtuqd4sg2x", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/7/swh-loader-debian.git", + "effective": "https://forge.softwareheritage.org/diffusion/7/swh-loader-debian.git", + "normalized": "forge.softwareheritage.org/diffusion/7" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "41", + "type": "RURI", + "phid": "PHID-RURI-dps3pzf6das2p2pczdao", + "fields": { + "repositoryPHID": "PHID-REPO-gkyta7fyiybtuqd4sg2x", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/swh-loader-debian.git", + "effective": "https://forge.softwareheritage.org/source/swh-loader-debian.git", + "normalized": "forge.softwareheritage.org/source/swh-loader-debian" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "40", + "type": "RURI", + "phid": "PHID-RURI-qk277eakpfsud7homi7x", + "fields": { + "repositoryPHID": "PHID-REPO-gkyta7fyiybtuqd4sg2x", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/DLDDEB/swh-loader-debian.git", + "effective": "https://forge.softwareheritage.org/diffusion/DLDDEB/swh-loader-debian.git", + "normalized": "forge.softwareheritage.org/diffusion/DLDDEB" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "39", + "type": "RURI", + "phid": "PHID-RURI-m2iwa2thft7t2bu6pvnu", + "fields": { + "repositoryPHID": "PHID-REPO-gkyta7fyiybtuqd4sg2x", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/7/swh-loader-debian.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/7/swh-loader-debian.git", + "normalized": "forge.softwareheritage.org/diffusion/7" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "38", + "type": "RURI", + "phid": "PHID-RURI-ibp7sepp5krmrz2zb7on", + "fields": { + "repositoryPHID": "PHID-REPO-gkyta7fyiybtuqd4sg2x", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/swh-loader-debian.git", + "effective": "ssh://git@forge.softwareheritage.org/source/swh-loader-debian.git", + "normalized": "forge.softwareheritage.org/source/swh-loader-debian" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "37", + "type": "RURI", + "phid": "PHID-RURI-xdda3g7vcxwny5styzga", + "fields": { + "repositoryPHID": "PHID-REPO-gkyta7fyiybtuqd4sg2x", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DLDDEB/swh-loader-debian.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DLDDEB/swh-loader-debian.git", + "normalized": "forge.softwareheritage.org/diffusion/DLDDEB" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 8, + "type": "REPO", + "phid": "PHID-REPO-6yxfwkp54snt37kjzv56", + "fields": { + "name": "Git loader", + "vcs": "git", + "callsign": "DLDG", + "shortName": "swh-loader-git", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877841, + "dateModified": 1547058608, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "users" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "810", + "type": "RURI", + "phid": "PHID-RURI-m757ey5cjtqoswnp4jfn", + "fields": { + "repositoryPHID": "PHID-REPO-6yxfwkp54snt37kjzv56", + "uri": { + "raw": "git@github.com:SoftwareHeritage/swh-loader-git.git", + "display": "git@github.com:SoftwareHeritage/swh-loader-git.git", + "effective": "git@github.com:SoftwareHeritage/swh-loader-git.git", + "normalized": "github.com/SoftwareHeritage/swh-loader-git" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1486571111", + "dateModified": "1486571111" + } + }, + { + "id": "51", + "type": "RURI", + "phid": "PHID-RURI-caomlqx42p6ndbd4dwwy", + "fields": { + "repositoryPHID": "PHID-REPO-6yxfwkp54snt37kjzv56", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/8/swh-loader-git.git", + "effective": "https://forge.softwareheritage.org/diffusion/8/swh-loader-git.git", + "normalized": "forge.softwareheritage.org/diffusion/8" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "50", + "type": "RURI", + "phid": "PHID-RURI-opinwxwlqq5w2nzy4c4z", + "fields": { + "repositoryPHID": "PHID-REPO-6yxfwkp54snt37kjzv56", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/swh-loader-git.git", + "effective": "https://forge.softwareheritage.org/source/swh-loader-git.git", + "normalized": "forge.softwareheritage.org/source/swh-loader-git" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "49", + "type": "RURI", + "phid": "PHID-RURI-nnlls52bb2nif7dtsvuc", + "fields": { + "repositoryPHID": "PHID-REPO-6yxfwkp54snt37kjzv56", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/DLDG/swh-loader-git.git", + "effective": "https://forge.softwareheritage.org/diffusion/DLDG/swh-loader-git.git", + "normalized": "forge.softwareheritage.org/diffusion/DLDG" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "48", + "type": "RURI", + "phid": "PHID-RURI-acwrxzvkjzd4ulsfz6er", + "fields": { + "repositoryPHID": "PHID-REPO-6yxfwkp54snt37kjzv56", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/8/swh-loader-git.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/8/swh-loader-git.git", + "normalized": "forge.softwareheritage.org/diffusion/8" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "47", + "type": "RURI", + "phid": "PHID-RURI-mwsdfswyruannmalcdgl", + "fields": { + "repositoryPHID": "PHID-REPO-6yxfwkp54snt37kjzv56", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/swh-loader-git.git", + "effective": "ssh://git@forge.softwareheritage.org/source/swh-loader-git.git", + "normalized": "forge.softwareheritage.org/source/swh-loader-git" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "46", + "type": "RURI", + "phid": "PHID-RURI-3mp7uo2nsawcb3b2uteq", + "fields": { + "repositoryPHID": "PHID-REPO-6yxfwkp54snt37kjzv56", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DLDG/swh-loader-git.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DLDG/swh-loader-git.git", + "normalized": "forge.softwareheritage.org/diffusion/DLDG" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 9, + "type": "REPO", + "phid": "PHID-REPO-b2cdo5agfixco2mqreuy", + "fields": { + "name": "Git loader - test data", + "vcs": "git", + "callsign": "DLDGT", + "shortName": "git-loader-test-data", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877841, + "dateModified": 1519745125, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "PHID-PROJ-zqzomfxtpkd5jgltioiy" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "811", + "type": "RURI", + "phid": "PHID-RURI-uhpi5u226sqfmwcpzpsa", + "fields": { + "repositoryPHID": "PHID-REPO-b2cdo5agfixco2mqreuy", + "uri": { + "raw": "git@github.com:SoftwareHeritage/git-loader-test-data.git", + "display": "git@github.com:SoftwareHeritage/git-loader-test-data.git", + "effective": "git@github.com:SoftwareHeritage/git-loader-test-data.git", + "normalized": "github.com/SoftwareHeritage/git-loader-test-data" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1486571113", + "dateModified": "1486571113" + } + }, + { + "id": "60", + "type": "RURI", + "phid": "PHID-RURI-76btknuireumkts2qqcn", + "fields": { + "repositoryPHID": "PHID-REPO-b2cdo5agfixco2mqreuy", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/9/git-loader-test-data.git", + "effective": "https://forge.softwareheritage.org/diffusion/9/git-loader-test-data.git", + "normalized": "forge.softwareheritage.org/diffusion/9" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "59", + "type": "RURI", + "phid": "PHID-RURI-qiudxih5pqk46btodq52", + "fields": { + "repositoryPHID": "PHID-REPO-b2cdo5agfixco2mqreuy", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/git-loader-test-data.git", + "effective": "https://forge.softwareheritage.org/source/git-loader-test-data.git", + "normalized": "forge.softwareheritage.org/source/git-loader-test-data" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "58", + "type": "RURI", + "phid": "PHID-RURI-ge4uegurx2y4f7bivvvs", + "fields": { + "repositoryPHID": "PHID-REPO-b2cdo5agfixco2mqreuy", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/DLDGT/git-loader-test-data.git", + "effective": "https://forge.softwareheritage.org/diffusion/DLDGT/git-loader-test-data.git", + "normalized": "forge.softwareheritage.org/diffusion/DLDGT" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "57", + "type": "RURI", + "phid": "PHID-RURI-hykqfusjkkosx46alejf", + "fields": { + "repositoryPHID": "PHID-REPO-b2cdo5agfixco2mqreuy", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/9/git-loader-test-data.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/9/git-loader-test-data.git", + "normalized": "forge.softwareheritage.org/diffusion/9" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "56", + "type": "RURI", + "phid": "PHID-RURI-smauekehnvde7rdhrvon", + "fields": { + "repositoryPHID": "PHID-REPO-b2cdo5agfixco2mqreuy", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/git-loader-test-data.git", + "effective": "ssh://git@forge.softwareheritage.org/source/git-loader-test-data.git", + "normalized": "forge.softwareheritage.org/source/git-loader-test-data" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "55", + "type": "RURI", + "phid": "PHID-RURI-ma6mpnw3e4hni6hcru4w", + "fields": { + "repositoryPHID": "PHID-REPO-b2cdo5agfixco2mqreuy", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DLDGT/git-loader-test-data.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DLDGT/git-loader-test-data.git", + "normalized": "forge.softwareheritage.org/diffusion/DLDGT" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 10, + "type": "REPO", + "phid": "PHID-REPO-4ocen5dkqx4i5pe34knp", + "fields": { + "name": "CGit lister", + "vcs": "git", + "callsign": "DLSCG", + "shortName": "swh-lister-cgit", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877841, + "dateModified": 1486571119, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "PHID-PROJ-zqzomfxtpkd5jgltioiy" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "813", + "type": "RURI", + "phid": "PHID-RURI-5voo6vvnvdd72k4anjob", + "fields": { + "repositoryPHID": "PHID-REPO-4ocen5dkqx4i5pe34knp", + "uri": { + "raw": "git@github.com:SoftwareHeritage/swh-lister-cgit.git", + "display": "git@github.com:SoftwareHeritage/swh-lister-cgit.git", + "effective": "git@github.com:SoftwareHeritage/swh-lister-cgit.git", + "normalized": "github.com/SoftwareHeritage/swh-lister-cgit" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1486571119", + "dateModified": "1486571119" + } + }, + { + "id": "69", + "type": "RURI", + "phid": "PHID-RURI-q6pr6zzemgn64iv5h4mz", + "fields": { + "repositoryPHID": "PHID-REPO-4ocen5dkqx4i5pe34knp", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/10/swh-lister-cgit.git", + "effective": "https://forge.softwareheritage.org/diffusion/10/swh-lister-cgit.git", + "normalized": "forge.softwareheritage.org/diffusion/10" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "68", + "type": "RURI", + "phid": "PHID-RURI-dchphkf7afnsz6o3d6s7", + "fields": { + "repositoryPHID": "PHID-REPO-4ocen5dkqx4i5pe34knp", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/swh-lister-cgit.git", + "effective": "https://forge.softwareheritage.org/source/swh-lister-cgit.git", + "normalized": "forge.softwareheritage.org/source/swh-lister-cgit" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "67", + "type": "RURI", + "phid": "PHID-RURI-oemnx7vueo7av5ergwhg", + "fields": { + "repositoryPHID": "PHID-REPO-4ocen5dkqx4i5pe34knp", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/DLSCG/swh-lister-cgit.git", + "effective": "https://forge.softwareheritage.org/diffusion/DLSCG/swh-lister-cgit.git", + "normalized": "forge.softwareheritage.org/diffusion/DLSCG" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "66", + "type": "RURI", + "phid": "PHID-RURI-ffucdrzfyqtwr2gpno37", + "fields": { + "repositoryPHID": "PHID-REPO-4ocen5dkqx4i5pe34knp", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/10/swh-lister-cgit.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/10/swh-lister-cgit.git", + "normalized": "forge.softwareheritage.org/diffusion/10" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "65", + "type": "RURI", + "phid": "PHID-RURI-vskwaaeuxrc2nanreaj3", + "fields": { + "repositoryPHID": "PHID-REPO-4ocen5dkqx4i5pe34knp", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/swh-lister-cgit.git", + "effective": "ssh://git@forge.softwareheritage.org/source/swh-lister-cgit.git", + "normalized": "forge.softwareheritage.org/source/swh-lister-cgit" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "64", + "type": "RURI", + "phid": "PHID-RURI-bl27gg5whneoxiytxmaj", + "fields": { + "repositoryPHID": "PHID-REPO-4ocen5dkqx4i5pe34knp", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DLSCG/swh-lister-cgit.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DLSCG/swh-lister-cgit.git", + "normalized": "forge.softwareheritage.org/diffusion/DLSCG" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 11, + "type": "REPO", + "phid": "PHID-REPO-lthllz32wb4x72kkdcs3", + "fields": { + "name": "Listers", + "vcs": "git", + "callsign": "DLS", + "shortName": "swh-lister", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877841, + "dateModified": 1547058690, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "users" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "846", + "type": "RURI", + "phid": "PHID-RURI-42rboz7opl2zz2hjvy6m", + "fields": { + "repositoryPHID": "PHID-REPO-lthllz32wb4x72kkdcs3", + "uri": { + "raw": "git@github.com:SoftwareHeritage/swh-lister.git", + "display": "git@github.com:SoftwareHeritage/swh-lister.git", + "effective": "git@github.com:SoftwareHeritage/swh-lister.git", + "normalized": "github.com/SoftwareHeritage/swh-lister" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1486652955", + "dateModified": "1492792215" + } + }, + { + "id": "845", + "type": "RURI", + "phid": "PHID-RURI-ezu77cg7xd47beaqklnh", + "fields": { + "repositoryPHID": "PHID-REPO-lthllz32wb4x72kkdcs3", + "uri": { + "raw": "git@github.com:SoftwareHeritage/swh-lister-github.git", + "display": "git@github.com:SoftwareHeritage/swh-lister-github.git", + "effective": "git@github.com:SoftwareHeritage/swh-lister-github.git", + "normalized": "github.com/SoftwareHeritage/swh-lister-github" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": true, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1486652889", + "dateModified": "1492792358" + } + }, + { + "id": "78", + "type": "RURI", + "phid": "PHID-RURI-b5uflskla4letdsvbnjv", + "fields": { + "repositoryPHID": "PHID-REPO-lthllz32wb4x72kkdcs3", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/11/swh-lister.git", + "effective": "https://forge.softwareheritage.org/diffusion/11/swh-lister.git", + "normalized": "forge.softwareheritage.org/diffusion/11" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "77", + "type": "RURI", + "phid": "PHID-RURI-tkpmxuj27b757c2gbnmb", + "fields": { + "repositoryPHID": "PHID-REPO-lthllz32wb4x72kkdcs3", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/swh-lister.git", + "effective": "https://forge.softwareheritage.org/source/swh-lister.git", + "normalized": "forge.softwareheritage.org/source/swh-lister" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "76", + "type": "RURI", + "phid": "PHID-RURI-f6dcl6mnnd7oi5rdxw26", + "fields": { + "repositoryPHID": "PHID-REPO-lthllz32wb4x72kkdcs3", + "uri": { + "raw": "https://callsign", + "display": "https://forge.softwareheritage.org/diffusion/DLS/swh-lister.git", + "effective": "https://forge.softwareheritage.org/diffusion/DLS/swh-lister.git", + "normalized": "forge.softwareheritage.org/diffusion/DLS" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "75", + "type": "RURI", + "phid": "PHID-RURI-dqcif6i64qt3yiyuztmf", + "fields": { + "repositoryPHID": "PHID-REPO-lthllz32wb4x72kkdcs3", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/11/swh-lister.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/11/swh-lister.git", + "normalized": "forge.softwareheritage.org/diffusion/11" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "74", + "type": "RURI", + "phid": "PHID-RURI-j63yjozb5zd3zqrmuv4q", + "fields": { + "repositoryPHID": "PHID-REPO-lthllz32wb4x72kkdcs3", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/swh-lister.git", + "effective": "ssh://git@forge.softwareheritage.org/source/swh-lister.git", + "normalized": "forge.softwareheritage.org/source/swh-lister" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "73", + "type": "RURI", + "phid": "PHID-RURI-dnad7c6pfowgxlfua6gy", + "fields": { + "repositoryPHID": "PHID-REPO-lthllz32wb4x72kkdcs3", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DLS/swh-lister.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DLS/swh-lister.git", + "normalized": "forge.softwareheritage.org/diffusion/DLS" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + }, + { + "id": 12, + "type": "REPO", + "phid": "PHID-REPO-kasovubyed5uxxpadvs3", + "fields": { + "name": "Storage manager", + "vcs": "git", + "callsign": "DSTO", + "shortName": "swh-storage", + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1441877842, + "dateModified": 1549623462, + "policy": { + "view": "public", + "edit": "PHID-PROJ-zqzomfxtpkd5jgltioiy", + "diffusion.push": "users" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "683", + "type": "RURI", + "phid": "PHID-RURI-4rjkye2jxnp7lgnqmsoa", + "fields": { + "repositoryPHID": "PHID-REPO-kasovubyed5uxxpadvs3", + "uri": { + "raw": "git@github.com:SoftwareHeritage/swh-storage.git", + "display": "git@github.com:SoftwareHeritage/swh-storage.git", + "effective": "git@github.com:SoftwareHeritage/swh-storage.git", + "normalized": "github.com/SoftwareHeritage/swh-storage" + }, + "io": { + "raw": "mirror", + "default": "none", + "effective": "mirror" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "PHID-CDTL-hwgnkw6vk2nrh475bvkf", + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1474643061", + "dateModified": "1519746814" + } + }, + { + "id": "87", + "type": "RURI", + "phid": "PHID-RURI-npie2vfeegszyxgmaee5", + "fields": { + "repositoryPHID": "PHID-REPO-kasovubyed5uxxpadvs3", + "uri": { + "raw": "https://id", + "display": "https://forge.softwareheritage.org/diffusion/12/swh-storage.git", + "effective": "https://forge.softwareheritage.org/diffusion/12/swh-storage.git", + "normalized": "forge.softwareheritage.org/diffusion/12" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "86", + "type": "RURI", + "phid": "PHID-RURI-7dxwck2ab6dedc7kkyay", + "fields": { + "repositoryPHID": "PHID-REPO-kasovubyed5uxxpadvs3", + "uri": { + "raw": "https://shortname", + "display": "https://forge.softwareheritage.org/source/swh-storage.git", + "effective": "https://forge.softwareheritage.org/source/swh-storage.git", + "normalized": "forge.softwareheritage.org/source/swh-storage" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "85", + "type": "RURI", + "phid": "PHID-RURI-kgiwcdlqfjlg6tbas4xq", + "fields": { + "repositoryPHID": "PHID-REPO-kasovubyed5uxxpadvs3", + "uri": { + "raw": "git@github.com:SoftwareHeritage/mirror-tryout.git", + "display": "https://forge.softwareheritage.org/diffusion/DSTO/swh-storage.git", + "effective": "https://forge.softwareheritage.org/diffusion/DSTO/swh-storage.git", + "normalized": "forge.softwareheritage.org/diffusion/DSTO" + }, + "io": { + "raw": "read", + "default": "readwrite", + "effective": "read" + }, + "display": { + "raw": "never", + "default": "never", + "effective": "never" + }, + "credentialPHID": "", + "disabled": false, + "builtin": { + "protocol": "https", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1485783470" + } + }, + { + "id": "84", + "type": "RURI", + "phid": "PHID-RURI-n5qnymw6y7os3quhonmt", + "fields": { + "repositoryPHID": "PHID-REPO-kasovubyed5uxxpadvs3", + "uri": { + "raw": "ssh://id", + "display": "ssh://git@forge.softwareheritage.org/diffusion/12/swh-storage.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/12/swh-storage.git", + "normalized": "forge.softwareheritage.org/diffusion/12" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "id" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "83", + "type": "RURI", + "phid": "PHID-RURI-nnaxsbmjekmw6lcd2qfz", + "fields": { + "repositoryPHID": "PHID-REPO-kasovubyed5uxxpadvs3", + "uri": { + "raw": "ssh://shortname", + "display": "ssh://git@forge.softwareheritage.org/source/swh-storage.git", + "effective": "ssh://git@forge.softwareheritage.org/source/swh-storage.git", + "normalized": "forge.softwareheritage.org/source/swh-storage" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "always", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "shortname" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + }, + { + "id": "82", + "type": "RURI", + "phid": "PHID-RURI-zgmgow3nmbn6nv2j7lzt", + "fields": { + "repositoryPHID": "PHID-REPO-kasovubyed5uxxpadvs3", + "uri": { + "raw": "ssh://callsign", + "display": "ssh://git@forge.softwareheritage.org/diffusion/DSTO/swh-storage.git", + "effective": "ssh://git@forge.softwareheritage.org/diffusion/DSTO/swh-storage.git", + "normalized": "forge.softwareheritage.org/diffusion/DSTO" + }, + "io": { + "raw": "default", + "default": "readwrite", + "effective": "readwrite" + }, + "display": { + "raw": "default", + "default": "never", + "effective": "never" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": "ssh", + "identifier": "callsign" + }, + "dateCreated": "1465923366", + "dateModified": "1465923366" + } + } + ] + } + } + } + ], + "maps": {}, + "query": { + "queryKey": null + }, + "cursor": { + "limit": 10, + "after": "12", + "before": null, + "order": "oldest" + } + }, + "error_code": null, + "error_info": null +} \ No newline at end of file diff --git a/swh/lister/phabricator/tests/api_response_undefined_protocol.json b/swh/lister/phabricator/tests/api_response_undefined_protocol.json new file mode 100644 index 0000000..df7ade7 --- /dev/null +++ b/swh/lister/phabricator/tests/api_response_undefined_protocol.json @@ -0,0 +1,60 @@ +{ + "id": 8, + "type": "REPO", + "phid": "PHID-REPO-ge2icigfu5ijk2whqfbl", + "fields": { + "name": "Blender Libraries", + "vcs": "svn", + "callsign": "BL", + "shortName": null, + "status": "active", + "isImporting": false, + "almanacServicePHID": null, + "spacePHID": null, + "dateCreated": 1385564674, + "dateModified": 1468574079, + "policy": { + "view": "public", + "edit": "admin", + "diffusion.push": "PHID-PROJ-hclk7tvd6pmpjmqastjl" + } + }, + "attachments": { + "uris": { + "uris": [ + { + "id": "70", + "type": "RURI", + "phid": "PHID-RURI-h7zdbkud6why4xrb2s2e", + "fields": { + "repositoryPHID": "PHID-REPO-ge2icigfu5ijk2whqfbl", + "uri": { + "raw": "https://svn.blender.org/svnroot/bf-blender/", + "display": "https://svn.blender.org/svnroot/bf-blender/", + "effective": "https://svn.blender.org/svnroot/bf-blender/", + "normalized": "svn.blender.org/svnroot/bf-blender" + }, + "io": { + "raw": "observe", + "default": "none", + "effective": "observe" + }, + "display": { + "raw": "always", + "default": "never", + "effective": "always" + }, + "credentialPHID": null, + "disabled": false, + "builtin": { + "protocol": null, + "identifier": null + }, + "dateCreated": "1467894515", + "dateModified": "1468574079" + } + } + ] + } + } +} \ No newline at end of file diff --git a/swh/lister/phabricator/tests/conftest.py b/swh/lister/phabricator/tests/conftest.py new file mode 100644 index 0000000..507fef9 --- /dev/null +++ b/swh/lister/phabricator/tests/conftest.py @@ -0,0 +1 @@ +from swh.lister.core.tests.conftest import * # noqa diff --git a/swh/lister/phabricator/tests/test_lister.py b/swh/lister/phabricator/tests/test_lister.py new file mode 100644 index 0000000..29fbcec --- /dev/null +++ b/swh/lister/phabricator/tests/test_lister.py @@ -0,0 +1,53 @@ +# Copyright (C) 2019 the Software Heritage developers +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import re +import json +import unittest +from swh.lister.core.tests.test_lister import HttpListerTester +from swh.lister.phabricator.lister import PhabricatorLister +from swh.lister.phabricator.lister import get_repo_url + + +class PhabricatorListerTester(HttpListerTester, unittest.TestCase): + Lister = PhabricatorLister + test_re = re.compile(r'\&after=([^?&]+)') + lister_subdir = 'phabricator' + good_api_response_file = 'api_response.json' + good_api_response_undefined_protocol = 'api_response_undefined_'\ + 'protocol.json' + bad_api_response_file = 'api_empty_response.json' + first_index = 1 + last_index = 12 + entries_per_page = 10 + + def get_fl(self, override_config=None): + """(Override) Retrieve an instance of fake lister (fl). + """ + if override_config or self.fl is None: + self.fl = self.Lister(forge_url='https://fakeurl', api_token='a-1', + override_config=override_config) + self.fl.INITIAL_BACKOFF = 1 + + self.fl.reset_backoff() + return self.fl + + def test_get_repo_url(self): + f = open('swh/lister/%s/tests/%s' % (self.lister_subdir, + self.good_api_response_file)) + api_response = json.load(f) + repos = api_response['result']['data'] + for repo in repos: + self.assertEqual( + 'https://forge.softwareheritage.org/source/%s.git' % + (repo['fields']['shortName']), + get_repo_url(repo['attachments']['uris']['uris'])) + + f = open('swh/lister/%s/tests/%s' % + (self.lister_subdir, + self.good_api_response_undefined_protocol)) + repo = json.load(f) + self.assertEqual( + 'https://svn.blender.org/svnroot/bf-blender/', + get_repo_url(repo['attachments']['uris']['uris'])) diff --git a/swh/lister/phabricator/tests/test_tasks.py b/swh/lister/phabricator/tests/test_tasks.py new file mode 100644 index 0000000..160efcc --- /dev/null +++ b/swh/lister/phabricator/tests/test_tasks.py @@ -0,0 +1,29 @@ +from unittest.mock import patch + + +def test_ping(swh_app, celery_session_worker): + res = swh_app.send_task( + 'swh.lister.phabricator.tasks.ping') + assert res + res.wait() + assert res.successful() + assert res.result == 'OK' + + +@patch('swh.lister.phabricator.tasks.PhabricatorLister') +def test_incremental(lister, swh_app, celery_session_worker): + # setup the mocked PhabricatorLister + lister.return_value = lister + lister.db_last_index.return_value = 42 + lister.run.return_value = None + + res = swh_app.send_task( + 'swh.lister.phabricator.tasks.IncrementalPhabricatorLister') + assert res + res.wait() + assert res.successful() + + lister.assert_called_once_with( + api_token='', forge_url='https://forge.softwareheritage.org') + lister.db_last_index.assert_called_once_with() + lister.run.assert_called_once_with(min_bound=42) diff --git a/swh/lister/pypi/lister.py b/swh/lister/pypi/lister.py index caa8089..a488850 100644 --- a/swh/lister/pypi/lister.py +++ b/swh/lister/pypi/lister.py @@ -1,76 +1,76 @@ # Copyright (C) 2018 the Software Heritage developers # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import random import xmltodict from .models import PyPIModel from swh.scheduler import utils from swh.lister.core.simple_lister import SimpleLister from swh.lister.core.lister_transports import ListerOnePageApiTransport class PyPILister(ListerOnePageApiTransport, SimpleLister): MODEL = PyPIModel LISTER_NAME = 'pypi' PAGE = 'https://pypi.org/simple/' def __init__(self, override_config=None): ListerOnePageApiTransport .__init__(self) SimpleLister.__init__(self, override_config=override_config) def task_dict(self, origin_type, origin_url, **kwargs): """(Override) Return task format dict This is overridden from the lister_base as more information is needed for the ingestion task creation. """ - _type = 'origin-update-%s' % origin_type + _type = 'load-%s' % origin_type _policy = 'recurring' project_name = kwargs.get('name') project_metadata_url = kwargs.get('html_url') return utils.create_task_dict( _type, _policy, project_name, origin_url, project_metadata_url=project_metadata_url) def list_packages(self, response): """(Override) List the actual pypi origins from the response. """ result = xmltodict.parse(response.content) _packages = [p['#text'] for p in result['html']['body']['a']] random.shuffle(_packages) return _packages def _compute_urls(self, repo_name): """Returns a tuple (project_url, project_metadata_url) """ return ( 'https://pypi.org/project/%s/' % repo_name, 'https://pypi.org/pypi/%s/json' % repo_name ) def get_model_from_repo(self, repo_name): """(Override) Transform from repository representation to model """ project_url, project_url_meta = self._compute_urls(repo_name) return { 'uid': repo_name, 'name': repo_name, 'full_name': repo_name, 'html_url': project_url_meta, 'origin_url': project_url, 'origin_type': 'pypi', 'description': None, } def transport_response_simplified(self, response): """(Override) Transform response to list for model manipulation """ return [self.get_model_from_repo(repo_name) for repo_name in response] diff --git a/version.txt b/version.txt index d63db74..7ae1137 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v0.0.21-0-g0b8d1d4 \ No newline at end of file +v0.0.22-0-gd6169c7 \ No newline at end of file