{
    "file_diffs": [
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"High-level interface for creating a publication target on a GitLab instance\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\n\nimport logging\nimport warnings\n\nfrom datalad.support.exceptions import CapturedException\n\nfrom ..distribution.dataset import (\n    EnsureDataset,\n    datasetmethod,\n    require_dataset,\n    resolve_path,\n)\n# bound methods\nfrom ..distribution.siblings import Siblings\nfrom ..dochelpers import exc_str\nfrom ..interface.base import (\n    Interface,\n    build_doc,\n    eval_results,\n)\nfrom ..interface.common_opts import (\n    publish_depends,\n    recursion_flag,\n    recursion_limit,\n)\nfrom ..local.subdatasets import Subdatasets\nfrom ..support.constraints import (\n    EnsureChoice,\n    EnsureNone,\n    EnsureStr,\n)\nfrom ..support.param import Parameter\nfrom ..utils import ensure_list\n\nlgr = logging.getLogger('datalad.distributed.create_sibling_gitlab')\n\nknown_layout_labels = ('hierarchy', 'collection', 'flat')\nknown_access_labels = ('http', 'ssh', 'ssh+http')\n\n\n@build_doc\nclass CreateSiblingGitlab(Interface):\n    \"\"\"Create dataset sibling at a GitLab site\n\n    An existing GitLab project, or a project created via the GitLab web\n    interface can be configured as a sibling with the :command:`siblings`\n    command. Alternatively, this command can create a GitLab project at any\n    location/path a given user has appropriate permissions for. This is\n    particularly helpful for recursive sibling creation for subdatasets. API\n    access and authentication are implemented via python-gitlab, and all its\n    features are supported. A particular GitLab site must be configured in a\n    named section of a python-gitlab.cfg file (see\n    https://python-gitlab.readthedocs.io/en/stable/cli.html#configuration for\n    details), such as::\n\n      [mygit]\n      url = https://git.example.com\n      api_version = 4\n      private_token = abcdefghijklmnopqrst\n\n    Subsequently, this site is identified by its name ('mygit' in the example\n    above).\n\n    (Recursive) sibling creation for all, or a selected subset of subdatasets\n    is supported with three different project layouts (see --layout):\n\n    \"hierarchy\"\n      Each dataset is placed into its own group, and the actual GitLab\n      project for a dataset is put in a project named \"project\" inside\n      this group. This project name is configurable (see Configuration).\n      Using this layout, arbitrarily deep hierarchies of\n      nested datasets can be represented, while the hierarchical structure\n      is reflected in the project path. This is the default layout, if\n      no project path is specified.\n    \"flat\"\n      All datasets are placed in the same group. The name of a project\n      is its relative path within the root dataset, with all path separator\n      characters replaced by '-'. This path separator is configurable\n      (see Configuration).\n    \"collection\"\n      This is a hybrid layout, where the root dataset is placed in a \"project\"\n      project inside a group, and all nested subdatasets are represented\n      inside the group using a \"flat\" layout. The project name is configurable\n      (see Configuration).\n\n    GitLab cannot host dataset content. However, in combination with\n    other data sources (and siblings), publishing a dataset to GitLab can\n    facilitate distribution and exchange, while still allowing any dataset\n    consumer to obtain actual data content from alternative sources.\n\n    *Configuration*\n\n    Many configuration switches and options for GitLab sibling creation can\n    be provided arguments to the command. However, it is also possible to\n    specify a particular setup in a dataset's configuration. This is\n    particularly important when managing large collections of datasets.\n    Configuration options are:\n\n    \"datalad.gitlab-default-site\"\n        Name of the default GitLab site (see --site)\n    \"datalad.gitlab-SITENAME-siblingname\"\n        Name of the sibling configured for the local dataset that points\n        to the GitLab instance SITENAME (see --name)\n    \"datalad.gitlab-SITENAME-layout\"\n        Project layout used at the GitLab instance SITENAME (see --layout)\n    \"datalad.gitlab-SITENAME-access\"\n        Access method used for the GitLab instance SITENAME (see --access)\n    \"datalad.gitlab-SITENAME-project\"\n        Project location/path used for a datasets at GitLab instance\n        SITENAME (see --project). Configuring this is useful for deriving\n        project paths for subdatasets, relative to superdataset.\n    \"datalad.gitlab-default-projectname\"\n        The hierarchy and collection layouts publish (sub)datasets as projects\n        with a custom name. The default name \"project\" can be overridden with\n        this configuration.\n    \"datalad.gitlab-default-pathseparator\"\n        The flat and collection layout represent subdatasets with project names\n        that correspond to the path, with the regular path separator replaced\n        with a \"-\": superdataset-subdataset. This configuration can override\n        this default separator.\n\n    This command can be configured with\n    \"datalad.create-sibling-ghlike.extra-remote-settings.NETLOC.KEY=VALUE\" in\n    order to add any local KEY = VALUE configuration to the created sibling in\n    the local `.git/config` file. NETLOC is the domain of the Gitlab instance to\n    apply the configuration for.\n    This leads to a behavior that is equivalent to calling datalad's\n    ``siblings('configure', ...)``||``siblings configure`` command with the\n    respective KEY-VALUE pair after creating the sibling.\n    The configuration, like any other, could be set at user- or system level, so\n    users do not need to add this configuration to every sibling created with\n    the service at NETLOC themselves.\n\n    \"\"\"\n    _params_ = dict(\n        path=Parameter(\n            args=('path',),\n            metavar='PATH',\n            nargs='*',\n            doc=\"\"\"selectively create siblings for any datasets underneath a given\n            path. By default only the root dataset is considered.\"\"\"),\n        dataset=Parameter(\n            args=(\"--dataset\", \"-d\",),\n            doc=\"\"\"reference or root dataset. If no path constraints are given,\n            a sibling for this dataset will be created. In this and all other\n            cases, the reference dataset is also consulted for the GitLab\n            configuration, and desired project layout. If no dataset is given,\n            an attempt is made to identify the dataset based on the current\n            working directory\"\"\",\n            constraints=EnsureDataset() | EnsureNone()),\n        site=Parameter(\n            args=('--site',),\n            metavar='SITENAME',\n            doc=\"\"\"name of the GitLab site to create a sibling at. Must match an\n            existing python-gitlab configuration section with location and\n            authentication settings (see\n            https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#configuration).\n            By default the dataset configuration is consulted.\n            \"\"\",\n            constraints=EnsureNone() | EnsureStr()),\n        project=Parameter(\n            args=('--project',),\n            metavar='NAME/LOCATION',\n            doc=\"\"\"project name/location at the GitLab site. If a subdataset of the\n            reference dataset is processed, its project path is automatically\n            determined by the `layout` configuration, by default.\n            \"\"\",\n            constraints=EnsureNone() | EnsureStr()),\n        layout=Parameter(\n            args=('--layout',),\n            constraints=EnsureChoice(None, *known_layout_labels),\n            doc=\"\"\"layout of projects at the GitLab site, if a collection, or\n            a hierarchy of datasets and subdatasets is to be created.\n            By default the dataset configuration is consulted.\n            \"\"\"),\n        recursive=recursion_flag,\n        recursion_limit=recursion_limit,\n        name=Parameter(\n            args=('-s', '--name',),\n            metavar='NAME',\n            doc=\"\"\"name to represent the GitLab sibling remote in the local\n            dataset installation. If not specified a name is looked up in the\n            dataset configuration, or defaults to the `site` name\"\"\",\n            constraints=EnsureStr() | EnsureNone()),\n        existing=Parameter(\n            args=(\"--existing\",),\n            constraints=EnsureChoice('skip', 'error', 'reconfigure'),\n            doc=\"\"\"desired behavior when already existing or configured\n            siblings are discovered. 'skip': ignore; 'error': fail, if access\n            URLs differ; 'reconfigure': use the existing repository and\n            reconfigure the local dataset to use it as a sibling\"\"\",),\n        access=Parameter(\n            args=(\"--access\",),\n            constraints=EnsureChoice(None, *known_access_labels),\n            doc=\"\"\"access method used for data transfer to and from the sibling.\n            'ssh': read and write access used the SSH protocol; 'http': read and\n            write access use HTTP requests; 'ssh+http': read access is done via\n            HTTP and write access performed with SSH. Dataset configuration is\n            consulted for a default, 'http' is used otherwise.\"\"\",),\n        description=Parameter(\n            args=(\"--description\",),\n            doc=\"\"\"brief description for the GitLab project (displayed on the\n            site)\"\"\",\n            constraints=EnsureStr() | EnsureNone()),\n        publish_depends=publish_depends,\n        dry_run=Parameter(\n            args=(\"--dry-run\",),\n            action=\"store_true\",\n            doc=\"\"\"if set, no repository will be created, only tests for\n            name collisions will be performed, and would-be repository names\n            are reported for all relevant datasets\"\"\"),\n        dryrun=Parameter(\n            args=(\"--dryrun\",),\n            action=\"store_true\",\n            doc=\"\"\"Deprecated. Use the renamed\n            ``dry_run||--dry-run`` parameter\"\"\")\n    )\n\n    @staticmethod\n    @datasetmethod(name='create_sibling_gitlab')\n    @eval_results\n    def __call__(\n            path=None,\n            *,\n            site=None,\n            project=None,\n            layout=None,\n            dataset=None,\n            recursive=False,\n            recursion_limit=None,\n            name=None,\n            existing='error',\n            access=None,\n            publish_depends=None,\n            description=None,\n            dryrun=False,\n            dry_run=False):\n        if dryrun and not dry_run:\n            # the old one is used, and not in agreement with the new one\n            warnings.warn(\n                \"datalad-create-sibling-github's `dryrun` option is \"\n                \"deprecated and will be removed in a future release, \"\n                \"use the renamed `dry_run/--dry-run` option instead.\",\n                DeprecationWarning)\n            dry_run = dryrun\n        path = resolve_path(ensure_list(path), ds=dataset) \\\n            if path else None\n\n        if project and (recursive or (path and len(path) > 1)):\n            raise ValueError(\n                'Providing a GitLab project name/location cannot be combined '\n                'with recursive operation or multiple paths, as each dataset '\n                'needs to be mapped onto its own individual project.')\n        # what to operate on\n        ds = require_dataset(\n            dataset, check_installed=True, purpose='create GitLab sibling(s)')\n\n        # cache for objects of gitlab sites (we could face different ones\n        # in a single hierarchy, cache them to avoid duplicate initialization\n        # while still being able to process each dataset individually\n        siteobjs = dict()\n\n        # which datasets to process?\n        if path is None or ds.pathobj in path:\n            for r in _proc_dataset(\n                    ds, ds,\n                    site, project, name, layout, existing, access,\n                    dry_run, siteobjs, publish_depends, description):\n                yield r\n        # we need to find a subdataset when recursing, or when there is a path that\n        # could point to one, we have to exclude the parent dataset in this test\n        # to avoid undesired level-1 recursion into subdatasets\n        if any(p != ds.pathobj for p in (path or [])) or recursive:\n            # also include any matching subdatasets\n            subds = ds.subdatasets(\n                    path=path,\n                    # we can only operate on present datasets\n                    state='present',\n                    recursive=recursive,\n                    recursion_limit=recursion_limit,\n                    contains=None,\n                    bottomup=False,\n                    result_xfm='datasets',\n                    result_renderer='disabled',\n                    return_type='list')\n            if not subds:\n                # we didn't find anything to operate on, let the user know\n                for p in path:\n                    yield dict(\n                          action='create_sibling_gitlab',\n                          status='impossible',\n                          refds=ds.path,\n                          path=p,\n                          message=('No dataset found under %s' % p),\n                          type='dataset',\n                          logger=lgr,\n                    )\n            else:\n                for sub in subds:\n                    for r in _proc_dataset(\n                            ds, sub,\n                            site, project, name, layout, existing, access,\n                            dry_run, siteobjs, publish_depends, description):\n                        yield r\n\n        return\n\n\ndef _proc_dataset(refds, ds, site, project, remotename, layout, existing,\n                  access, dry_run, siteobjs, depends, description):\n    # basic result setup\n    res_kwargs = dict(\n        action='create_sibling_gitlab',\n        refds=refds.path,\n        path=ds.path,\n        type='dataset',\n        logger=lgr,\n    )\n    if description:\n        res_kwargs['description'] = description\n\n    if site is None:\n        # always try pulling the base config from a parent dataset\n        # even if paths were given (may be overwritten later)\n        basecfgsite = ds.config.get('datalad.gitlab-default-site', None)\n\n    # let the dataset config overwrite the target site, if none\n    # was given\n    site = refds.config.get(\n        'datalad.gitlab-default-site', basecfgsite) \\\n        if site is None else site\n    if site is None:\n        # this means the most top-level dataset has no idea about\n        # gitlab, and no site was specified as an argument\n        # fail rather then give an error result, as this is very\n        # unlikely to be intentional\n        raise ValueError(\n            'No GitLab site was specified (--site) or configured '\n            'in {} (datalad.gitlab.default-site)'.format(ds))\n    res_kwargs['site'] = site\n\n    # determine target remote name, unless given\n    if remotename is None:\n        remotename_var = 'datalad.gitlab-{}-siblingname'.format(site)\n        remotename = ds.config.get(\n            remotename_var,\n            # use config from parent, if needed\n            refds.config.get(\n                remotename_var,\n                # fall back on site name, if nothing else can be used\n                site))\n    res_kwargs['sibling'] = remotename\n    # check against existing remotes\n    dremotes = {\n        r['name']: r\n        for r in ds.siblings(\n            action='query',\n            # fastest possible\n            get_annex_info=False,\n            recursive=False,\n            return_type='generator',\n            result_renderer='disabled')\n    }\n    if remotename in dremotes and existing not in ['replace', 'reconfigure']:\n        # we already know a sibling with this name\n        yield dict(\n            res_kwargs,\n            status='error' if existing == 'error' else 'notneeded',\n            message=('already has a configured sibling \"%s\"', remotename),\n        )\n        return\n\n    if layout is None:\n        # figure out the layout of projects on the site\n        # use the reference dataset as default, and fall back\n        # on 'hierarchy' as the most generic method of representing\n        # the filesystem in a group/subgroup structure\n        layout_var = 'datalad.gitlab-{}-layout'.format(site)\n        layout = ds.config.get(\n            layout_var, refds.config.get(\n                layout_var, 'hierarchy'))\n    if layout not in known_layout_labels:\n        raise ValueError(\n            \"Unknown site layout '{}' given or configured, \"\n            \"known ones are: {}\".format(layout, known_layout_labels))\n\n    if access is None:\n        access_var = 'datalad.gitlab-{}-access'.format(site)\n        access = ds.config.get(\n            access_var, refds.config.get(\n                access_var, 'http'))\n    if access not in known_access_labels:\n        raise ValueError(\n            \"Unknown site access '{}' given or configured, \"\n            \"known ones are: {}\".format(access, known_access_labels))\n\n    pathsep = ds.config.get(\"datalad.gitlab-default-pathseparator\", None) or \"-\"\n    project_stub = \\\n        ds.config.get(\"datalad.gitlab-default-projectname\", None) or \"project\"\n    project_var = 'datalad.gitlab-{}-project'.format(site)\n    process_root = refds == ds\n    if project is None:\n        # look for a specific config in the dataset\n        project = ds.config.get(project_var, None)\n\n    if project and process_root and layout == 'collection':\n        # the root of a collection\n        project = f'{project}/{project_stub}'\n    elif project is None and not process_root:\n        # check if we can build one from the refds config\n        ref_project = refds.config.get(project_var, None)\n        if ref_project:\n            # layout-specific derivation of a path from\n            # the reference dataset configuration\n            rproject = ds.pathobj.relative_to(refds.pathobj).as_posix()\n            if layout == 'hierarchy':\n                project = f'{ref_project}/{rproject}/{project_stub}'\n            elif layout == 'collection':\n                project = '{}/{}'.format(\n                    ref_project,\n                    rproject.replace('/', pathsep))\n            else:\n                project = '{}-{}'.format(\n                    ref_project,\n                    rproject.replace('/', pathsep))\n\n    if project is None:\n        yield dict(\n            res_kwargs,\n            status='error',\n            message='No project name/location specified, and no configuration '\n                    'to derive one',\n        )\n        return\n\n    res_kwargs['project'] = project\n\n    if dry_run:\n        # this is as far as we can get without talking to GitLab\n        yield dict(\n            res_kwargs,\n            status='ok',\n            dryrun=True,\n        )\n        return\n\n    # and now talk to GitLab for real\n    site_api = siteobjs[site] if site in siteobjs else GitLabSite(site)\n\n    site_project = site_api.get_project(project)\n    if site_project is None:\n        try:\n            site_project = site_api.create_project(project, description)\n            # report success\n            message = \"sibling repository '%s' created at %s\",\\\n                      remotename, site_project.get('web_url', None)\n            yield dict(\n                res_kwargs,\n                # relay all attributes\n                project_attributes=site_project,\n                message=message,\n                status='ok',\n            )\n        except Exception as e:\n            ce = CapturedException(e)\n            yield dict(\n                res_kwargs,\n                # relay all attributes\n                status='error',\n                message=('Failed to create GitLab project: %s', ce),\n                exception=ce\n            )\n            return\n    else:\n        # there already is a project\n        if existing == 'error':\n            # be nice and only actually error if there is a real mismatch\n            if remotename not in dremotes:\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but no sibling with name '%s' is configured, \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site, remotename,\n                    )\n                )\n                return\n            elif access in ('ssh', 'ssh+http') \\\n                    and dremotes[remotename].get(\n                        'url', None) != site_project.get(\n                            # use False as a default so that there is a\n                            # mismatch, complain if both are missing\n                            'ssh_url_to_repo', False):\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but SSH access URL '%s' does not match '%s', \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site,\n                        dremotes[remotename].get('url', None),\n                        site_project.get('ssh_url_to_repo', None)\n                    )\n                )\n                return\n            elif access == 'http' \\\n                    and dremotes[remotename].get(\n                        'url', None) != site_project.get(\n                            # use False as a default so that there is a\n                            # mismatch, veen if both are missing\n                            'http_url_to_repo', False):\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but HTTP access URL '%s' does not match '%s', \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site,\n                        dremotes[remotename].get('url', None),\n                        site_project.get('http_url_to_repo', None)\n                    )\n                )\n                return\n        yield dict(\n            res_kwargs,\n            project_attributes=site_project,\n            status='notneeded',\n            message=(\n                \"There is already a project at '%s' on site '%s'\",\n                project, site,\n            )\n        )\n\n    # first make sure that annex doesn't touch this one\n    # but respect any existing config\n    ignore_var = 'remote.{}.annex-ignore'.format(remotename)\n    if ignore_var not in ds.config:\n        ds.config.add(ignore_var, 'true', scope='local')\n\n    for res in ds.siblings(\n            'configure',\n            name=remotename,\n            url=site_project['http_url_to_repo']\n            if access in ('http', 'ssh+http')\n            else site_project['ssh_url_to_repo'],\n            pushurl=site_project['ssh_url_to_repo']\n            if access in ('ssh', 'ssh+http')\n            else None,\n            recursive=False,\n            publish_depends=depends,\n            result_renderer='disabled',\n            return_type='generator'):\n        yield res\n\n\nclass GitLabSite(object):\n    def __init__(self, site):\n        import gitlab\n        self.gitlab = gitlab\n        try:\n            self.site = gitlab.Gitlab.from_config(site)\n        except gitlab.config.GitlabDataError as e:\n            raise ValueError(\n                '{}, please configure access to this GitLab instance'.format(\n                    str(e)))\n\n    def get_project(self, path):\n        try:\n            return self.site.projects.get(path).attributes\n        except self.gitlab.GitlabGetError as e:\n            lgr.debug(\"Project with path '%s' does not yet exist at site '%s'\",\n                      path, self.site.url)\n            return None\n\n    def create_project(self, path, description=None):\n        path_l = path.split('/')\n        namespace_id = self._obtain_namespace(path_l)\n        # check for options:\n        # https://gitlab.com/help/api/projects.md#create-project\n        props = dict(\n            name=path_l[-1],\n            namespace_id=namespace_id,\n        )\n        if description:\n            props['description'] = description\n        project = self.site.projects.create(props)\n        return project.attributes\n\n    def _obtain_namespace(self, path_l):\n\n        if len(path_l) == 1:\n            # no nesting whatsoever\n            return None\n\n        try:\n            namespace_id = self.site.groups.get(\n                '/'.join(path_l[:-1])).get_id()\n            lgr.debug(\"Found existing parent group '%s' with ID %s\",\n                      '/'.join(path_l[:-1]), namespace_id)\n        except self.gitlab.GitlabGetError as e:\n            try:\n                if len(path_l) > 2:\n                    parent_group = self.site.groups.get(\n                        '/'.join(path_l[:-2]))\n                else:\n                    parent_group = None\n            except self.gitlab.GitlabGetError as e:\n                raise ValueError(\n                    \"No parent group {} for project {} found, \"\n                    \"and a group {} also does not exist. At most one \"\n                    \"parent group would be created.\".format(\n                        '/'.join(path_l[:-1]),\n                        '/'.join(path_l),\n                        '/'.join(path_l[:-2]),\n                    ))\n            # create the group for the target project\n            try:\n                # prevent failure due to specification of a users personal\n                # group, always exists, cannot and must not be created\n                self.site.auth()\n                if len(path_l) == 2 \\\n                        and path_l[0] == self.site.user.attributes.get(\n                            'username', None):\n                    # attempt to create a personal project in the users\n                    # top-level personal group-- this is the same as\n                    # having no parent namespace, don't attempt to\n                    # create the group\n                    return None\n                namespace_id = self.site.groups.create(dict(\n                    name=path_l[-2],\n                    path=path_l[-2],\n                    parent_id=parent_group.get_id() if parent_group else None)\n                ).get_id()\n            except self.gitlab.GitlabCreateError as e:\n                raise RuntimeError(\n                    \"Failed to create parent group '{}' under {}: {}\".format(\n                        path_l[-2],\n                        repr(parent_group.attributes['full_path'])\n                        if parent_group else 'the account root',\n                        str(e)),\n                )\n        return namespace_id\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"High-level interface for creating a publication target on a GitLab instance\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\n\nimport logging\nimport warnings\n\nfrom datalad.support.exceptions import CapturedException\n\nfrom ..distribution.dataset import (\n    EnsureDataset,\n    datasetmethod,\n    require_dataset,\n    resolve_path,\n)\n# bound methods\nfrom ..distribution.siblings import Siblings\nfrom ..dochelpers import exc_str\nfrom ..interface.base import (\n    Interface,\n    build_doc,\n    eval_results,\n)\nfrom ..interface.common_opts import (\n    publish_depends,\n    recursion_flag,\n    recursion_limit,\n)\nfrom ..local.subdatasets import Subdatasets\nfrom ..support.constraints import (\n    EnsureChoice,\n    EnsureNone,\n    EnsureStr,\n)\nfrom ..support.param import Parameter\nfrom ..utils import ensure_list\n\nlgr = logging.getLogger('datalad.distributed.create_sibling_gitlab')\n\nknown_layout_labels = ('hierarchy', 'collection', 'flat')\nknown_access_labels = ('http', 'ssh', 'ssh+http')\n\n\n@build_doc\nclass CreateSiblingGitlab(Interface):\n    \"\"\"Create dataset sibling at a GitLab site\n\n    An existing GitLab project, or a project created via the GitLab web\n    interface can be configured as a sibling with the :command:`siblings`\n    command. Alternatively, this command can create a GitLab project at any\n    location/path a given user has appropriate permissions for. This is\n    particularly helpful for recursive sibling creation for subdatasets. API\n    access and authentication are implemented via python-gitlab, and all its\n    features are supported. A particular GitLab site must be configured in a\n    named section of a python-gitlab.cfg file (see\n    https://python-gitlab.readthedocs.io/en/stable/cli.html#configuration for\n    details), such as::\n\n      [mygit]\n      url = https://git.example.com\n      api_version = 4\n      private_token = abcdefghijklmnopqrst\n\n    Subsequently, this site is identified by its name ('mygit' in the example\n    above).\n\n    (Recursive) sibling creation for all, or a selected subset of subdatasets\n    is supported with three different project layouts (see --layout):\n\n    \"hierarchy\"\n      Each dataset is placed into its own group, and the actual GitLab\n      project for a dataset is put in a project named \"project\" inside\n      this group. This project name is configurable (see Configuration).\n      Using this layout, arbitrarily deep hierarchies of\n      nested datasets can be represented, while the hierarchical structure\n      is reflected in the project path. This is the default layout, if\n      no project path is specified.\n    \"flat\"\n      All datasets are placed in the same group. The name of a project\n      is its relative path within the root dataset, with all path separator\n      characters replaced by '-'. This path separator is configurable\n      (see Configuration).\n    \"collection\"\n      This is a hybrid layout, where the root dataset is placed in a \"project\"\n      project inside a group, and all nested subdatasets are represented\n      inside the group using a \"flat\" layout. The project name is configurable\n      (see Configuration).\n\n    GitLab cannot host dataset content. However, in combination with\n    other data sources (and siblings), publishing a dataset to GitLab can\n    facilitate distribution and exchange, while still allowing any dataset\n    consumer to obtain actual data content from alternative sources.\n\n    *Configuration*\n\n    Many configuration switches and options for GitLab sibling creation can\n    be provided arguments to the command. However, it is also possible to\n    specify a particular setup in a dataset's configuration. This is\n    particularly important when managing large collections of datasets.\n    Configuration options are:\n\n    \"datalad.gitlab-default-site\"\n        Name of the default GitLab site (see --site)\n    \"datalad.gitlab-SITENAME-siblingname\"\n        Name of the sibling configured for the local dataset that points\n        to the GitLab instance SITENAME (see --name)\n    \"datalad.gitlab-SITENAME-layout\"\n        Project layout used at the GitLab instance SITENAME (see --layout)\n    \"datalad.gitlab-SITENAME-access\"\n        Access method used for the GitLab instance SITENAME (see --access)\n    \"datalad.gitlab-SITENAME-project\"\n        Project location/path used for a datasets at GitLab instance\n        SITENAME (see --project). Configuring this is useful for deriving\n        project paths for subdatasets, relative to superdataset.\n    \"datalad.gitlab-default-projectname\"\n        The hierarchy and collection layouts publish (sub)datasets as projects\n        with a custom name. The default name \"project\" can be overridden with\n        this configuration.\n    \"datalad.gitlab-default-pathseparator\"\n        The flat and collection layout represent subdatasets with project names\n        that correspond to the path, with the regular path separator replaced\n        with a \"-\": superdataset-subdataset. This configuration can override\n        this default separator.\n\n    This command can be configured with\n    \"datalad.create-sibling-ghlike.extra-remote-settings.NETLOC.KEY=VALUE\" in\n    order to add any local KEY = VALUE configuration to the created sibling in\n    the local `.git/config` file. NETLOC is the domain of the Gitlab instance to\n    apply the configuration for.\n    This leads to a behavior that is equivalent to calling datalad's\n    ``siblings('configure', ...)``||``siblings configure`` command with the\n    respective KEY-VALUE pair after creating the sibling.\n    The configuration, like any other, could be set at user- or system level, so\n    users do not need to add this configuration to every sibling created with\n    the service at NETLOC themselves.\n\n    \"\"\"\n    _params_ = dict(\n        path=Parameter(\n            args=('path',),\n            metavar='PATH',\n            nargs='*',\n            doc=\"\"\"selectively create siblings for any datasets underneath a given\n            path. By default only the root dataset is considered.\"\"\"),\n        dataset=Parameter(\n            args=(\"--dataset\", \"-d\",),\n            doc=\"\"\"reference or root dataset. If no path constraints are given,\n            a sibling for this dataset will be created. In this and all other\n            cases, the reference dataset is also consulted for the GitLab\n            configuration, and desired project layout. If no dataset is given,\n            an attempt is made to identify the dataset based on the current\n            working directory\"\"\",\n            constraints=EnsureDataset() | EnsureNone()),\n        site=Parameter(\n            args=('--site',),\n            metavar='SITENAME',\n            doc=\"\"\"name of the GitLab site to create a sibling at. Must match an\n            existing python-gitlab configuration section with location and\n            authentication settings (see\n            https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#configuration).\n            By default the dataset configuration is consulted.\n            \"\"\",\n            constraints=EnsureNone() | EnsureStr()),\n        project=Parameter(\n            args=('--project',),\n            metavar='NAME/LOCATION',\n            doc=\"\"\"project name/location at the GitLab site. If a subdataset of the\n            reference dataset is processed, its project path is automatically\n            determined by the `layout` configuration, by default.\n            \"\"\",\n            constraints=EnsureNone() | EnsureStr()),\n        layout=Parameter(\n            args=('--layout',),\n            constraints=EnsureChoice(None, *known_layout_labels),\n            doc=\"\"\"layout of projects at the GitLab site, if a collection, or\n            a hierarchy of datasets and subdatasets is to be created.\n            By default the dataset configuration is consulted.\n            \"\"\"),\n        recursive=recursion_flag,\n        recursion_limit=recursion_limit,\n        name=Parameter(\n            args=('-s', '--name',),\n            metavar='NAME',\n            doc=\"\"\"name to represent the GitLab sibling remote in the local\n            dataset installation. If not specified a name is looked up in the\n            dataset configuration, or defaults to the `site` name\"\"\",\n            constraints=EnsureStr() | EnsureNone()),\n        existing=Parameter(\n            args=(\"--existing\",),\n            constraints=EnsureChoice('skip', 'error', 'reconfigure'),\n            doc=\"\"\"desired behavior when already existing or configured\n            siblings are discovered. 'skip': ignore; 'error': fail, if access\n            URLs differ; 'reconfigure': use the existing repository and\n            reconfigure the local dataset to use it as a sibling\"\"\",),\n        access=Parameter(\n            args=(\"--access\",),\n            constraints=EnsureChoice(None, *known_access_labels),\n            doc=\"\"\"access method used for data transfer to and from the sibling.\n            'ssh': read and write access used the SSH protocol; 'http': read and\n            write access use HTTP requests; 'ssh+http': read access is done via\n            HTTP and write access performed with SSH. Dataset configuration is\n            consulted for a default, 'http' is used otherwise.\"\"\",),\n        description=Parameter(\n            args=(\"--description\",),\n            doc=\"\"\"brief description for the GitLab project (displayed on the\n            site)\"\"\",\n            constraints=EnsureStr() | EnsureNone()),\n        publish_depends=publish_depends,\n        dry_run=Parameter(\n            args=(\"--dry-run\",),\n            action=\"store_true\",\n            doc=\"\"\"if set, no repository will be created, only tests for\n            name collisions will be performed, and would-be repository names\n            are reported for all relevant datasets\"\"\"),\n        dryrun=Parameter(\n            args=(\"--dryrun\",),\n            action=\"store_true\",\n            doc=\"\"\"Deprecated. Use the renamed\n            ``dry_run||--dry-run`` parameter\"\"\")\n    )\n\n    @staticmethod\n    @datasetmethod(name='create_sibling_gitlab')\n    @eval_results\n    def __call__(\n            path=None,\n            *,\n            site=None,\n            project=None,\n            layout=None,\n            dataset=None,\n            recursive=False,\n            recursion_limit=None,\n            name=None,\n            existing='error',\n            access=None,\n            publish_depends=None,\n            description=None,\n            dryrun=False,\n            dry_run=False):\n        if dryrun and not dry_run:\n            # the old one is used, and not in agreement with the new one\n            warnings.warn(\n                \"datalad-create-sibling-github's `dryrun` option is \"\n                \"deprecated and will be removed in a future release, \"\n                \"use the renamed `dry_run/--dry-run` option instead.\",\n                DeprecationWarning)\n            dry_run = dryrun\n        path = resolve_path(ensure_list(path), ds=dataset) \\\n            if path else None\n\n        if project and (recursive or (path and len(path) > 1)):\n            raise ValueError(\n                'Providing a GitLab project name/location cannot be combined '\n                'with recursive operation or multiple paths, as each dataset '\n                'needs to be mapped onto its own individual project.')\n        # what to operate on\n        ds = require_dataset(\n            dataset, check_installed=True, purpose='create GitLab sibling(s)')\n\n        # cache for objects of gitlab sites (we could face different ones\n        # in a single hierarchy, cache them to avoid duplicate initialization\n        # while still being able to process each dataset individually\n        siteobjs = dict()\n\n        # which datasets to process?\n        if path is None or ds.pathobj in path:\n            for r in _proc_dataset(\n                    ds, ds,\n                    site, project, name, layout, existing, access,\n                    dry_run, siteobjs, publish_depends, description):\n                yield r\n        # we need to find a subdataset when recursing, or when there is a path that\n        # could point to one, we have to exclude the parent dataset in this test\n        # to avoid undesired level-1 recursion into subdatasets\n        if any(p != ds.pathobj for p in (path or [])) or recursive:\n            # also include any matching subdatasets\n            subds = ds.subdatasets(\n                    path=path,\n                    # we can only operate on present datasets\n                    state='present',\n                    recursive=recursive,\n                    recursion_limit=recursion_limit,\n                    contains=None,\n                    bottomup=False,\n                    result_xfm='datasets',\n                    result_renderer='disabled',\n                    return_type='list')\n            if not subds:\n                # we didn't find anything to operate on, let the user know\n                for p in path:\n                    yield dict(\n                          action='create_sibling_gitlab',\n                          status='impossible',\n                          refds=ds.path,\n                          path=p,\n                          message=('No dataset found under %s' % p),\n                          type='dataset',\n                          logger=lgr,\n                    )\n            else:\n                for sub in subds:\n                    for r in _proc_dataset(\n                            ds, sub,\n                            site, project, name, layout, existing, access,\n                            dry_run, siteobjs, publish_depends, description):\n                        yield r\n\n        return\n\n\ndef _proc_dataset(refds, ds, site, project, remotename, layout, existing,\n                  access, dry_run, siteobjs, depends, description):\n    # basic result setup\n    res_kwargs = dict(\n        action='create_sibling_gitlab',\n        refds=refds.path,\n        path=ds.path,\n        type='dataset',\n        logger=lgr,\n    )\n    if description:\n        res_kwargs['description'] = description\n\n    if site is None:\n        # always try pulling the base config from a parent dataset\n        # even if paths were given (may be overwritten later)\n        basecfgsite = ds.config.get('datalad.gitlab-default-site', None)\n\n    # let the dataset config overwrite the target site, if none\n    # was given\n    site = refds.config.get(\n        'datalad.gitlab-default-site', basecfgsite) \\\n        if site is None else site\n    if site is None:\n        # this means the most top-level dataset has no idea about\n        # gitlab, and no site was specified as an argument\n        # fail rather then give an error result, as this is very\n        # unlikely to be intentional\n        raise ValueError(\n            'No GitLab site was specified (--site) or configured '\n            'in {} (datalad.gitlab.default-site)'.format(ds))\n    res_kwargs['site'] = site\n\n    # determine target remote name, unless given\n    if remotename is None:\n        remotename_var = 'datalad.gitlab-{}-siblingname'.format(site)\n        remotename = ds.config.get(\n            remotename_var,\n            # use config from parent, if needed\n            refds.config.get(\n                remotename_var,\n                # fall back on site name, if nothing else can be used\n                site))\n    res_kwargs['sibling'] = remotename\n    # check against existing remotes\n    dremotes = {\n        r['name']: r\n        for r in ds.siblings(\n            action='query',\n            # fastest possible\n            get_annex_info=False,\n            recursive=False,\n            return_type='generator',\n            result_renderer='disabled')\n    }\n    if remotename in dremotes and existing not in ['replace', 'reconfigure']:\n        # we already know a sibling with this name\n        yield dict(\n            res_kwargs,\n            status='error' if existing == 'error' else 'notneeded',\n            message=('already has a configured sibling \"%s\"', remotename),\n        )\n        return\n\n    if layout is None:\n        # figure out the layout of projects on the site\n        # use the reference dataset as default, and fall back\n        # on 'hierarchy' as the most generic method of representing\n        # the filesystem in a group/subgroup structure\n        layout_var = 'datalad.gitlab-{}-layout'.format(site)\n        layout = ds.config.get(\n            layout_var, refds.config.get(\n                layout_var, 'hierarchy'))\n    if layout not in known_layout_labels:\n        raise ValueError(\n            \"Unknown site layout '{}' given or configured, \"\n            \"known ones are: {}\".format(layout, known_layout_labels))\n\n    if access is None:\n        access_var = 'datalad.gitlab-{}-access'.format(site)\n        access = ds.config.get(\n            access_var, refds.config.get(\n                access_var, 'http'))\n    if access not in known_access_labels:\n        raise ValueError(\n            \"Unknown site access '{}' given or configured, \"\n            \"known ones are: {}\".format(access, known_access_labels))\n\n    pathsep = ds.config.get(\"datalad.gitlab-default-pathseparator\", None) or \"-\"\n    project_stub = \\\n        ds.config.get(\"datalad.gitlab-default-projectname\", None) or \"project\"\n    project_var = 'datalad.gitlab-{}-project'.format(site)\n    process_root = refds == ds\n    if project is None:\n        # look for a specific config in the dataset\n        project = ds.config.get(project_var, None)\n\n    if project and process_root and layout != 'flat':\n        # the root of a collection\n        project = f'{project}/{project_stub}'\n    elif project is None and not process_root:\n        # check if we can build one from the refds config\n        ref_project = refds.config.get(project_var, None)\n        if ref_project:\n            # layout-specific derivation of a path from\n            # the reference dataset configuration\n            rproject = ds.pathobj.relative_to(refds.pathobj).as_posix()\n            if layout == 'hierarchy':\n                project = f'{ref_project}/{rproject}/{project_stub}'\n            elif layout == 'collection':\n                project = '{}/{}'.format(\n                    ref_project,\n                    rproject.replace('/', pathsep))\n            else:\n                project = '{}-{}'.format(\n                    ref_project,\n                    rproject.replace('/', pathsep))\n\n    if project is None:\n        yield dict(\n            res_kwargs,\n            status='error',\n            message='No project name/location specified, and no configuration '\n                    'to derive one',\n        )\n        return\n\n    res_kwargs['project'] = project\n\n    if dry_run:\n        # this is as far as we can get without talking to GitLab\n        yield dict(\n            res_kwargs,\n            status='ok',\n            dryrun=True,\n        )\n        return\n\n    # and now talk to GitLab for real\n    site_api = siteobjs[site] if site in siteobjs else GitLabSite(site)\n\n    site_project = site_api.get_project(project)\n    if site_project is None:\n        try:\n            site_project = site_api.create_project(project, description)\n            # report success\n            message = \"sibling repository '%s' created at %s\",\\\n                      remotename, site_project.get('web_url', None)\n            yield dict(\n                res_kwargs,\n                # relay all attributes\n                project_attributes=site_project,\n                message=message,\n                status='ok',\n            )\n        except Exception as e:\n            ce = CapturedException(e)\n            yield dict(\n                res_kwargs,\n                # relay all attributes\n                status='error',\n                message=('Failed to create GitLab project: %s', ce),\n                exception=ce\n            )\n            return\n    else:\n        # there already is a project\n        if existing == 'error':\n            # be nice and only actually error if there is a real mismatch\n            if remotename not in dremotes:\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but no sibling with name '%s' is configured, \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site, remotename,\n                    )\n                )\n                return\n            elif access in ('ssh', 'ssh+http') \\\n                    and dremotes[remotename].get(\n                        'url', None) != site_project.get(\n                            # use False as a default so that there is a\n                            # mismatch, complain if both are missing\n                            'ssh_url_to_repo', False):\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but SSH access URL '%s' does not match '%s', \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site,\n                        dremotes[remotename].get('url', None),\n                        site_project.get('ssh_url_to_repo', None)\n                    )\n                )\n                return\n            elif access == 'http' \\\n                    and dremotes[remotename].get(\n                        'url', None) != site_project.get(\n                            # use False as a default so that there is a\n                            # mismatch, veen if both are missing\n                            'http_url_to_repo', False):\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but HTTP access URL '%s' does not match '%s', \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site,\n                        dremotes[remotename].get('url', None),\n                        site_project.get('http_url_to_repo', None)\n                    )\n                )\n                return\n        yield dict(\n            res_kwargs,\n            project_attributes=site_project,\n            status='notneeded',\n            message=(\n                \"There is already a project at '%s' on site '%s'\",\n                project, site,\n            )\n        )\n\n    # first make sure that annex doesn't touch this one\n    # but respect any existing config\n    ignore_var = 'remote.{}.annex-ignore'.format(remotename)\n    if ignore_var not in ds.config:\n        ds.config.add(ignore_var, 'true', scope='local')\n\n    for res in ds.siblings(\n            'configure',\n            name=remotename,\n            url=site_project['http_url_to_repo']\n            if access in ('http', 'ssh+http')\n            else site_project['ssh_url_to_repo'],\n            pushurl=site_project['ssh_url_to_repo']\n            if access in ('ssh', 'ssh+http')\n            else None,\n            recursive=False,\n            publish_depends=depends,\n            result_renderer='disabled',\n            return_type='generator'):\n        yield res\n\n\nclass GitLabSite(object):\n    def __init__(self, site):\n        import gitlab\n        self.gitlab = gitlab\n        try:\n            self.site = gitlab.Gitlab.from_config(site)\n        except gitlab.config.GitlabDataError as e:\n            raise ValueError(\n                '{}, please configure access to this GitLab instance'.format(\n                    str(e)))\n\n    def get_project(self, path):\n        try:\n            return self.site.projects.get(path).attributes\n        except self.gitlab.GitlabGetError as e:\n            lgr.debug(\"Project with path '%s' does not yet exist at site '%s'\",\n                      path, self.site.url)\n            return None\n\n    def create_project(self, path, description=None):\n        path_l = path.split('/')\n        namespace_id = self._obtain_namespace(path_l)\n        # check for options:\n        # https://gitlab.com/help/api/projects.md#create-project\n        props = dict(\n            name=path_l[-1],\n            namespace_id=namespace_id,\n        )\n        if description:\n            props['description'] = description\n        project = self.site.projects.create(props)\n        return project.attributes\n\n    def _obtain_namespace(self, path_l):\n\n        if len(path_l) == 1:\n            # no nesting whatsoever\n            return None\n\n        try:\n            namespace_id = self.site.groups.get(\n                '/'.join(path_l[:-1])).get_id()\n            lgr.debug(\"Found existing parent group '%s' with ID %s\",\n                      '/'.join(path_l[:-1]), namespace_id)\n        except self.gitlab.GitlabGetError as e:\n            try:\n                if len(path_l) > 2:\n                    parent_group = self.site.groups.get(\n                        '/'.join(path_l[:-2]))\n                else:\n                    parent_group = None\n            except self.gitlab.GitlabGetError as e:\n                raise ValueError(\n                    \"No parent group {} for project {} found, \"\n                    \"and a group {} also does not exist. At most one \"\n                    \"parent group would be created.\".format(\n                        '/'.join(path_l[:-1]),\n                        '/'.join(path_l),\n                        '/'.join(path_l[:-2]),\n                    ))\n            # create the group for the target project\n            try:\n                # prevent failure due to specification of a users personal\n                # group, always exists, cannot and must not be created\n                self.site.auth()\n                if len(path_l) == 2 \\\n                        and path_l[0] == self.site.user.attributes.get(\n                            'username', None):\n                    # attempt to create a personal project in the users\n                    # top-level personal group-- this is the same as\n                    # having no parent namespace, don't attempt to\n                    # create the group\n                    return None\n                namespace_id = self.site.groups.create(dict(\n                    name=path_l[-2],\n                    path=path_l[-2],\n                    parent_id=parent_group.get_id() if parent_group else None)\n                ).get_id()\n            except self.gitlab.GitlabCreateError as e:\n                raise RuntimeError(\n                    \"Failed to create parent group '{}' under {}: {}\".format(\n                        path_l[-2],\n                        repr(parent_group.attributes['full_path'])\n                        if parent_group else 'the account root',\n                        str(e)),\n                )\n        return namespace_id\n",
            "header": {
                "file": {
                    "path": "datalad/distributed/create_sibling_gitlab.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "2e6a5c0d6",
                "new_commit_hash": "30d0bbdd1",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/distributed/create_sibling_gitlab.py"
            },
            "plus_file": {
                "path": "b/datalad/distributed/create_sibling_gitlab.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 416,
                            "length": 7
                        },
                        "new_range": {
                            "start": 416,
                            "length": 7
                        },
                        "section": "def _proc_dataset(refds, ds, site, project, remotename, layout, existing,"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        # look for a specific config in the dataset",
                                "type": "context"
                            },
                            {
                                "content": "        project = ds.config.get(project_var, None)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    if project and process_root and layout == 'collection':",
                                "type": "deleted"
                            },
                            {
                                "content": "    if project and process_root and layout != 'flat':",
                                "type": "added"
                            },
                            {
                                "content": "        # the root of a collection",
                                "type": "context"
                            },
                            {
                                "content": "        project = f'{project}/{project_stub}'",
                                "type": "context"
                            },
                            {
                                "content": "    elif project is None and not process_root:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/create_sibling_gitlab.py",
                            "type": "function",
                            "name": "_proc_dataset",
                            "content": "def _proc_dataset(refds, ds, site, project, remotename, layout, existing,\n                  access, dry_run, siteobjs, depends, description):\n    # basic result setup\n    res_kwargs = dict(\n        action='create_sibling_gitlab',\n        refds=refds.path,\n        path=ds.path,\n        type='dataset',\n        logger=lgr,\n    )\n    if description:\n        res_kwargs['description'] = description\n\n    if site is None:\n        # always try pulling the base config from a parent dataset\n        # even if paths were given (may be overwritten later)\n        basecfgsite = ds.config.get('datalad.gitlab-default-site', None)\n\n    # let the dataset config overwrite the target site, if none\n    # was given\n    site = refds.config.get(\n        'datalad.gitlab-default-site', basecfgsite) \\\n        if site is None else site\n    if site is None:\n        # this means the most top-level dataset has no idea about\n        # gitlab, and no site was specified as an argument\n        # fail rather then give an error result, as this is very\n        # unlikely to be intentional\n        raise ValueError(\n            'No GitLab site was specified (--site) or configured '\n            'in {} (datalad.gitlab.default-site)'.format(ds))\n    res_kwargs['site'] = site\n\n    # determine target remote name, unless given\n    if remotename is None:\n        remotename_var = 'datalad.gitlab-{}-siblingname'.format(site)\n        remotename = ds.config.get(\n            remotename_var,\n            # use config from parent, if needed\n            refds.config.get(\n                remotename_var,\n                # fall back on site name, if nothing else can be used\n                site))\n    res_kwargs['sibling'] = remotename\n    # check against existing remotes\n    dremotes = {\n        r['name']: r\n        for r in ds.siblings(\n            action='query',\n            # fastest possible\n            get_annex_info=False,\n            recursive=False,\n            return_type='generator',\n            result_renderer='disabled')\n    }\n    if remotename in dremotes and existing not in ['replace', 'reconfigure']:\n        # we already know a sibling with this name\n        yield dict(\n            res_kwargs,\n            status='error' if existing == 'error' else 'notneeded',\n            message=('already has a configured sibling \"%s\"', remotename),\n        )\n        return\n\n    if layout is None:\n        # figure out the layout of projects on the site\n        # use the reference dataset as default, and fall back\n        # on 'hierarchy' as the most generic method of representing\n        # the filesystem in a group/subgroup structure\n        layout_var = 'datalad.gitlab-{}-layout'.format(site)\n        layout = ds.config.get(\n            layout_var, refds.config.get(\n                layout_var, 'hierarchy'))\n    if layout not in known_layout_labels:\n        raise ValueError(\n            \"Unknown site layout '{}' given or configured, \"\n            \"known ones are: {}\".format(layout, known_layout_labels))\n\n    if access is None:\n        access_var = 'datalad.gitlab-{}-access'.format(site)\n        access = ds.config.get(\n            access_var, refds.config.get(\n                access_var, 'http'))\n    if access not in known_access_labels:\n        raise ValueError(\n            \"Unknown site access '{}' given or configured, \"\n            \"known ones are: {}\".format(access, known_access_labels))\n\n    pathsep = ds.config.get(\"datalad.gitlab-default-pathseparator\", None) or \"-\"\n    project_stub = \\\n        ds.config.get(\"datalad.gitlab-default-projectname\", None) or \"project\"\n    project_var = 'datalad.gitlab-{}-project'.format(site)\n    process_root = refds == ds\n    if project is None:\n        # look for a specific config in the dataset\n        project = ds.config.get(project_var, None)\n\n    if project and process_root and layout == 'collection':\n        # the root of a collection\n        project = f'{project}/{project_stub}'\n    elif project is None and not process_root:\n        # check if we can build one from the refds config\n        ref_project = refds.config.get(project_var, None)\n        if ref_project:\n            # layout-specific derivation of a path from\n            # the reference dataset configuration\n            rproject = ds.pathobj.relative_to(refds.pathobj).as_posix()\n            if layout == 'hierarchy':\n                project = f'{ref_project}/{rproject}/{project_stub}'\n            elif layout == 'collection':\n                project = '{}/{}'.format(\n                    ref_project,\n                    rproject.replace('/', pathsep))\n            else:\n                project = '{}-{}'.format(\n                    ref_project,\n                    rproject.replace('/', pathsep))\n\n    if project is None:\n        yield dict(\n            res_kwargs,\n            status='error',\n            message='No project name/location specified, and no configuration '\n                    'to derive one',\n        )\n        return\n\n    res_kwargs['project'] = project\n\n    if dry_run:\n        # this is as far as we can get without talking to GitLab\n        yield dict(\n            res_kwargs,\n            status='ok',\n            dryrun=True,\n        )\n        return\n\n    # and now talk to GitLab for real\n    site_api = siteobjs[site] if site in siteobjs else GitLabSite(site)\n\n    site_project = site_api.get_project(project)\n    if site_project is None:\n        try:\n            site_project = site_api.create_project(project, description)\n            # report success\n            message = \"sibling repository '%s' created at %s\",\\\n                      remotename, site_project.get('web_url', None)\n            yield dict(\n                res_kwargs,\n                # relay all attributes\n                project_attributes=site_project,\n                message=message,\n                status='ok',\n            )\n        except Exception as e:\n            ce = CapturedException(e)\n            yield dict(\n                res_kwargs,\n                # relay all attributes\n                status='error',\n                message=('Failed to create GitLab project: %s', ce),\n                exception=ce\n            )\n            return\n    else:\n        # there already is a project\n        if existing == 'error':\n            # be nice and only actually error if there is a real mismatch\n            if remotename not in dremotes:\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but no sibling with name '%s' is configured, \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site, remotename,\n                    )\n                )\n                return\n            elif access in ('ssh', 'ssh+http') \\\n                    and dremotes[remotename].get(\n                        'url', None) != site_project.get(\n                            # use False as a default so that there is a\n                            # mismatch, complain if both are missing\n                            'ssh_url_to_repo', False):\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but SSH access URL '%s' does not match '%s', \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site,\n                        dremotes[remotename].get('url', None),\n                        site_project.get('ssh_url_to_repo', None)\n                    )\n                )\n                return\n            elif access == 'http' \\\n                    and dremotes[remotename].get(\n                        'url', None) != site_project.get(\n                            # use False as a default so that there is a\n                            # mismatch, veen if both are missing\n                            'http_url_to_repo', False):\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but HTTP access URL '%s' does not match '%s', \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site,\n                        dremotes[remotename].get('url', None),\n                        site_project.get('http_url_to_repo', None)\n                    )\n                )\n                return\n        yield dict(\n            res_kwargs,\n            project_attributes=site_project,\n            status='notneeded',\n            message=(\n                \"There is already a project at '%s' on site '%s'\",\n                project, site,\n            )\n        )\n\n    # first make sure that annex doesn't touch this one\n    # but respect any existing config\n    ignore_var = 'remote.{}.annex-ignore'.format(remotename)\n    if ignore_var not in ds.config:\n        ds.config.add(ignore_var, 'true', scope='local')\n\n    for res in ds.siblings(\n            'configure',\n            name=remotename,\n            url=site_project['http_url_to_repo']\n            if access in ('http', 'ssh+http')\n            else site_project['ssh_url_to_repo'],\n            pushurl=site_project['ssh_url_to_repo']\n            if access in ('ssh', 'ssh+http')\n            else None,\n            recursive=False,\n            publish_depends=depends,\n            result_renderer='disabled',\n            return_type='generator'):\n        yield res",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 322,
                            "end_lineno": 573,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test create publication target on gitlab\"\"\"\n\nimport os\n\n# this must import ok with and without gitlab\nfrom datalad.api import (\n    Dataset,\n    create,\n    create_sibling_gitlab,\n)\nfrom datalad.tests.utils_pytest import (\n    assert_raises,\n    assert_repo_status,\n    assert_result_count,\n    assert_status,\n    eq_,\n    with_tempfile,\n)\nfrom datalad.utils import chpwd\n\n\ndef _get_nested_collections(path):\n    ds = Dataset(path).create()\n    c1 = ds.create(ds.pathobj / 'subdir' / 'collection1')\n    c1s1 = c1.create('sub1')\n    c1s2 = c1.create('sub2')\n    c2 = ds.create('collection2')\n    c2s1 = c2.create('sub1')\n    c2s11 = c2s1.create('deepsub1')\n    ds.save(recursive=True)\n    assert_repo_status(ds.path)\n    # return a catalog\n    return dict(\n        root=ds,\n        c1=c1,\n        c1s1=c1s1,\n        c1s2=c1s2,\n        c2=c2,\n        c2s1=c2s1,\n        c2s11=c2s11,\n    )\n\n\n# doesn't actually need gitlab and exercises most of the decision logic\n@with_tempfile\ndef test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n\n\nclass _FakeGitLab(object):\n    def __init__(self, site):\n        pass\n\n\nclass _NewProjectGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return None\n\n    def create_project(self, path, description=None):\n        return dict(\n            http_url_to_repo='http://example.com',\n            ssh_url_to_repo='example.com',\n            description=description,\n        )\n\n\nclass _ExistingProjectGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return dict(\n            http_url_to_repo='http://example.com',\n            ssh_url_to_repo='example.com',\n        )\n\n\nclass _ExistingProjectOtherURLGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return dict(\n            http_url_to_repo='http://example2.com',\n            ssh_url_to_repo='example2.com',\n        )\n\n\nclass _CreateFailureGitLab(_FakeGitLab):\n    def get_project(self, path):\n        None\n\n    def create_project(self, path, description=None):\n        raise RuntimeError\n\n\n@with_tempfile\ndef test_fake_gitlab(path=None):\n    from unittest.mock import patch\n    ds = Dataset(path).create()\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='dummy', project='here', description='thisisit')\n        assert_result_count(res, 2)\n        # GitLab success\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='dummy', sibling='dummy', project='here', description='thisisit',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': 'thisisit'\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='dummy',\n            url='http://example.com', status='ok')\n\n    # test sibling name conflicts\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='notneeded',\n            type='dataset'\n            )\n    # sibling name conflict with existing='error' should yiel error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='error',\n            type='dataset'\n            )\n    # try recreation, the sibling is already configured, same setup, no error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='reconfigure')\n        assert_result_count(\n        res, 1, action='configure-sibling', path=path, name='dummy',\n        url='http://example.com', status='ok')\n        # but error when the name differs\n        res = ds.create_sibling_gitlab(\n            site='dummy', project='here', name='othername', on_failure='ignore')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path,\n            site='dummy', sibling='othername', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com'\n            },\n            status='error')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _CreateFailureGitLab):\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='dummy', project='here', on_failure='ignore')\n        )\n\n    # new sibling, ssh access\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here', access='ssh')\n        assert_result_count(res, 2)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='sshsite', sibling='sshsite', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': None\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='sshsite',\n            url='example.com', status='ok')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectOtherURLGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here',\n                                       access='ssh', on_failure='ignore',\n                                       name='sshsite2')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=[\"There is already a project at '%s' on site '%s', \"\n                     \"but no sibling with name '%s' is configured, \"\n                     \"maybe use --existing=reconfigure\", \"here\", \"sshsite\",\n                     \"sshsite2\"],\n            path=path,\n            refds=path,\n            site='sshsite', sibling='sshsite2', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example2.com',\n                'ssh_url_to_repo': 'example2.com'\n            },\n            status='error',\n            type='dataset')\n        # same goes for switching the access type without --reconfigure\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='sshsite', project='here',\n                                     access='http', on_failure='ignore')\n        )\n",
            "new_file_content": "# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test create publication target on gitlab\"\"\"\n\nimport os\n\n# this must import ok with and without gitlab\nfrom datalad.api import (\n    Dataset,\n    create,\n    create_sibling_gitlab,\n)\nfrom datalad.tests.utils_pytest import (\n    assert_raises,\n    assert_repo_status,\n    assert_result_count,\n    assert_status,\n    eq_,\n    with_tempfile,\n)\nfrom datalad.utils import chpwd\n\n\ndef _get_nested_collections(path):\n    ds = Dataset(path).create()\n    c1 = ds.create(ds.pathobj / 'subdir' / 'collection1')\n    c1s1 = c1.create('sub1')\n    c1s2 = c1.create('sub2')\n    c2 = ds.create('collection2')\n    c2s1 = c2.create('sub1')\n    c2s11 = c2s1.create('deepsub1')\n    ds.save(recursive=True)\n    assert_repo_status(ds.path)\n    # return a catalog\n    return dict(\n        root=ds,\n        c1=c1,\n        c1s1=c1s1,\n        c1s2=c1s2,\n        c2=c2,\n        c2s1=c2s1,\n        c2s11=c2s11,\n    )\n\n\n# doesn't actually need gitlab and exercises most of the decision logic\n@with_tempfile\ndef test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here/project',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here/project',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here/project',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here/project',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret/project',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/myownname',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n\n\nclass _FakeGitLab(object):\n    def __init__(self, site):\n        pass\n\n\nclass _NewProjectGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return None\n\n    def create_project(self, path, description=None):\n        return dict(\n            http_url_to_repo='http://example.com',\n            ssh_url_to_repo='example.com',\n            description=description,\n        )\n\n\nclass _ExistingProjectGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return dict(\n            http_url_to_repo='http://example.com',\n            ssh_url_to_repo='example.com',\n        )\n\n\nclass _ExistingProjectOtherURLGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return dict(\n            http_url_to_repo='http://example2.com',\n            ssh_url_to_repo='example2.com',\n        )\n\n\nclass _CreateFailureGitLab(_FakeGitLab):\n    def get_project(self, path):\n        None\n\n    def create_project(self, path, description=None):\n        raise RuntimeError\n\n\n@with_tempfile\ndef test_fake_gitlab(path=None):\n    from unittest.mock import patch\n    ds = Dataset(path).create()\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='dummy', project='here', description='thisisit')\n        assert_result_count(res, 2)\n        # GitLab success\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='dummy', sibling='dummy', project='here/project', description='thisisit',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': 'thisisit'\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='dummy',\n            url='http://example.com', status='ok')\n\n    # test sibling name conflicts\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='notneeded',\n            type='dataset'\n            )\n    # sibling name conflict with existing='error' should yiel error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='error',\n            type='dataset'\n            )\n    # try recreation, the sibling is already configured, same setup, no error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='reconfigure')\n        assert_result_count(\n        res, 1, action='configure-sibling', path=path, name='dummy',\n        url='http://example.com', status='ok')\n        # but error when the name differs\n        res = ds.create_sibling_gitlab(\n            site='dummy', project='here', name='othername', on_failure='ignore')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path,\n            site='dummy', sibling='othername', project='here/project',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com'\n            },\n            status='error')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _CreateFailureGitLab):\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='dummy', project='here', on_failure='ignore')\n        )\n\n    # new sibling, ssh access\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here', access='ssh')\n        assert_result_count(res, 2)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='sshsite', sibling='sshsite', project='here/project',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': None\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='sshsite',\n            url='example.com', status='ok')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectOtherURLGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here',\n                                       access='ssh', on_failure='ignore',\n                                       name='sshsite2')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=[\"There is already a project at '%s' on site '%s', \"\n                     \"but no sibling with name '%s' is configured, \"\n                     \"maybe use --existing=reconfigure\", \"here\", \"sshsite\",\n                     \"sshsite2\"],\n            path=path,\n            refds=path,\n            site='sshsite', sibling='sshsite2', project='here/project',\n            project_attributes={\n                'http_url_to_repo': 'http://example2.com',\n                'ssh_url_to_repo': 'example2.com'\n            },\n            status='error',\n            type='dataset')\n        # same goes for switching the access type without --reconfigure\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='sshsite', project='here',\n                                     access='http', on_failure='ignore')\n        )\n",
            "header": {
                "file": {
                    "path": "datalad/distributed/tests/test_create_sibling_gitlab.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "ef93bab23",
                "new_commit_hash": "2cd0aeefb",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/distributed/tests/test_create_sibling_gitlab.py"
            },
            "plus_file": {
                "path": "b/datalad/distributed/tests/test_create_sibling_gitlab.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 92,
                            "length": 7
                        },
                        "new_range": {
                            "start": 92,
                            "length": 7
                        },
                        "section": "def test_dryrun(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        assert_result_count(res, 1)",
                                "type": "context"
                            },
                            {
                                "content": "        assert_result_count(",
                                "type": "context"
                            },
                            {
                                "content": "            res, 1, path=ctlg['root'].path, type='dataset', status='ok',",
                                "type": "context"
                            },
                            {
                                "content": "            site='dummy', sibling='dummy', project='here',",
                                "type": "deleted"
                            },
                            {
                                "content": "            site='dummy', sibling='dummy', project='here/project',",
                                "type": "added"
                            },
                            {
                                "content": "        )",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # now configure a default gitlab site",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_dryrun",
                            "content": "def test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 53,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 106,
                            "length": 7
                        },
                        "new_range": {
                            "start": 106,
                            "length": 7
                        },
                        "section": "def test_dryrun(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    assert_result_count(res, 1)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_result_count(",
                                "type": "context"
                            },
                            {
                                "content": "        res, 1, path=ctlg['root'].path, type='dataset', status='ok',",
                                "type": "context"
                            },
                            {
                                "content": "        site='theone', sibling='ursula', project='here',",
                                "type": "deleted"
                            },
                            {
                                "content": "        site='theone', sibling='ursula', project='here/project',",
                                "type": "added"
                            },
                            {
                                "content": "    )",
                                "type": "context"
                            },
                            {
                                "content": "    # now configure a sibling name for this site",
                                "type": "context"
                            },
                            {
                                "content": "    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_dryrun",
                            "content": "def test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 53,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 119,
                            "length": 7
                        },
                        "new_range": {
                            "start": 119,
                            "length": 7
                        },
                        "section": "def test_dryrun(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    )",
                                "type": "context"
                            },
                            {
                                "content": "    assert_result_count(",
                                "type": "context"
                            },
                            {
                                "content": "        res, 1, path=ctlg['root'].path, type='dataset', status='ok',",
                                "type": "context"
                            },
                            {
                                "content": "        site='theone', sibling='dieter', project='here',",
                                "type": "deleted"
                            },
                            {
                                "content": "        site='theone', sibling='dieter', project='here/project',",
                                "type": "added"
                            },
                            {
                                "content": "    )",
                                "type": "context"
                            },
                            {
                                "content": "    # properly switches the name based on site",
                                "type": "context"
                            },
                            {
                                "content": "    res = ctlg['root'].create_sibling_gitlab(",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_dryrun",
                            "content": "def test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 53,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 128,
                            "length": 7
                        },
                        "new_range": {
                            "start": 128,
                            "length": 7
                        },
                        "section": "def test_dryrun(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    )",
                                "type": "context"
                            },
                            {
                                "content": "    assert_result_count(",
                                "type": "context"
                            },
                            {
                                "content": "        res, 1, path=ctlg['root'].path, type='dataset', status='ok',",
                                "type": "context"
                            },
                            {
                                "content": "        site='otherone', sibling='ulf', project='here',",
                                "type": "deleted"
                            },
                            {
                                "content": "        site='otherone', sibling='ulf', project='here/project',",
                                "type": "added"
                            },
                            {
                                "content": "    )",
                                "type": "context"
                            },
                            {
                                "content": "    # reports notneeded on existing='skip' with an existing remote",
                                "type": "context"
                            },
                            {
                                "content": "    ctlg['root'].repo.add_remote('dieter', 'http://example.com')",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_dryrun",
                            "content": "def test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 53,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 148,
                            "length": 7
                        },
                        "new_range": {
                            "start": 148,
                            "length": 7
                        },
                        "section": "def test_dryrun(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    res = ctlg['root'].create_sibling_gitlab(dry_run=True)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_result_count(",
                                "type": "context"
                            },
                            {
                                "content": "        res, 1, path=ctlg['root'].path, type='dataset', status='ok',",
                                "type": "context"
                            },
                            {
                                "content": "        site='theone', sibling='dieter', project='secret',",
                                "type": "deleted"
                            },
                            {
                                "content": "        site='theone', sibling='dieter', project='secret/project',",
                                "type": "added"
                            },
                            {
                                "content": "    )",
                                "type": "context"
                            },
                            {
                                "content": "    # we can make use of the config in the base dataset to drive",
                                "type": "context"
                            },
                            {
                                "content": "    # calls on subdatasets: use -d plus a path",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_dryrun",
                            "content": "def test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 53,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 226,
                            "length": 10
                        },
                        "new_range": {
                            "start": 226,
                            "length": 10
                        },
                        "section": "def test_dryrun(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    eq_(",
                                "type": "context"
                            },
                            {
                                "content": "        sorted(r['project'] for r in res),",
                                "type": "context"
                            },
                            {
                                "content": "        [",
                                "type": "context"
                            },
                            {
                                "content": "            'secret',",
                                "type": "deleted"
                            },
                            {
                                "content": "            'secret/collection2/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/collection2/sub1/deepsub1/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/collection2/sub1/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/project',",
                                "type": "added"
                            },
                            {
                                "content": "            'secret/subdir/collection1/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/subdir/collection1/sub1/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/subdir/collection1/sub2/project',",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_dryrun",
                            "content": "def test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 53,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 268,
                            "length": 7
                        },
                        "new_range": {
                            "start": 268,
                            "length": 6
                        },
                        "section": "def test_dryrun(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    # test that the configurations work",
                                "type": "context"
                            },
                            {
                                "content": "    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')",
                                "type": "context"
                            },
                            {
                                "content": "    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')",
                                "type": "context"
                            },
                            {
                                "content": "    #import pdb; pdb.set_trace()",
                                "type": "deleted"
                            },
                            {
                                "content": "    res = ctlg['root'].create_sibling_gitlab(",
                                "type": "context"
                            },
                            {
                                "content": "        recursive=True, layout='flat', dry_run=True)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_result_count(res, len(ctlg))",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_dryrun",
                            "content": "def test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 53,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 305,
                            "length": 10
                        },
                        "new_range": {
                            "start": 304,
                            "length": 10
                        },
                        "section": "def test_dryrun(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    eq_(",
                                "type": "context"
                            },
                            {
                                "content": "        sorted(r['project'] for r in res),",
                                "type": "context"
                            },
                            {
                                "content": "        [",
                                "type": "context"
                            },
                            {
                                "content": "            'secret',",
                                "type": "deleted"
                            },
                            {
                                "content": "            'secret/collection2/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/collection2/sub1/deepsub1/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/collection2/sub1/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/myownname',",
                                "type": "added"
                            },
                            {
                                "content": "            'secret/subdir/collection1/project',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/subdir/collection1/sub1/myownname',",
                                "type": "context"
                            },
                            {
                                "content": "            'secret/subdir/collection1/sub2/project',",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_dryrun",
                            "content": "def test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    #import pdb; pdb.set_trace()\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 53,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 367,
                            "length": 7
                        },
                        "new_range": {
                            "start": 366,
                            "length": 7
                        },
                        "section": "def test_fake_gitlab(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        # GitLab success",
                                "type": "context"
                            },
                            {
                                "content": "        assert_result_count(",
                                "type": "context"
                            },
                            {
                                "content": "            res, 1, action='create_sibling_gitlab', path=path, type='dataset',",
                                "type": "context"
                            },
                            {
                                "content": "            site='dummy', sibling='dummy', project='here', description='thisisit',",
                                "type": "deleted"
                            },
                            {
                                "content": "            site='dummy', sibling='dummy', project='here/project', description='thisisit',",
                                "type": "added"
                            },
                            {
                                "content": "            project_attributes={",
                                "type": "context"
                            },
                            {
                                "content": "                'http_url_to_repo': 'http://example.com',",
                                "type": "context"
                            },
                            {
                                "content": "                'ssh_url_to_repo': 'example.com',",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_fake_gitlab",
                            "content": "def test_fake_gitlab(path=None):\n    from unittest.mock import patch\n    ds = Dataset(path).create()\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='dummy', project='here', description='thisisit')\n        assert_result_count(res, 2)\n        # GitLab success\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='dummy', sibling='dummy', project='here', description='thisisit',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': 'thisisit'\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='dummy',\n            url='http://example.com', status='ok')\n\n    # test sibling name conflicts\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='notneeded',\n            type='dataset'\n            )\n    # sibling name conflict with existing='error' should yiel error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='error',\n            type='dataset'\n            )\n    # try recreation, the sibling is already configured, same setup, no error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='reconfigure')\n        assert_result_count(\n        res, 1, action='configure-sibling', path=path, name='dummy',\n        url='http://example.com', status='ok')\n        # but error when the name differs\n        res = ds.create_sibling_gitlab(\n            site='dummy', project='here', name='othername', on_failure='ignore')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path,\n            site='dummy', sibling='othername', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com'\n            },\n            status='error')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _CreateFailureGitLab):\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='dummy', project='here', on_failure='ignore')\n        )\n\n    # new sibling, ssh access\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here', access='ssh')\n        assert_result_count(res, 2)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='sshsite', sibling='sshsite', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': None\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='sshsite',\n            url='example.com', status='ok')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectOtherURLGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here',\n                                       access='ssh', on_failure='ignore',\n                                       name='sshsite2')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=[\"There is already a project at '%s' on site '%s', \"\n                     \"but no sibling with name '%s' is configured, \"\n                     \"maybe use --existing=reconfigure\", \"here\", \"sshsite\",\n                     \"sshsite2\"],\n            path=path,\n            refds=path,\n            site='sshsite', sibling='sshsite2', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example2.com',\n                'ssh_url_to_repo': 'example2.com'\n            },\n            status='error',\n            type='dataset')\n        # same goes for switching the access type without --reconfigure\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='sshsite', project='here',\n                                     access='http', on_failure='ignore')\n        )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 361,
                            "end_lineno": 479,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 420,
                            "length": 7
                        },
                        "new_range": {
                            "start": 419,
                            "length": 7
                        },
                        "section": "def test_fake_gitlab(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        assert_result_count(res, 1)",
                                "type": "context"
                            },
                            {
                                "content": "        assert_result_count(",
                                "type": "context"
                            },
                            {
                                "content": "            res, 1, action='create_sibling_gitlab', path=path,",
                                "type": "context"
                            },
                            {
                                "content": "            site='dummy', sibling='othername', project='here',",
                                "type": "deleted"
                            },
                            {
                                "content": "            site='dummy', sibling='othername', project='here/project',",
                                "type": "added"
                            },
                            {
                                "content": "            project_attributes={",
                                "type": "context"
                            },
                            {
                                "content": "                'http_url_to_repo': 'http://example.com',",
                                "type": "context"
                            },
                            {
                                "content": "                'ssh_url_to_repo': 'example.com'",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_fake_gitlab",
                            "content": "def test_fake_gitlab(path=None):\n    from unittest.mock import patch\n    ds = Dataset(path).create()\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='dummy', project='here', description='thisisit')\n        assert_result_count(res, 2)\n        # GitLab success\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='dummy', sibling='dummy', project='here', description='thisisit',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': 'thisisit'\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='dummy',\n            url='http://example.com', status='ok')\n\n    # test sibling name conflicts\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='notneeded',\n            type='dataset'\n            )\n    # sibling name conflict with existing='error' should yiel error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='error',\n            type='dataset'\n            )\n    # try recreation, the sibling is already configured, same setup, no error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='reconfigure')\n        assert_result_count(\n        res, 1, action='configure-sibling', path=path, name='dummy',\n        url='http://example.com', status='ok')\n        # but error when the name differs\n        res = ds.create_sibling_gitlab(\n            site='dummy', project='here', name='othername', on_failure='ignore')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path,\n            site='dummy', sibling='othername', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com'\n            },\n            status='error')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _CreateFailureGitLab):\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='dummy', project='here', on_failure='ignore')\n        )\n\n    # new sibling, ssh access\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here', access='ssh')\n        assert_result_count(res, 2)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='sshsite', sibling='sshsite', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': None\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='sshsite',\n            url='example.com', status='ok')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectOtherURLGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here',\n                                       access='ssh', on_failure='ignore',\n                                       name='sshsite2')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=[\"There is already a project at '%s' on site '%s', \"\n                     \"but no sibling with name '%s' is configured, \"\n                     \"maybe use --existing=reconfigure\", \"here\", \"sshsite\",\n                     \"sshsite2\"],\n            path=path,\n            refds=path,\n            site='sshsite', sibling='sshsite2', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example2.com',\n                'ssh_url_to_repo': 'example2.com'\n            },\n            status='error',\n            type='dataset')\n        # same goes for switching the access type without --reconfigure\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='sshsite', project='here',\n                                     access='http', on_failure='ignore')\n        )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 361,
                            "end_lineno": 479,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 439,
                            "length": 7
                        },
                        "new_range": {
                            "start": 438,
                            "length": 7
                        },
                        "section": "def test_fake_gitlab(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        assert_result_count(res, 2)",
                                "type": "context"
                            },
                            {
                                "content": "        assert_result_count(",
                                "type": "context"
                            },
                            {
                                "content": "            res, 1, action='create_sibling_gitlab', path=path, type='dataset',",
                                "type": "context"
                            },
                            {
                                "content": "            site='sshsite', sibling='sshsite', project='here',",
                                "type": "deleted"
                            },
                            {
                                "content": "            site='sshsite', sibling='sshsite', project='here/project',",
                                "type": "added"
                            },
                            {
                                "content": "            project_attributes={",
                                "type": "context"
                            },
                            {
                                "content": "                'http_url_to_repo': 'http://example.com',",
                                "type": "context"
                            },
                            {
                                "content": "                'ssh_url_to_repo': 'example.com',",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_fake_gitlab",
                            "content": "def test_fake_gitlab(path=None):\n    from unittest.mock import patch\n    ds = Dataset(path).create()\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='dummy', project='here', description='thisisit')\n        assert_result_count(res, 2)\n        # GitLab success\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='dummy', sibling='dummy', project='here', description='thisisit',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': 'thisisit'\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='dummy',\n            url='http://example.com', status='ok')\n\n    # test sibling name conflicts\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='notneeded',\n            type='dataset'\n            )\n    # sibling name conflict with existing='error' should yiel error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='error',\n            type='dataset'\n            )\n    # try recreation, the sibling is already configured, same setup, no error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='reconfigure')\n        assert_result_count(\n        res, 1, action='configure-sibling', path=path, name='dummy',\n        url='http://example.com', status='ok')\n        # but error when the name differs\n        res = ds.create_sibling_gitlab(\n            site='dummy', project='here', name='othername', on_failure='ignore')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path,\n            site='dummy', sibling='othername', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com'\n            },\n            status='error')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _CreateFailureGitLab):\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='dummy', project='here', on_failure='ignore')\n        )\n\n    # new sibling, ssh access\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here', access='ssh')\n        assert_result_count(res, 2)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='sshsite', sibling='sshsite', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': None\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='sshsite',\n            url='example.com', status='ok')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectOtherURLGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here',\n                                       access='ssh', on_failure='ignore',\n                                       name='sshsite2')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=[\"There is already a project at '%s' on site '%s', \"\n                     \"but no sibling with name '%s' is configured, \"\n                     \"maybe use --existing=reconfigure\", \"here\", \"sshsite\",\n                     \"sshsite2\"],\n            path=path,\n            refds=path,\n            site='sshsite', sibling='sshsite2', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example2.com',\n                'ssh_url_to_repo': 'example2.com'\n            },\n            status='error',\n            type='dataset')\n        # same goes for switching the access type without --reconfigure\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='sshsite', project='here',\n                                     access='http', on_failure='ignore')\n        )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 361,
                            "end_lineno": 479,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 464,
                            "length": 7
                        },
                        "new_range": {
                            "start": 463,
                            "length": 7
                        },
                        "section": "def test_fake_gitlab(path=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                     \"sshsite2\"],",
                                "type": "context"
                            },
                            {
                                "content": "            path=path,",
                                "type": "context"
                            },
                            {
                                "content": "            refds=path,",
                                "type": "context"
                            },
                            {
                                "content": "            site='sshsite', sibling='sshsite2', project='here',",
                                "type": "deleted"
                            },
                            {
                                "content": "            site='sshsite', sibling='sshsite2', project='here/project',",
                                "type": "added"
                            },
                            {
                                "content": "            project_attributes={",
                                "type": "context"
                            },
                            {
                                "content": "                'http_url_to_repo': 'http://example2.com',",
                                "type": "context"
                            },
                            {
                                "content": "                'ssh_url_to_repo': 'example2.com'",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/distributed/tests/test_create_sibling_gitlab.py",
                            "type": "function",
                            "name": "test_fake_gitlab",
                            "content": "def test_fake_gitlab(path=None):\n    from unittest.mock import patch\n    ds = Dataset(path).create()\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='dummy', project='here', description='thisisit')\n        assert_result_count(res, 2)\n        # GitLab success\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='dummy', sibling='dummy', project='here', description='thisisit',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': 'thisisit'\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='dummy',\n            url='http://example.com', status='ok')\n\n    # test sibling name conflicts\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='notneeded',\n            type='dataset'\n            )\n    # sibling name conflict with existing='error' should yiel error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='error',\n            type='dataset'\n            )\n    # try recreation, the sibling is already configured, same setup, no error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='reconfigure')\n        assert_result_count(\n        res, 1, action='configure-sibling', path=path, name='dummy',\n        url='http://example.com', status='ok')\n        # but error when the name differs\n        res = ds.create_sibling_gitlab(\n            site='dummy', project='here', name='othername', on_failure='ignore')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path,\n            site='dummy', sibling='othername', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com'\n            },\n            status='error')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _CreateFailureGitLab):\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='dummy', project='here', on_failure='ignore')\n        )\n\n    # new sibling, ssh access\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here', access='ssh')\n        assert_result_count(res, 2)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='sshsite', sibling='sshsite', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': None\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='sshsite',\n            url='example.com', status='ok')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectOtherURLGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here',\n                                       access='ssh', on_failure='ignore',\n                                       name='sshsite2')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=[\"There is already a project at '%s' on site '%s', \"\n                     \"but no sibling with name '%s' is configured, \"\n                     \"maybe use --existing=reconfigure\", \"here\", \"sshsite\",\n                     \"sshsite2\"],\n            path=path,\n            refds=path,\n            site='sshsite', sibling='sshsite2', project='here',\n            project_attributes={\n                'http_url_to_repo': 'http://example2.com',\n                'ssh_url_to_repo': 'example2.com'\n            },\n            status='error',\n            type='dataset')\n        # same goes for switching the access type without --reconfigure\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='sshsite', project='here',\n                                     access='http', on_failure='ignore')\n        )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 361,
                            "end_lineno": 479,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "0770727d31363d20175a30adb9ffe5c92b763bc8^",
    "new_commit_hash": "0770727d31363d20175a30adb9ffe5c92b763bc8",
    "commit_message": "Merge remote-tracking branch 'gh-jsheunis/gitlab-hierarchy-fix' into gitlab-hierarchy-fix",
    "commit_date": "2023-06-06T12:59:49+02:00",
    "metadata": {}
}
{
    "repo_name": "datalad",
    "new_commit_hash": "0770727d31363d20175a30adb9ffe5c92b763bc8",
    "test_file_codes": [
        "# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test create publication target on gitlab\"\"\"\n\nimport os\n\n# this must import ok with and without gitlab\nfrom datalad.api import (\n    Dataset,\n    create,\n    create_sibling_gitlab,\n)\nfrom datalad.tests.utils_pytest import (\n    assert_raises,\n    assert_repo_status,\n    assert_result_count,\n    assert_status,\n    eq_,\n    with_tempfile,\n)\nfrom datalad.utils import chpwd\n\n\ndef _get_nested_collections(path):\n    ds = Dataset(path).create()\n    c1 = ds.create(ds.pathobj / 'subdir' / 'collection1')\n    c1s1 = c1.create('sub1')\n    c1s2 = c1.create('sub2')\n    c2 = ds.create('collection2')\n    c2s1 = c2.create('sub1')\n    c2s11 = c2s1.create('deepsub1')\n    ds.save(recursive=True)\n    assert_repo_status(ds.path)\n    # return a catalog\n    return dict(\n        root=ds,\n        c1=c1,\n        c1s1=c1s1,\n        c1s2=c1s2,\n        c2=c2,\n        c2s1=c2s1,\n        c2s11=c2s11,\n    )\n\n\n# doesn't actually need gitlab and exercises most of the decision logic\n@with_tempfile\ndef test_dryrun(path=None):\n    ctlg = _get_nested_collections(path)\n    # no site config -> error\n    assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n    # wrong path specification -> impossible result\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy', path='imaghost'\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                          status='impossible')\n    # single project vs multi-dataset call\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', recursive=True)\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab,\n        site='site', project='one', path=['one', 'two'])\n    # explicit cite, no path constraints, fails for lack of project path config\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='dummy',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='error',\n        site='dummy', sibling='dummy',\n    )\n    # now a working, fully manual call\n    for p in (None, ctlg['root'].path):\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', project='here',\n            path=p,\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n            site='dummy', sibling='dummy', project='here/project',\n        )\n\n    # now configure a default gitlab site\n    ctlg['root'].config.set('datalad.gitlab-default-site', 'theone')\n    # we don't need to specify one anymore, but we can still customize\n    # the sibling name\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        name='ursula', project='here',\n    )\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='ursula', project='here/project',\n    )\n    # now configure a sibling name for this site\n    ctlg['root'].config.set('datalad.gitlab-theone-siblingname', 'dieter')\n    # and another one for another site\n    ctlg['root'].config.set('datalad.gitlab-otherone-siblingname', 'ulf')\n    # no need to specific 'name' anymore\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='here/project',\n    )\n    # properly switches the name based on site\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        site='otherone', project='here',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='otherone', sibling='ulf', project='here/project',\n    )\n    # reports notneeded on existing='skip' with an existing remote\n    ctlg['root'].repo.add_remote('dieter', 'http://example.com')\n    res = ctlg['root'].create_sibling_gitlab(\n        dry_run=True, on_failure='ignore',\n        project='here', existing='skip',\n    )\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='notneeded',\n        site='theone', sibling='dieter',\n    )\n    ctlg['root'].repo.remove_remote('dieter')\n\n    # lastly, configure a project path\n    ctlg['root'].config.set('datalad.gitlab-theone-project', 'secret')\n    # now we can drive it blind\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter', project='secret/project',\n    )\n    # we can make use of the config in the base dataset to drive\n    # calls on subdatasets: use -d plus a path\n    res = ctlg['root'].create_sibling_gitlab(path='subdir', dry_run=True)\n    # only a single result, doesn't touch the parent\n    assert_result_count(res, 1)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        site='theone', sibling='dieter',\n        # hierarchical setup: directories becomes groups\n        # which implies each dataset is in its own group\n        # project itself is placed at 'project' to give URLs like\n        # http://site/dir/dir/dir/project.git\n        # as a balance between readability and name conflict minimization\n        project='secret/{}/project'.format(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),\n    )\n    # we get the same result with an explicit layout request\n    expl_res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', layout='hierarchy', dry_run=True)\n    eq_(res, expl_res)\n    # layout can be configured too, \"collection\" is \"flat\" in a group\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'collection')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/group/dir-dir-dir-name.git\n        project='secret/{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n    # make sure the reference dataset does not conflict with its group in this\n    # case\n    res = ctlg['root'].create_sibling_gitlab(dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n        project='secret/project')\n    # \"flat\" does GitHub-style\n    ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')\n    res = ctlg['root'].create_sibling_gitlab(\n        path='subdir', dry_run=True)\n    assert_result_count(\n        res, 1, path=ctlg['c1'].path, type='dataset', status='ok',\n        # http://site/base-dir-dir-dir-name.git\n        project='secret-{}'.format(str(\n            ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(\n                os.sep, '-')),\n    )\n\n    # the results do not depend on explicitly given datasets, if we just enter\n    # the parent dataset we get the same results\n    with chpwd(str(ctlg['root'].pathobj / 'subdir')):\n        rel_res = create_sibling_gitlab(path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n    # and again the same results if we are in a subdataset and point to a parent\n    # dataset as a reference and config provider\n    with chpwd(ctlg['c1'].path):\n        rel_res = create_sibling_gitlab(\n            dataset=ctlg['root'].path, path=os.curdir, dry_run=True)\n        eq_(res, rel_res)\n\n    # blows on unknown layout\n    ctlg['root'].config.unset('datalad.gitlab-theone-layout')\n    assert_raises(\n        ValueError,\n        ctlg['root'].create_sibling_gitlab, layout='funny', dry_run=True)\n\n    # and finally recursion\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    # one result per dataset\n    assert_result_count(res, len(ctlg))\n    # verbose check of target layout (easier to see target pattern for humans)\n    # default layout: hierarchy\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/project',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/project',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/project',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    # test that the configurations work\n    ctlg['root'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-pathseparator\", '+')\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='flat', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret',\n            'secret-collection2',\n            'secret-collection2-sub1',\n            'secret-collection2-sub1-deepsub1',\n            'secret-subdir+collection1+sub1',\n            'secret-subdir-collection1',\n            'secret-subdir-collection1-sub2',\n        ],\n    )\n    res = ctlg['root'].create_sibling_gitlab(\n        recursive=True, layout='collection', dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2',\n            'secret/collection2-sub1',\n            'secret/collection2-sub1-deepsub1',\n            'secret/myownname',\n            'secret/subdir+collection1+sub1',\n            'secret/subdir-collection1',\n            'secret/subdir-collection1-sub2',\n        ],\n    )\n    ctlg['c1s1'].config.set(\"datalad.gitlab-default-projectname\", 'myownname')\n    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)\n    assert_result_count(res, len(ctlg))\n    eq_(\n        sorted(r['project'] for r in res),\n        [\n            'secret/collection2/project',\n            'secret/collection2/sub1/deepsub1/project',\n            'secret/collection2/sub1/project',\n            'secret/myownname',\n            'secret/subdir/collection1/project',\n            'secret/subdir/collection1/sub1/myownname',\n            'secret/subdir/collection1/sub2/project',\n        ]\n    )\n\n\nclass _FakeGitLab(object):\n    def __init__(self, site):\n        pass\n\n\nclass _NewProjectGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return None\n\n    def create_project(self, path, description=None):\n        return dict(\n            http_url_to_repo='http://example.com',\n            ssh_url_to_repo='example.com',\n            description=description,\n        )\n\n\nclass _ExistingProjectGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return dict(\n            http_url_to_repo='http://example.com',\n            ssh_url_to_repo='example.com',\n        )\n\n\nclass _ExistingProjectOtherURLGitLab(_FakeGitLab):\n    def get_project(self, path):\n        return dict(\n            http_url_to_repo='http://example2.com',\n            ssh_url_to_repo='example2.com',\n        )\n\n\nclass _CreateFailureGitLab(_FakeGitLab):\n    def get_project(self, path):\n        None\n\n    def create_project(self, path, description=None):\n        raise RuntimeError\n\n\n@with_tempfile\ndef test_fake_gitlab(path=None):\n    from unittest.mock import patch\n    ds = Dataset(path).create()\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='dummy', project='here', description='thisisit')\n        assert_result_count(res, 2)\n        # GitLab success\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='dummy', sibling='dummy', project='here/project', description='thisisit',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': 'thisisit'\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='dummy',\n            url='http://example.com', status='ok')\n\n    # test sibling name conflicts\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='notneeded',\n            type='dataset'\n            )\n    # sibling name conflict with existing='error' should yiel error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='skip')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=['already has a configured sibling \"%s\"', \"dummy\"],\n            path=path,\n            refds=path,\n            site='dummy', sibling='dummy',\n            status='error',\n            type='dataset'\n            )\n    # try recreation, the sibling is already configured, same setup, no error\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectGitLab):\n        res = ds.create_sibling_gitlab(path=ds.path, site='dummy',\n                                       project='here', existing='reconfigure')\n        assert_result_count(\n        res, 1, action='configure-sibling', path=path, name='dummy',\n        url='http://example.com', status='ok')\n        # but error when the name differs\n        res = ds.create_sibling_gitlab(\n            site='dummy', project='here', name='othername', on_failure='ignore')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path,\n            site='dummy', sibling='othername', project='here/project',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com'\n            },\n            status='error')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _CreateFailureGitLab):\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='dummy', project='here', on_failure='ignore')\n        )\n\n    # new sibling, ssh access\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here', access='ssh')\n        assert_result_count(res, 2)\n        assert_result_count(\n            res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n            site='sshsite', sibling='sshsite', project='here/project',\n            project_attributes={\n                'http_url_to_repo': 'http://example.com',\n                'ssh_url_to_repo': 'example.com',\n                'description': None\n            },\n            status='ok')\n        assert_result_count(\n            res, 1, action='configure-sibling', path=path, name='sshsite',\n            url='example.com', status='ok')\n\n    with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\",\n               _ExistingProjectOtherURLGitLab):\n        res = ds.create_sibling_gitlab(site='sshsite', project='here',\n                                       access='ssh', on_failure='ignore',\n                                       name='sshsite2')\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 0, action='create_sibling_gitlab',\n            message=[\"There is already a project at '%s' on site '%s', \"\n                     \"but no sibling with name '%s' is configured, \"\n                     \"maybe use --existing=reconfigure\", \"here\", \"sshsite\",\n                     \"sshsite2\"],\n            path=path,\n            refds=path,\n            site='sshsite', sibling='sshsite2', project='here/project',\n            project_attributes={\n                'http_url_to_repo': 'http://example2.com',\n                'ssh_url_to_repo': 'example2.com'\n            },\n            status='error',\n            type='dataset')\n        # same goes for switching the access type without --reconfigure\n        assert_status(\n            'error',\n            ds.create_sibling_gitlab(site='sshsite', project='here',\n                                     access='http', on_failure='ignore')\n        )\n",
        "import pytest\nimport tempfile\nimport os\n\n\n@pytest.fixture\ndef path():\n    with tempfile.TemporaryDirectory() as tmpdir:\n        yield tmpdir\n\n\n@pytest.fixture(autouse=True)\ndef setup_git_config():\n    with tempfile.TemporaryDirectory() as home:\n        old_home = os.environ.get(\"HOME\")\n        os.environ[\"HOME\"] = home\n        os.system('git config --global user.name \"DataLad Tester\"')\n        os.system('git config --global user.email \"test@example.com\"')\n        yield\n        if old_home:\n            os.environ[\"HOME\"] = old_home\n"
    ],
    "test_file_names": [
        "test_1.py",
        "conftest.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Datalad installation attempts...\nVerifying installation...\nDatalad version: 0.18.4+50.g0770727d3\n✅ Installation successful!\nSuccessfully installed Datalad using Python 3.9\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 25 packages in 124ms\nInstalled 25 packages in 3.84s\n + coverage==7.6.10\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + grabbit==0.2.6\n + importlib-resources==6.4.5\n + iniconfig==2.0.0\n + nibabel==5.3.2\n + num2words==0.5.14\n + numpy==2.0.2\n + packaging==24.2\n + pandas==2.2.3\n + patsy==1.0.1\n + pluggy==1.5.0\n + pybids==0.6.5\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.13.1\n + setuptools==75.6.0\n + six==1.17.0\n + tomli==2.2.1\n + typing-extensions==4.12.2\n + tzdata==2024.2\n + zipp==3.21.0\nResolved 65 packages in 4.76s\nPrepared 1 package in 4.02s\nInstalled 53 packages in 412ms\n + annexremote==1.6.6\n + argcomplete==3.5.3\n + backports-tarfile==1.2.0\n + beautifulsoup4==4.12.3\n + boto==2.49.0\n + certifi==2024.12.14\n + cffi==1.17.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + citeproc-py==0.6.0\n + cryptography==44.0.0\n + datalad==0.18.4+50.g0770727d3 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_0770727d31363d20175a30adb9ffe5c92b763bc8)\n + distro==1.9.0\n + duecredit==0.10.2\n + fasteners==0.19\n + httpretty==1.1.4\n + humanize==4.11.0\n + idna==3.10\n + importlib-metadata==8.5.0\n + iso8601==2.1.0\n + jaraco-classes==3.4.0\n + jaraco-context==6.0.1\n + jaraco-functools==4.1.0\n + jeepney==0.8.0\n + keyring==25.6.0\n + keyrings-alt==5.0.2\n + looseversion==1.3.0\n + lxml==5.3.0\n + more-itertools==10.5.0\n + msgpack==1.1.0\n + multidict==6.1.0\n + mypy==1.14.1\n + mypy-extensions==1.0.0\n + patool==1.15.0\n + platformdirs==4.3.6\n + propcache==0.2.1\n + pycparser==2.22\n + pyperclip==1.9.0\n + pytest-fail-slow==0.6.0\n + python-gitlab==5.3.0\n + pyyaml==6.0.2\n + requests==2.32.3\n + requests-ftp==0.3.1\n + requests-toolbelt==1.0.0\n + secretstorage==3.3.3\n + soupsieve==2.6\n + tqdm==4.67.1\n + types-python-dateutil==2.9.0.20241206\n + types-requests==2.32.0.20241016\n + urllib3==2.3.0\n + vcrpy==4.3.0\n + wrapt==1.17.0\n + yarl==1.18.3\nResolved 103 packages in 332ms\nInstalled 38 packages in 1.06s\n + alabaster==0.7.16\n + asv==0.6.4\n + asv-runner==0.2.1\n + attrs==24.3.0\n + babel==2.16.0\n + build==1.2.2.post1\n + click==8.1.8\n + click-log==0.4.0\n + distlib==0.3.9\n + docutils==0.21.2\n + execnet==2.1.1\n + filelock==3.16.1\n + gprof2dot==2024.6.6\n + imagesize==1.4.1\n + jinja2==3.1.5\n + json5==0.10.0\n + markdown-it-py==3.0.0\n + markupsafe==3.0.2\n + mdurl==0.1.2\n + psutil==6.1.1\n + pygments==2.18.0\n + pympler==1.1\n + pypandoc==1.14\n + pyproject-hooks==1.2.0\n + pytest-xdist==3.6.1\n + scriv==1.5.1\n + snowballstemmer==2.2.0\n + sphinx==7.4.7\n + sphinx-rtd-theme==3.0.2\n + sphinxcontrib-applehelp==2.0.0\n + sphinxcontrib-devhelp==2.0.0\n + sphinxcontrib-htmlhelp==2.1.0\n + sphinxcontrib-jquery==4.1\n + sphinxcontrib-jsmath==1.0.1\n + sphinxcontrib-qthelp==2.0.0\n + sphinxcontrib-serializinghtml==2.0.0\n + tabulate==0.9.0\n + virtualenv==20.28.0\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_0770727d31363d20175a30adb9ffe5c92b763bc8\nconfigfile: tox.ini\nplugins: fail-slow-0.6.0, xdist-3.6.1, cov-6.0.0\ncollected 2 items\n\nr2e_tests/test_1.py ..                                                   [100%]\n\n==================================== PASSES ====================================\n_________________________________ test_dryrun __________________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): subdir/collection1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subdir/collection1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): sub1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): sub2 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub2 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): collection2 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): collection2 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): sub1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): deepsub1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): deepsub1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): sub1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): collection2 (dataset)\nadd(ok): collection2 (dataset)\nadd(ok): subdir/collection1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 5)\n  save (notneeded: 5, ok: 2)\ncreate_sibling_gitlab(impossible): imaghost (dataset) [No dataset found under /tmp/datalad_temp_test_dryrunob5cgv1s/imaghost]\ncreate_sibling_gitlab(error): . (dataset) [No project name/location specified, and no configuration to derive one]\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): collection2 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1/deepsub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub2 (dataset)\naction summary:\n  create_sibling_gitlab (ok: 7)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): collection2 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1/deepsub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub2 (dataset)\naction summary:\n  create_sibling_gitlab (ok: 7)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): collection2 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1/deepsub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub2 (dataset)\naction summary:\n  create_sibling_gitlab (ok: 7)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): collection2 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1/deepsub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub2 (dataset)\naction summary:\n  create_sibling_gitlab (ok: 7)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): collection2 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1/deepsub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub2 (dataset)\naction summary:\n  create_sibling_gitlab (ok: 7)\ncreate_sibling_gitlab(ok): . (dataset)\ncreate_sibling_gitlab(ok): collection2 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1 (dataset)\ncreate_sibling_gitlab(ok): collection2/sub1/deepsub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub1 (dataset)\ncreate_sibling_gitlab(ok): subdir/collection1/sub2 (dataset)\naction summary:\n  create_sibling_gitlab (ok: 7)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/subdir/collection1\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/subdir/collection1\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/collection2\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/collection2/sub1\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/subdir/collection1/sub2\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/subdir/collection1/sub1\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/subdir/collection1\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/collection2/sub1/deepsub1\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/collection2/sub1\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s/collection2\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunob5cgv1s\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_______________________________ test_fake_gitlab _______________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ncreate_sibling_gitlab(ok): . (dataset) [sibling repository 'dummy' created at None]\nconfigure-sibling(ok): . (sibling)\naction summary:\n  configure-sibling (ok: 1)\n  create_sibling_gitlab (ok: 1)\nconfigure-sibling(ok): . (sibling)\naction summary:\n  configure-sibling (ok: 1)\n  create_sibling_gitlab (notneeded: 1)\ncreate_sibling_gitlab(error): . (dataset) [There is already a project at 'here/project' on site 'dummy', but no sibling with name 'othername' is configured, maybe use --existing=reconfigure]\ncreate_sibling_gitlab(error): . (dataset) [already has a configured sibling \"dummy\"]\ncreate_sibling_gitlab(ok): . (dataset) [sibling repository 'sshsite' created at None]\nconfigure-sibling(ok): . (sibling)\naction summary:\n  configure-sibling (ok: 1)\n  create_sibling_gitlab (ok: 1)\ncreate_sibling_gitlab(error): . (dataset) [There is already a project at 'here/project' on site 'sshsite', but no sibling with name 'sshsite2' is configured, maybe use --existing=reconfigure]\ncreate_sibling_gitlab(error): . (dataset) [already has a configured sibling \"sshsite\"]\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_dryrun\nPASSED r2e_tests/test_1.py::test_fake_gitlab\n============================== 2 passed in 11.69s ==============================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_0770727d31363d20175a30adb9ffe5c92b763bc8\nconfigfile: tox.ini\nplugins: fail-slow-0.6.0, xdist-3.6.1, cov-6.0.0\ncollected 2 items\n\nr2e_tests/test_1.py FF                                                   [100%]\n\n=================================== FAILURES ===================================\n_________________________________ test_dryrun __________________________________\n\npath = '/tmp/datalad_temp_test_dryrunen_xcio2'\n\n    @with_tempfile\n    def test_dryrun(path=None):\n        ctlg = _get_nested_collections(path)\n        # no site config -> error\n        assert_raises(ValueError, ctlg['root'].create_sibling_gitlab)\n        # wrong path specification -> impossible result\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy', path='imaghost'\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].pathobj / 'imaghost', type='dataset',\n                              status='impossible')\n        # single project vs multi-dataset call\n        assert_raises(\n            ValueError,\n            ctlg['root'].create_sibling_gitlab,\n            site='site', project='one', recursive=True)\n        assert_raises(\n            ValueError,\n            ctlg['root'].create_sibling_gitlab,\n            site='site', project='one', path=['one', 'two'])\n        # explicit cite, no path constraints, fails for lack of project path config\n        res = ctlg['root'].create_sibling_gitlab(\n            dry_run=True, on_failure='ignore',\n            site='dummy',\n        )\n        assert_result_count(res, 1)\n        assert_result_count(\n            res, 1, path=ctlg['root'].path, type='dataset', status='error',\n            site='dummy', sibling='dummy',\n        )\n        # now a working, fully manual call\n        for p in (None, ctlg['root'].path):\n            res = ctlg['root'].create_sibling_gitlab(\n                dry_run=True, on_failure='ignore',\n                site='dummy', project='here',\n                path=p,\n            )\n            assert_result_count(res, 1)\n>           assert_result_count(\n                res, 1, path=ctlg['root'].path, type='dataset', status='ok',\n                site='dummy', sibling='dummy', project='here/project',\n            )\n\nr2e_tests/test_1.py:93: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nresults = [{'action': 'create_sibling_gitlab', 'dryrun': True, 'path': '/tmp/datalad_temp_test_dryrunen_xcio2', 'project': 'here', ...}]\nn = 1\nkwargs = {'path': '/tmp/datalad_temp_test_dryrunen_xcio2', 'project': 'here/project', 'sibling': 'dummy', 'site': 'dummy', ...}\ncount = 0\n\n    def assert_result_count(results, n, **kwargs):\n        \"\"\"Verify specific number of results (matching criteria, if any)\"\"\"\n        count = 0\n        results = ensure_result_list(results)\n        for r in results:\n            if not len(kwargs):\n                count += 1\n            elif all(k in r and r[k] == v for k, v in kwargs.items()):\n                count += 1\n        if not n == count:\n>           raise AssertionError(\n                'Got {} instead of {} expected results matching\\n{}\\nInspected {} record(s):\\n{}'.format(\n                    count,\n                    n,\n                    _format_res(kwargs),\n                    len(results),\n                    _format_res(results)))\nE           AssertionError: Got 0 instead of 1 expected results matching\nE             {\nE              \"path\": \"/tmp/datalad_temp_test_dryrunen_xcio2\",\nE              \"project\": \"here/project\",\nE              \"sibling\": \"dummy\",\nE              \"site\": \"dummy\",\nE              \"status\": \"ok\",\nE              \"type\": \"dataset\"\nE             }\nE           Inspected 1 record(s):\nE             [\nE              {\nE               \"action\": \"create_sibling_gitlab\",\nE               \"dryrun\": true,\nE               \"path\": \"/tmp/datalad_temp_test_dryrunen_xcio2\",\nE               \"project\": \"here\",\nE               \"refds\": \"/tmp/datalad_temp_test_dryrunen_xcio2\",\nE               \"sibling\": \"dummy\",\nE               \"site\": \"dummy\",\nE               \"status\": \"ok\",\nE               \"type\": \"dataset\"\nE              }\nE             ]\n\ndatalad/tests/utils_pytest.py:1366: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nadd(ok): subdir/collection1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): subdir/collection1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): sub1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): sub2 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub2 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): collection2 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): collection2 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): sub1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): sub1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): deepsub1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\ncreate(ok): deepsub1 (dataset)\naction summary:\n  add (ok: 2)\n  create (ok: 1)\n  save (ok: 1)\nadd(ok): sub1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): collection2 (dataset)\nadd(ok): collection2 (dataset)\nadd(ok): subdir/collection1 (dataset)\nadd(ok): .gitmodules (file)\nsave(ok): . (dataset)\naction summary:\n  add (ok: 5)\n  save (notneeded: 5, ok: 2)\ncreate_sibling_gitlab(impossible): imaghost (dataset) [No dataset found under /tmp/datalad_temp_test_dryrunen_xcio2/imaghost]\ncreate_sibling_gitlab(error): . (dataset) [No project name/location specified, and no configuration to derive one]\ncreate_sibling_gitlab(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/subdir/collection1\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/subdir/collection1\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/collection2\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/collection2/sub1\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: starting\nINFO     datalad.core.local.save:log.py:432 \nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/subdir/collection1/sub2\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/subdir/collection1/sub1\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/subdir/collection1\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/collection2/sub1/deepsub1\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/collection2/sub1\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2/collection2\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: processed result for /tmp/datalad_temp_test_dryrunen_xcio2\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.save:log.py:432 Total: done\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_______________________________ test_fake_gitlab _______________________________\n\npath = '/tmp/datalad_temp_test_fake_gitlabxq8xlz7n'\n\n    @with_tempfile\n    def test_fake_gitlab(path=None):\n        from unittest.mock import patch\n        ds = Dataset(path).create()\n        with patch(\"datalad.distributed.create_sibling_gitlab.GitLabSite\", _NewProjectGitLab):\n            res = ds.create_sibling_gitlab(site='dummy', project='here', description='thisisit')\n            assert_result_count(res, 2)\n            # GitLab success\n>           assert_result_count(\n                res, 1, action='create_sibling_gitlab', path=path, type='dataset',\n                site='dummy', sibling='dummy', project='here/project', description='thisisit',\n                project_attributes={\n                    'http_url_to_repo': 'http://example.com',\n                    'ssh_url_to_repo': 'example.com',\n                    'description': 'thisisit'\n                },\n                status='ok')\n\nr2e_tests/test_1.py:367: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nresults = [{'action': 'create_sibling_gitlab', 'description': 'thisisit', 'message': (\"sibling repository '%s' created at %s\", '...tion': 'configure-sibling', 'annex-ignore': True, 'fetch': '+refs/heads/*:refs/remotes/dummy/*', 'name': 'dummy', ...}]\nn = 1\nkwargs = {'action': 'create_sibling_gitlab', 'description': 'thisisit', 'path': '/tmp/datalad_temp_test_fake_gitlabxq8xlz7n', 'project': 'here/project', ...}\ncount = 0\n\n    def assert_result_count(results, n, **kwargs):\n        \"\"\"Verify specific number of results (matching criteria, if any)\"\"\"\n        count = 0\n        results = ensure_result_list(results)\n        for r in results:\n            if not len(kwargs):\n                count += 1\n            elif all(k in r and r[k] == v for k, v in kwargs.items()):\n                count += 1\n        if not n == count:\n>           raise AssertionError(\n                'Got {} instead of {} expected results matching\\n{}\\nInspected {} record(s):\\n{}'.format(\n                    count,\n                    n,\n                    _format_res(kwargs),\n                    len(results),\n                    _format_res(results)))\nE           AssertionError: Got 0 instead of 1 expected results matching\nE             {\nE              \"action\": \"create_sibling_gitlab\",\nE              \"description\": \"thisisit\",\nE              \"path\": \"/tmp/datalad_temp_test_fake_gitlabxq8xlz7n\",\nE              \"project\": \"here/project\",\nE              \"project_attributes\": {\nE               \"description\": \"thisisit\",\nE               \"http_url_to_repo\": \"http://example.com\",\nE               \"ssh_url_to_repo\": \"example.com\"\nE              },\nE              \"sibling\": \"dummy\",\nE              \"site\": \"dummy\",\nE              \"status\": \"ok\",\nE              \"type\": \"dataset\"\nE             }\nE           Inspected 2 record(s):\nE             [\nE              {\nE               \"action\": \"create_sibling_gitlab\",\nE               \"description\": \"thisisit\",\nE               \"message\": [\nE                \"sibling repository '%s' created at %s\",\nE                \"dummy\",\nE                null\nE               ],\nE               \"path\": \"/tmp/datalad_temp_test_fake_gitlabxq8xlz7n\",\nE               \"project\": \"here\",\nE               \"project_attributes\": {\nE                \"description\": \"thisisit\",\nE                \"http_url_to_repo\": \"http://example.com\",\nE                \"ssh_url_to_repo\": \"example.com\"\nE               },\nE               \"refds\": \"/tmp/datalad_temp_test_fake_gitlabxq8xlz7n\",\nE               \"sibling\": \"dummy\",\nE               \"site\": \"dummy\",\nE               \"status\": \"ok\",\nE               \"type\": \"dataset\"\nE              },\nE              {\nE               \"action\": \"configure-sibling\",\nE               \"annex-ignore\": true,\nE               \"fetch\": \"+refs/heads/*:refs/remotes/dummy/*\",\nE               \"name\": \"dummy\",\nE               \"path\": \"/tmp/datalad_temp_test_fake_gitlabxq8xlz7n\",\nE               \"refds\": \"/tmp/datalad_temp_test_fake_gitlabxq8xlz7n\",\nE               \"status\": \"ok\",\nE               \"type\": \"sibling\",\nE               \"url\": \"http://example.com\"\nE              }\nE             ]\n\ndatalad/tests/utils_pytest.py:1366: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\ncreate_sibling_gitlab(ok): . (dataset) [sibling repository 'dummy' created at None]\nconfigure-sibling(ok): . (sibling)\naction summary:\n  configure-sibling (ok: 1)\n  create_sibling_gitlab (ok: 1)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3943 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n=========================== short test summary info ============================\nFAILED r2e_tests/test_1.py::test_dryrun - AssertionError: Got 0 instead of 1 ...\nFAILED r2e_tests/test_1.py::test_fake_gitlab - AssertionError: Got 0 instead ...\n============================== 2 failed in 5.91s ===============================\n",
    "old_commit_res_stderr": ""
}
datalad/distributed/create_sibling_gitlab.py datalad/distributed/tests/test_create_sibling_gitlab.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Incorrect Project Path Construction with Non-Flat Layout in `create_sibling_gitlab`

**Description:**
When creating a GitLab sibling with a non-flat layout, the project path is not constructed correctly. Specifically, the `project` field does not include the necessary hierarchy, leading to incorrect project paths.

**Buggy Code Example:**
```python
# Attempting to create a GitLab sibling with a hierarchical layout
result = create_sibling_gitlab(
    site='dummy',
    sibling='dummy',
    project='here',
    layout='collection',
    dry_run=True
)

print(result['project'])  # Expected: 'here/project', but got 'here'
```

**Expected Behavior:**
When using a hierarchical layout (e.g., `layout='collection'`), the `project` field should include the hierarchy, resulting in paths like `'here/project'`. This ensures that the projects are correctly organized within the GitLab hierarchy.

**Actual Behavior:**
The `project` field is incorrectly set to `'here'` without appending `'/project'`, resulting in an incorrect project path. This leads to inconsistencies and causes related operations to fail.

**Steps to Reproduce:**
1. Configure the project to use a non-flat layout (e.g., `layout='collection'`).
2. Attempt to create a GitLab sibling using the `create_sibling_gitlab` function with the specified layout.
3. Observe that the `project` field does not include the expected hierarchical path.

**Impact:**
This issue prevents proper organization of projects within GitLab when using hierarchical layouts, potentially causing configuration errors and workflow disruptions for users relying on structured project paths.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/distributed/create_sibling_gitlab.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""High-level interface for creating a publication target on a GitLab instance
"""

__docformat__ = 'restructuredtext'


import logging
import warnings

from datalad.support.exceptions import CapturedException

from ..distribution.dataset import (
    EnsureDataset,
    datasetmethod,
    require_dataset,
    resolve_path,
)
# bound methods
from ..distribution.siblings import Siblings
from ..dochelpers import exc_str
from ..interface.base import (
    Interface,
    build_doc,
    eval_results,
)
from ..interface.common_opts import (
    publish_depends,
    recursion_flag,
    recursion_limit,
)
from ..local.subdatasets import Subdatasets
from ..support.constraints import (
    EnsureChoice,
    EnsureNone,
    EnsureStr,
)
from ..support.param import Parameter
from ..utils import ensure_list

lgr = logging.getLogger('datalad.distributed.create_sibling_gitlab')

known_layout_labels = ('hierarchy', 'collection', 'flat')
known_access_labels = ('http', 'ssh', 'ssh+http')


@build_doc
class CreateSiblingGitlab(Interface):
    """Create dataset sibling at a GitLab site

    An existing GitLab project, or a project created via the GitLab web
    interface can be configured as a sibling with the :command:`siblings`
    command. Alternatively, this command can create a GitLab project at any
    location/path a given user has appropriate permissions for. This is
    particularly helpful for recursive sibling creation for subdatasets. API
    access and authentication are implemented via python-gitlab, and all its
    features are supported. A particular GitLab site must be configured in a
    named section of a python-gitlab.cfg file (see
    https://python-gitlab.readthedocs.io/en/stable/cli.html#configuration for
    details), such as::

      [mygit]
      url = https://git.example.com
      api_version = 4
      private_token = abcdefghijklmnopqrst

    Subsequently, this site is identified by its name ('mygit' in the example
    above).

    (Recursive) sibling creation for all, or a selected subset of subdatasets
    is supported with three different project layouts (see --layout):

    "hierarchy"
      Each dataset is placed into its own group, and the actual GitLab
      project for a dataset is put in a project named "project" inside
      this group. This project name is configurable (see Configuration).
      Using this layout, arbitrarily deep hierarchies of
      nested datasets can be represented, while the hierarchical structure
      is reflected in the project path. This is the default layout, if
      no project path is specified.
    "flat"
      All datasets are placed in the same group. The name of a project
      is its relative path within the root dataset, with all path separator
      characters replaced by '-'. This path separator is configurable
      (see Configuration).
    "collection"
      This is a hybrid layout, where the root dataset is placed in a "project"
      project inside a group, and all nested subdatasets are represented
      inside the group using a "flat" layout. The project name is configurable
      (see Configuration).

    GitLab cannot host dataset content. However, in combination with
    other data sources (and siblings), publishing a dataset to GitLab can
    facilitate distribution and exchange, while still allowing any dataset
    consumer to obtain actual data content from alternative sources.

    *Configuration*

    Many configuration switches and options for GitLab sibling creation can
    be provided arguments to the command. However, it is also possible to
    specify a particular setup in a dataset's configuration. This is
    particularly important when managing large collections of datasets.
    Configuration options are:

    "datalad.gitlab-default-site"
        Name of the default GitLab site (see --site)
    "datalad.gitlab-SITENAME-siblingname"
        Name of the sibling configured for the local dataset that points
        to the GitLab instance SITENAME (see --name)
    "datalad.gitlab-SITENAME-layout"
        Project layout used at the GitLab instance SITENAME (see --layout)
    "datalad.gitlab-SITENAME-access"
        Access method used for the GitLab instance SITENAME (see --access)
    "datalad.gitlab-SITENAME-project"
        Project location/path used for a datasets at GitLab instance
        SITENAME (see --project). Configuring this is useful for deriving
        project paths for subdatasets, relative to superdataset.
    "datalad.gitlab-default-projectname"
        The hierarchy and collection layouts publish (sub)datasets as projects
        with a custom name. The default name "project" can be overridden with
        this configuration.
    "datalad.gitlab-default-pathseparator"
        The flat and collection layout represent subdatasets with project names
        that correspond to the path, with the regular path separator replaced
        with a "-": superdataset-subdataset. This configuration can override
        this default separator.

    This command can be configured with
    "datalad.create-sibling-ghlike.extra-remote-settings.NETLOC.KEY=VALUE" in
    order to add any local KEY = VALUE configuration to the created sibling in
    the local `.git/config` file. NETLOC is the domain of the Gitlab instance to
    apply the configuration for.
    This leads to a behavior that is equivalent to calling datalad's
    ``siblings('configure', ...)``||``siblings configure`` command with the
    respective KEY-VALUE pair after creating the sibling.
    The configuration, like any other, could be set at user- or system level, so
    users do not need to add this configuration to every sibling created with
    the service at NETLOC themselves.

    """
    _params_ = dict(
        path=Parameter(
            args=('path',),
            metavar='PATH',
            nargs='*',
            doc="""selectively create siblings for any datasets underneath a given
            path. By default only the root dataset is considered."""),
        dataset=Parameter(
            args=("--dataset", "-d",),
            doc="""reference or root dataset. If no path constraints are given,
            a sibling for this dataset will be created. In this and all other
            cases, the reference dataset is also consulted for the GitLab
            configuration, and desired project layout. If no dataset is given,
            an attempt is made to identify the dataset based on the current
            working directory""",
            constraints=EnsureDataset() | EnsureNone()),
        site=Parameter(
            args=('--site',),
            metavar='SITENAME',
            doc="""name of the GitLab site to create a sibling at. Must match an
            existing python-gitlab configuration section with location and
            authentication settings (see
            https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#configuration).
            By default the dataset configuration is consulted.
            """,
            constraints=EnsureNone() | EnsureStr()),
        project=Parameter(
            args=('--project',),
            metavar='NAME/LOCATION',
            doc="""project name/location at the GitLab site. If a subdataset of the
            reference dataset is processed, its project path is automatically
            determined by the `layout` configuration, by default.
            """,
            constraints=EnsureNone() | EnsureStr()),
        layout=Parameter(
            args=('--layout',),
            constraints=EnsureChoice(None, *known_layout_labels),
            doc="""layout of projects at the GitLab site, if a collection, or
            a hierarchy of datasets and subdatasets is to be created.
            By default the dataset configuration is consulted.
            """),
        recursive=recursion_flag,
        recursion_limit=recursion_limit,
        name=Parameter(
            args=('-s', '--name',),
            metavar='NAME',
            doc="""name to represent the GitLab sibling remote in the local
            dataset installation. If not specified a name is looked up in the
            dataset configuration, or defaults to the `site` name""",
            constraints=EnsureStr() | EnsureNone()),
        existing=Parameter(
            args=("--existing",),
            constraints=EnsureChoice('skip', 'error', 'reconfigure'),
            doc="""desired behavior when already existing or configured
            siblings are discovered. 'skip': ignore; 'error': fail, if access
            URLs differ; 'reconfigure': use the existing repository and
            reconfigure the local dataset to use it as a sibling""",),
        access=Parameter(
            args=("--access",),
            constraints=EnsureChoice(None, *known_access_labels),
            doc="""access method used for data transfer to and from the sibling.
            'ssh': read and write access used the SSH protocol; 'http': read and
            write access use HTTP requests; 'ssh+http': read access is done via
            HTTP and write access performed with SSH. Dataset configuration is
            consulted for a default, 'http' is used otherwise.""",),
        description=Parameter(
            args=("--description",),
            doc="""brief description for the GitLab project (displayed on the
            site)""",
            constraints=EnsureStr() | EnsureNone()),
        publish_depends=publish_depends,
        dry_run=Parameter(
            args=("--dry-run",),
            action="store_true",
            doc="""if set, no repository will be created, only tests for
            name collisions will be performed, and would-be repository names
            are reported for all relevant datasets"""),
        dryrun=Parameter(
            args=("--dryrun",),
            action="store_true",
            doc="""Deprecated. Use the renamed
            ``dry_run||--dry-run`` parameter""")
    )

    @staticmethod
    @datasetmethod(name='create_sibling_gitlab')
    @eval_results
    def __call__(
            path=None,
            *,
            site=None,
            project=None,
            layout=None,
            dataset=None,
            recursive=False,
            recursion_limit=None,
            name=None,
            existing='error',
            access=None,
            publish_depends=None,
            description=None,
            dryrun=False,
            dry_run=False):
        if dryrun and not dry_run:
            # the old one is used, and not in agreement with the new one
            warnings.warn(
                "datalad-create-sibling-github's `dryrun` option is "
                "deprecated and will be removed in a future release, "
                "use the renamed `dry_run/--dry-run` option instead.",
                DeprecationWarning)
            dry_run = dryrun
        path = resolve_path(ensure_list(path), ds=dataset) \
            if path else None

        if project and (recursive or (path and len(path) > 1)):
            raise ValueError(
                'Providing a GitLab project name/location cannot be combined '
                'with recursive operation or multiple paths, as each dataset '
                'needs to be mapped onto its own individual project.')
        # what to operate on
        ds = require_dataset(
            dataset, check_installed=True, purpose='create GitLab sibling(s)')

        # cache for objects of gitlab sites (we could face different ones
        # in a single hierarchy, cache them to avoid duplicate initialization
        # while still being able to process each dataset individually
        siteobjs = dict()

        # which datasets to process?
        if path is None or ds.pathobj in path:
            for r in _proc_dataset(
                    ds, ds,
                    site, project, name, layout, existing, access,
                    dry_run, siteobjs, publish_depends, description):
                yield r
        # we need to find a subdataset when recursing, or when there is a path that
        # could point to one, we have to exclude the parent dataset in this test
        # to avoid undesired level-1 recursion into subdatasets
        if any(p != ds.pathobj for p in (path or [])) or recursive:
            # also include any matching subdatasets
            subds = ds.subdatasets(
                    path=path,
                    # we can only operate on present datasets
                    state='present',
                    recursive=recursive,
                    recursion_limit=recursion_limit,
                    contains=None,
                    bottomup=False,
                    result_xfm='datasets',
                    result_renderer='disabled',
                    return_type='list')
            if not subds:
                # we didn't find anything to operate on, let the user know
                for p in path:
                    yield dict(
                          action='create_sibling_gitlab',
                          status='impossible',
                          refds=ds.path,
                          path=p,
                          message=('No dataset found under %s' % p),
                          type='dataset',
                          logger=lgr,
                    )
            else:
                for sub in subds:
                    for r in _proc_dataset(
                            ds, sub,
                            site, project, name, layout, existing, access,
                            dry_run, siteobjs, publish_depends, description):
                        yield r

        return


def _proc_dataset(refds, ds, site, project, remotename, layout, existing,
                  access, dry_run, siteobjs, depends, description):
    # basic result setup
    res_kwargs = dict(
        action='create_sibling_gitlab',
        refds=refds.path,
        path=ds.path,
        type='dataset',
        logger=lgr,
    )
    if description:
        res_kwargs['description'] = description

    if site is None:
        # always try pulling the base config from a parent dataset
        # even if paths were given (may be overwritten later)
        basecfgsite = ds.config.get('datalad.gitlab-default-site', None)

    # let the dataset config overwrite the target site, if none
    # was given
    site = refds.config.get(
        'datalad.gitlab-default-site', basecfgsite) \
        if site is None else site
    if site is None:
        # this means the most top-level dataset has no idea about
        # gitlab, and no site was specified as an argument
        # fail rather then give an error result, as this is very
        # unlikely to be intentional
        raise ValueError(
            'No GitLab site was specified (--site) or configured '
            'in {} (datalad.gitlab.default-site)'.format(ds))
    res_kwargs['site'] = site

    # determine target remote name, unless given
    if remotename is None:
        remotename_var = 'datalad.gitlab-{}-siblingname'.format(site)
        remotename = ds.config.get(
            remotename_var,
            # use config from parent, if needed
            refds.config.get(
                remotename_var,
                # fall back on site name, if nothing else can be used
                site))
    res_kwargs['sibling'] = remotename
    # check against existing remotes
    dremotes = {
        r['name']: r
        for r in ds.siblings(
            action='query',
            # fastest possible
            get_annex_info=False,
            recursive=False,
            return_type='generator',
            result_renderer='disabled')
    }
    if remotename in dremotes and existing not in ['replace', 'reconfigure']:
        # we already know a sibling with this name
        yield dict(
            res_kwargs,
            status='error' if existing == 'error' else 'notneeded',
            message=('already has a configured sibling "%s"', remotename),
        )
        return

    if layout is None:
        # figure out the layout of projects on the site
        # use the reference dataset as default, and fall back
        # on 'hierarchy' as the most generic method of representing
        # the filesystem in a group/subgroup structure
        layout_var = 'datalad.gitlab-{}-layout'.format(site)
        layout = ds.config.get(
            layout_var, refds.config.get(
                layout_var, 'hierarchy'))
    if layout not in known_layout_labels:
        raise ValueError(
            "Unknown site layout '{}' given or configured, "
            "known ones are: {}".format(layout, known_layout_labels))

    if access is None:
        access_var = 'datalad.gitlab-{}-access'.format(site)
        access = ds.config.get(
            access_var, refds.config.get(
                access_var, 'http'))
    if access not in known_access_labels:
        raise ValueError(
            "Unknown site access '{}' given or configured, "
            "known ones are: {}".format(access, known_access_labels))

    pathsep = ds.config.get("datalad.gitlab-default-pathseparator", None) or "-"
    project_stub = \
        ds.config.get("datalad.gitlab-default-projectname", None) or "project"
    project_var = 'datalad.gitlab-{}-project'.format(site)
    process_root = refds == ds
    if project is None:
        # look for a specific config in the dataset
        project = ds.config.get(project_var, None)

    if project and process_root and layout == 'collection':
        # the root of a collection
        project = f'{project}/{project_stub}'
    elif project is None and not process_root:
        # check if we can build one from the refds config
        ref_project = refds.config.get(project_var, None)
        if ref_project:
            # layout-specific derivation of a path from
            # the reference dataset configuration
            rproject = ds.pathobj.relative_to(refds.pathobj).as_posix()
            if layout == 'hierarchy':
                project = f'{ref_project}/{rproject}/{project_stub}'
            elif layout == 'collection':
                project = '{}/{}'.format(
                    ref_project,
                    rproject.replace('/', pathsep))
            else:
                project = '{}-{}'.format(
                    ref_project,
                    rproject.replace('/', pathsep))

    if project is None:
        yield dict(
            res_kwargs,
            status='error',
            message='No project name/location specified, and no configuration '
                    'to derive one',
        )
        return

    res_kwargs['project'] = project

    if dry_run:
        # this is as far as we can get without talking to GitLab
        yield dict(
            res_kwargs,
            status='ok',
            dryrun=True,
        )
        return

    # and now talk to GitLab for real
    site_api = siteobjs[site] if site in siteobjs else GitLabSite(site)

    site_project = site_api.get_project(project)
    if site_project is None:
        try:
            site_project = site_api.create_project(project, description)
            # report success
            message = "sibling repository '%s' created at %s",\
                      remotename, site_project.get('web_url', None)
            yield dict(
                res_kwargs,
                # relay all attributes
                project_attributes=site_project,
                message=message,
                status='ok',
            )
        except Exception as e:
            ce = CapturedException(e)
            yield dict(
                res_kwargs,
                # relay all attributes
                status='error',
                message=('Failed to create GitLab project: %s', ce),
                exception=ce
            )
            return
    else:
        # there already is a project
        if existing == 'error':
            # be nice and only actually error if there is a real mismatch
            if remotename not in dremotes:
                yield dict(
                    res_kwargs,
                    project_attributes=site_project,
                    status='error',
                    message=(
                        "There is already a project at '%s' on site '%s', "
                        "but no sibling with name '%s' is configured, "
                        "maybe use --existing=reconfigure",
                        project, site, remotename,
                    )
                )
                return
            elif access in ('ssh', 'ssh+http') \
                    and dremotes[remotename].get(
                        'url', None) != site_project.get(
                            # use False as a default so that there is a
                            # mismatch, complain if both are missing
                            'ssh_url_to_repo', False):
                yield dict(
                    res_kwargs,
                    project_attributes=site_project,
                    status='error',
                    message=(
                        "There is already a project at '%s' on site '%s', "
                        "but SSH access URL '%s' does not match '%s', "
                        "maybe use --existing=reconfigure",
                        project, site,
                        dremotes[remotename].get('url', None),
                        site_project.get('ssh_url_to_repo', None)
                    )
                )
                return
            elif access == 'http' \
                    and dremotes[remotename].get(
                        'url', None) != site_project.get(
                            # use False as a default so that there is a
                            # mismatch, veen if both are missing
                            'http_url_to_repo', False):
                yield dict(
                    res_kwargs,
                    project_attributes=site_project,
                    status='error',
                    message=(
                        "There is already a project at '%s' on site '%s', "
                        "but HTTP access URL '%s' does not match '%s', "
                        "maybe use --existing=reconfigure",
                        project, site,
                        dremotes[remotename].get('url', None),
                        site_project.get('http_url_to_repo', None)
                    )
                )
                return
        yield dict(
            res_kwargs,
            project_attributes=site_project,
            status='notneeded',
            message=(
                "There is already a project at '%s' on site '%s'",
                project, site,
            )
        )

    # first make sure that annex doesn't touch this one
    # but respect any existing config
    ignore_var = 'remote.{}.annex-ignore'.format(remotename)
    if ignore_var not in ds.config:
        ds.config.add(ignore_var, 'true', scope='local')

    for res in ds.siblings(
            'configure',
            name=remotename,
            url=site_project['http_url_to_repo']
            if access in ('http', 'ssh+http')
            else site_project['ssh_url_to_repo'],
            pushurl=site_project['ssh_url_to_repo']
            if access in ('ssh', 'ssh+http')
            else None,
            recursive=False,
            publish_depends=depends,
            result_renderer='disabled',
            return_type='generator'):
        yield res


class GitLabSite(object):
    def __init__(self, site):
        import gitlab
        self.gitlab = gitlab
        try:
            self.site = gitlab.Gitlab.from_config(site)
        except gitlab.config.GitlabDataError as e:
            raise ValueError(
                '{}, please configure access to this GitLab instance'.format(
                    str(e)))

    def get_project(self, path):
        try:
            return self.site.projects.get(path).attributes
        except self.gitlab.GitlabGetError as e:
            lgr.debug("Project with path '%s' does not yet exist at site '%s'",
                      path, self.site.url)
            return None

    def create_project(self, path, description=None):
        path_l = path.split('/')
        namespace_id = self._obtain_namespace(path_l)
        # check for options:
        # https://gitlab.com/help/api/projects.md#create-project
        props = dict(
            name=path_l[-1],
            namespace_id=namespace_id,
        )
        if description:
            props['description'] = description
        project = self.site.projects.create(props)
        return project.attributes

    def _obtain_namespace(self, path_l):

        if len(path_l) == 1:
            # no nesting whatsoever
            return None

        try:
            namespace_id = self.site.groups.get(
                '/'.join(path_l[:-1])).get_id()
            lgr.debug("Found existing parent group '%s' with ID %s",
                      '/'.join(path_l[:-1]), namespace_id)
        except self.gitlab.GitlabGetError as e:
            try:
                if len(path_l) > 2:
                    parent_group = self.site.groups.get(
                        '/'.join(path_l[:-2]))
                else:
                    parent_group = None
            except self.gitlab.GitlabGetError as e:
                raise ValueError(
                    "No parent group {} for project {} found, "
                    "and a group {} also does not exist. At most one "
                    "parent group would be created.".format(
                        '/'.join(path_l[:-1]),
                        '/'.join(path_l),
                        '/'.join(path_l[:-2]),
                    ))
            # create the group for the target project
            try:
                # prevent failure due to specification of a users personal
                # group, always exists, cannot and must not be created
                self.site.auth()
                if len(path_l) == 2 \
                        and path_l[0] == self.site.user.attributes.get(
                            'username', None):
                    # attempt to create a personal project in the users
                    # top-level personal group-- this is the same as
                    # having no parent namespace, don't attempt to
                    # create the group
                    return None
                namespace_id = self.site.groups.create(dict(
                    name=path_l[-2],
                    path=path_l[-2],
                    parent_id=parent_group.get_id() if parent_group else None)
                ).get_id()
            except self.gitlab.GitlabCreateError as e:
                raise RuntimeError(
                    "Failed to create parent group '{}' under {}: {}".format(
                        path_l[-2],
                        repr(parent_group.attributes['full_path'])
                        if parent_group else 'the account root',
                        str(e)),
                )
        return namespace_id

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** Incorrect Project Path Construction with Non-Flat Layout in `create_sibling_gitlab`

**Description:**
When creating a GitLab sibling with a non-flat layout, the project path is not constructed correctly. Specifically, the `project` field does not include the necessary hierarchy, leading to incorrect project paths.

**Buggy Code Example:**
```python
# Attempting to create a GitLab sibling with a hierarchical layout
result = create_sibling_gitlab(
    site='dummy',
    sibling='dummy',
    project='here',
    layout='collection',
    dry_run=True
)

print(result['project'])  # Expected: 'here/project', but got 'here'
```

**Expected Behavior:**
When using a hierarchical layout (e.g., `layout='collection'`), the `project` field should include the hierarchy, resulting in paths like `'here/project'`. This ensures that the projects are correctly organized within the GitLab hierarchy.

**Actual Behavior:**
The `project` field is incorrectly set to `'here'` without appending `'/project'`, resulting in an incorrect project path. This leads to inconsistencies and causes related operations to fail.

**Steps to Reproduce:**
1. Configure the project to use a non-flat layout (e.g., `layout='collection'`).
2. Attempt to create a GitLab sibling using the `create_sibling_gitlab` function with the specified layout.
3. Observe that the `project` field does not include the expected hierarchical path.

**Impact:**
This issue prevents proper organization of projects within GitLab when using hierarchical layouts, potentially causing configuration errors and workflow disruptions for users relying on structured project paths.

[/ISSUE]
{
    "test_dryrun": "PASSED",
    "test_fake_gitlab": "PASSED"
}
diff --git a/datalad/distributed/create_sibling_gitlab.py b/datalad/distributed/create_sibling_gitlab.py
index 2e6a5c0d6..30d0bbdd1 100644
--- a/datalad/distributed/create_sibling_gitlab.py
+++ b/datalad/distributed/create_sibling_gitlab.py
@@ -416,7 +416,7 @@ def _proc_dataset(refds, ds, site, project, remotename, layout, existing,
         # look for a specific config in the dataset
         project = ds.config.get(project_var, None)
 
-    if project and process_root and layout == 'collection':
+    if project and process_root and layout != 'flat':
         # the root of a collection
         project = f'{project}/{project_stub}'
     elif project is None and not process_root:

{"datalad/distributed/create_sibling_gitlab.py": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"High-level interface for creating a publication target on a GitLab instance\n\"\"\"\n\n__docformat__ = 'restructuredtext'\n\n\nimport logging\nimport warnings\n\nfrom datalad.support.exceptions import CapturedException\n\nfrom ..distribution.dataset import (\n    EnsureDataset,\n    datasetmethod,\n    require_dataset,\n    resolve_path,\n)\n# bound methods\nfrom ..distribution.siblings import Siblings\nfrom ..dochelpers import exc_str\nfrom ..interface.base import (\n    Interface,\n    build_doc,\n    eval_results,\n)\nfrom ..interface.common_opts import (\n    publish_depends,\n    recursion_flag,\n    recursion_limit,\n)\nfrom ..local.subdatasets import Subdatasets\nfrom ..support.constraints import (\n    EnsureChoice,\n    EnsureNone,\n    EnsureStr,\n)\nfrom ..support.param import Parameter\nfrom ..utils import ensure_list\n\nlgr = logging.getLogger('datalad.distributed.create_sibling_gitlab')\n\nknown_layout_labels = ('hierarchy', 'collection', 'flat')\nknown_access_labels = ('http', 'ssh', 'ssh+http')\n\n\n@build_doc\nclass CreateSiblingGitlab(Interface):\n    \"\"\"Create dataset sibling at a GitLab site\n\n    An existing GitLab project, or a project created via the GitLab web\n    interface can be configured as a sibling with the :command:`siblings`\n    command. Alternatively, this command can create a GitLab project at any\n    location/path a given user has appropriate permissions for. This is\n    particularly helpful for recursive sibling creation for subdatasets. API\n    access and authentication are implemented via python-gitlab, and all its\n    features are supported. A particular GitLab site must be configured in a\n    named section of a python-gitlab.cfg file (see\n    https://python-gitlab.readthedocs.io/en/stable/cli.html#configuration for\n    details), such as::\n\n      [mygit]\n      url = https://git.example.com\n      api_version = 4\n      private_token = abcdefghijklmnopqrst\n\n    Subsequently, this site is identified by its name ('mygit' in the example\n    above).\n\n    (Recursive) sibling creation for all, or a selected subset of subdatasets\n    is supported with three different project layouts (see --layout):\n\n    \"hierarchy\"\n      Each dataset is placed into its own group, and the actual GitLab\n      project for a dataset is put in a project named \"project\" inside\n      this group. This project name is configurable (see Configuration).\n      Using this layout, arbitrarily deep hierarchies of\n      nested datasets can be represented, while the hierarchical structure\n      is reflected in the project path. This is the default layout, if\n      no project path is specified.\n    \"flat\"\n      All datasets are placed in the same group. The name of a project\n      is its relative path within the root dataset, with all path separator\n      characters replaced by '-'. This path separator is configurable\n      (see Configuration).\n    \"collection\"\n      This is a hybrid layout, where the root dataset is placed in a \"project\"\n      project inside a group, and all nested subdatasets are represented\n      inside the group using a \"flat\" layout. The project name is configurable\n      (see Configuration).\n\n    GitLab cannot host dataset content. However, in combination with\n    other data sources (and siblings), publishing a dataset to GitLab can\n    facilitate distribution and exchange, while still allowing any dataset\n    consumer to obtain actual data content from alternative sources.\n\n    *Configuration*\n\n    Many configuration switches and options for GitLab sibling creation can\n    be provided arguments to the command. However, it is also possible to\n    specify a particular setup in a dataset's configuration. This is\n    particularly important when managing large collections of datasets.\n    Configuration options are:\n\n    \"datalad.gitlab-default-site\"\n        Name of the default GitLab site (see --site)\n    \"datalad.gitlab-SITENAME-siblingname\"\n        Name of the sibling configured for the local dataset that points\n        to the GitLab instance SITENAME (see --name)\n    \"datalad.gitlab-SITENAME-layout\"\n        Project layout used at the GitLab instance SITENAME (see --layout)\n    \"datalad.gitlab-SITENAME-access\"\n        Access method used for the GitLab instance SITENAME (see --access)\n    \"datalad.gitlab-SITENAME-project\"\n        Project location/path used for a datasets at GitLab instance\n        SITENAME (see --project). Configuring this is useful for deriving\n        project paths for subdatasets, relative to superdataset.\n    \"datalad.gitlab-default-projectname\"\n        The hierarchy and collection layouts publish (sub)datasets as projects\n        with a custom name. The default name \"project\" can be overridden with\n        this configuration.\n    \"datalad.gitlab-default-pathseparator\"\n        The flat and collection layout represent subdatasets with project names\n        that correspond to the path, with the regular path separator replaced\n        with a \"-\": superdataset-subdataset. This configuration can override\n        this default separator.\n\n    This command can be configured with\n    \"datalad.create-sibling-ghlike.extra-remote-settings.NETLOC.KEY=VALUE\" in\n    order to add any local KEY = VALUE configuration to the created sibling in\n    the local `.git/config` file. NETLOC is the domain of the Gitlab instance to\n    apply the configuration for.\n    This leads to a behavior that is equivalent to calling datalad's\n    ``siblings('configure', ...)``||``siblings configure`` command with the\n    respective KEY-VALUE pair after creating the sibling.\n    The configuration, like any other, could be set at user- or system level, so\n    users do not need to add this configuration to every sibling created with\n    the service at NETLOC themselves.\n\n    \"\"\"\n    _params_ = dict(\n        path=Parameter(\n            args=('path',),\n            metavar='PATH',\n            nargs='*',\n            doc=\"\"\"selectively create siblings for any datasets underneath a given\n            path. By default only the root dataset is considered.\"\"\"),\n        dataset=Parameter(\n            args=(\"--dataset\", \"-d\",),\n            doc=\"\"\"reference or root dataset. If no path constraints are given,\n            a sibling for this dataset will be created. In this and all other\n            cases, the reference dataset is also consulted for the GitLab\n            configuration, and desired project layout. If no dataset is given,\n            an attempt is made to identify the dataset based on the current\n            working directory\"\"\",\n            constraints=EnsureDataset() | EnsureNone()),\n        site=Parameter(\n            args=('--site',),\n            metavar='SITENAME',\n            doc=\"\"\"name of the GitLab site to create a sibling at. Must match an\n            existing python-gitlab configuration section with location and\n            authentication settings (see\n            https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#configuration).\n            By default the dataset configuration is consulted.\n            \"\"\",\n            constraints=EnsureNone() | EnsureStr()),\n        project=Parameter(\n            args=('--project',),\n            metavar='NAME/LOCATION',\n            doc=\"\"\"project name/location at the GitLab site. If a subdataset of the\n            reference dataset is processed, its project path is automatically\n            determined by the `layout` configuration, by default.\n            \"\"\",\n            constraints=EnsureNone() | EnsureStr()),\n        layout=Parameter(\n            args=('--layout',),\n            constraints=EnsureChoice(None, *known_layout_labels),\n            doc=\"\"\"layout of projects at the GitLab site, if a collection, or\n            a hierarchy of datasets and subdatasets is to be created.\n            By default the dataset configuration is consulted.\n            \"\"\"),\n        recursive=recursion_flag,\n        recursion_limit=recursion_limit,\n        name=Parameter(\n            args=('-s', '--name',),\n            metavar='NAME',\n            doc=\"\"\"name to represent the GitLab sibling remote in the local\n            dataset installation. If not specified a name is looked up in the\n            dataset configuration, or defaults to the `site` name\"\"\",\n            constraints=EnsureStr() | EnsureNone()),\n        existing=Parameter(\n            args=(\"--existing\",),\n            constraints=EnsureChoice('skip', 'error', 'reconfigure'),\n            doc=\"\"\"desired behavior when already existing or configured\n            siblings are discovered. 'skip': ignore; 'error': fail, if access\n            URLs differ; 'reconfigure': use the existing repository and\n            reconfigure the local dataset to use it as a sibling\"\"\",),\n        access=Parameter(\n            args=(\"--access\",),\n            constraints=EnsureChoice(None, *known_access_labels),\n            doc=\"\"\"access method used for data transfer to and from the sibling.\n            'ssh': read and write access used the SSH protocol; 'http': read and\n            write access use HTTP requests; 'ssh+http': read access is done via\n            HTTP and write access performed with SSH. Dataset configuration is\n            consulted for a default, 'http' is used otherwise.\"\"\",),\n        description=Parameter(\n            args=(\"--description\",),\n            doc=\"\"\"brief description for the GitLab project (displayed on the\n            site)\"\"\",\n            constraints=EnsureStr() | EnsureNone()),\n        publish_depends=publish_depends,\n        dry_run=Parameter(\n            args=(\"--dry-run\",),\n            action=\"store_true\",\n            doc=\"\"\"if set, no repository will be created, only tests for\n            name collisions will be performed, and would-be repository names\n            are reported for all relevant datasets\"\"\"),\n        dryrun=Parameter(\n            args=(\"--dryrun\",),\n            action=\"store_true\",\n            doc=\"\"\"Deprecated. Use the renamed\n            ``dry_run||--dry-run`` parameter\"\"\")\n    )\n\n    @staticmethod\n    @datasetmethod(name='create_sibling_gitlab')\n    @eval_results\n    def __call__(\n            path=None,\n            *,\n            site=None,\n            project=None,\n            layout=None,\n            dataset=None,\n            recursive=False,\n            recursion_limit=None,\n            name=None,\n            existing='error',\n            access=None,\n            publish_depends=None,\n            description=None,\n            dryrun=False,\n            dry_run=False):\n        if dryrun and not dry_run:\n            # the old one is used, and not in agreement with the new one\n            warnings.warn(\n                \"datalad-create-sibling-github's `dryrun` option is \"\n                \"deprecated and will be removed in a future release, \"\n                \"use the renamed `dry_run/--dry-run` option instead.\",\n                DeprecationWarning)\n            dry_run = dryrun\n        path = resolve_path(ensure_list(path), ds=dataset) \\\n            if path else None\n\n        if project and (recursive or (path and len(path) > 1)):\n            raise ValueError(\n                'Providing a GitLab project name/location cannot be combined '\n                'with recursive operation or multiple paths, as each dataset '\n                'needs to be mapped onto its own individual project.')\n        # what to operate on\n        ds = require_dataset(\n            dataset, check_installed=True, purpose='create GitLab sibling(s)')\n\n        # cache for objects of gitlab sites (we could face different ones\n        # in a single hierarchy, cache them to avoid duplicate initialization\n        # while still being able to process each dataset individually\n        siteobjs = dict()\n\n        # which datasets to process?\n        if path is None or ds.pathobj in path:\n            for r in _proc_dataset(\n                    ds, ds,\n                    site, project, name, layout, existing, access,\n                    dry_run, siteobjs, publish_depends, description):\n                yield r\n        # we need to find a subdataset when recursing, or when there is a path that\n        # could point to one, we have to exclude the parent dataset in this test\n        # to avoid undesired level-1 recursion into subdatasets\n        if any(p != ds.pathobj for p in (path or [])) or recursive:\n            # also include any matching subdatasets\n            subds = ds.subdatasets(\n                    path=path,\n                    # we can only operate on present datasets\n                    state='present',\n                    recursive=recursive,\n                    recursion_limit=recursion_limit,\n                    contains=None,\n                    bottomup=False,\n                    result_xfm='datasets',\n                    result_renderer='disabled',\n                    return_type='list')\n            if not subds:\n                # we didn't find anything to operate on, let the user know\n                for p in path:\n                    yield dict(\n                          action='create_sibling_gitlab',\n                          status='impossible',\n                          refds=ds.path,\n                          path=p,\n                          message=('No dataset found under %s' % p),\n                          type='dataset',\n                          logger=lgr,\n                    )\n            else:\n                for sub in subds:\n                    for r in _proc_dataset(\n                            ds, sub,\n                            site, project, name, layout, existing, access,\n                            dry_run, siteobjs, publish_depends, description):\n                        yield r\n\n        return\n\n\ndef _proc_dataset(refds, ds, site, project, remotename, layout, existing,\n                  access, dry_run, siteobjs, depends, description):\n    # basic result setup\n    res_kwargs = dict(\n        action='create_sibling_gitlab',\n        refds=refds.path,\n        path=ds.path,\n        type='dataset',\n        logger=lgr,\n    )\n    if description:\n        res_kwargs['description'] = description\n\n    if site is None:\n        # always try pulling the base config from a parent dataset\n        # even if paths were given (may be overwritten later)\n        basecfgsite = ds.config.get('datalad.gitlab-default-site', None)\n\n    # let the dataset config overwrite the target site, if none\n    # was given\n    site = refds.config.get(\n        'datalad.gitlab-default-site', basecfgsite) \\\n        if site is None else site\n    if site is None:\n        # this means the most top-level dataset has no idea about\n        # gitlab, and no site was specified as an argument\n        # fail rather then give an error result, as this is very\n        # unlikely to be intentional\n        raise ValueError(\n            'No GitLab site was specified (--site) or configured '\n            'in {} (datalad.gitlab.default-site)'.format(ds))\n    res_kwargs['site'] = site\n\n    # determine target remote name, unless given\n    if remotename is None:\n        remotename_var = 'datalad.gitlab-{}-siblingname'.format(site)\n        remotename = ds.config.get(\n            remotename_var,\n            # use config from parent, if needed\n            refds.config.get(\n                remotename_var,\n                # fall back on site name, if nothing else can be used\n                site))\n    res_kwargs['sibling'] = remotename\n    # check against existing remotes\n    dremotes = {\n        r['name']: r\n        for r in ds.siblings(\n            action='query',\n            # fastest possible\n            get_annex_info=False,\n            recursive=False,\n            return_type='generator',\n            result_renderer='disabled')\n    }\n    if remotename in dremotes and existing not in ['replace', 'reconfigure']:\n        # we already know a sibling with this name\n        yield dict(\n            res_kwargs,\n            status='error' if existing == 'error' else 'notneeded',\n            message=('already has a configured sibling \"%s\"', remotename),\n        )\n        return\n\n    if layout is None:\n        # figure out the layout of projects on the site\n        # use the reference dataset as default, and fall back\n        # on 'hierarchy' as the most generic method of representing\n        # the filesystem in a group/subgroup structure\n        layout_var = 'datalad.gitlab-{}-layout'.format(site)\n        layout = ds.config.get(\n            layout_var, refds.config.get(\n                layout_var, 'hierarchy'))\n    if layout not in known_layout_labels:\n        raise ValueError(\n            \"Unknown site layout '{}' given or configured, \"\n            \"known ones are: {}\".format(layout, known_layout_labels))\n\n    if access is None:\n        access_var = 'datalad.gitlab-{}-access'.format(site)\n        access = ds.config.get(\n            access_var, refds.config.get(\n                access_var, 'http'))\n    if access not in known_access_labels:\n        raise ValueError(\n            \"Unknown site access '{}' given or configured, \"\n            \"known ones are: {}\".format(access, known_access_labels))\n\n    pathsep = ds.config.get(\"datalad.gitlab-default-pathseparator\", None) or \"-\"\n    project_stub = \\\n        ds.config.get(\"datalad.gitlab-default-projectname\", None) or \"project\"\n    project_var = 'datalad.gitlab-{}-project'.format(site)\n    process_root = refds == ds\n    if project is None:\n        # look for a specific config in the dataset\n        project = ds.config.get(project_var, None)\n\n    if project and process_root and layout == 'collection':\n        # the root of a collection\n        project = f'{project}/{project_stub}'\n    elif project is None and not process_root:\n        # check if we can build one from the refds config\n        ref_project = refds.config.get(project_var, None)\n        if ref_project:\n            # layout-specific derivation of a path from\n            # the reference dataset configuration\n            rproject = ds.pathobj.relative_to(refds.pathobj).as_posix()\n            if layout == 'hierarchy':\n                project = f'{ref_project}/{rproject}/{project_stub}'\n            elif layout == 'collection':\n                project = '{}/{}'.format(\n                    ref_project,\n                    rproject.replace('/', pathsep))\n            else:\n                project = '{}-{}'.format(\n                    ref_project,\n                    rproject.replace('/', pathsep))\n\n    if project is None:\n        yield dict(\n            res_kwargs,\n            status='error',\n            message='No project name/location specified, and no configuration '\n                    'to derive one',\n        )\n        return\n\n    res_kwargs['project'] = project\n\n    if dry_run:\n        # this is as far as we can get without talking to GitLab\n        yield dict(\n            res_kwargs,\n            status='ok',\n            dryrun=True,\n        )\n        return\n\n    # and now talk to GitLab for real\n    site_api = siteobjs[site] if site in siteobjs else GitLabSite(site)\n\n    site_project = site_api.get_project(project)\n    if site_project is None:\n        try:\n            site_project = site_api.create_project(project, description)\n            # report success\n            message = \"sibling repository '%s' created at %s\",\\\n                      remotename, site_project.get('web_url', None)\n            yield dict(\n                res_kwargs,\n                # relay all attributes\n                project_attributes=site_project,\n                message=message,\n                status='ok',\n            )\n        except Exception as e:\n            ce = CapturedException(e)\n            yield dict(\n                res_kwargs,\n                # relay all attributes\n                status='error',\n                message=('Failed to create GitLab project: %s', ce),\n                exception=ce\n            )\n            return\n    else:\n        # there already is a project\n        if existing == 'error':\n            # be nice and only actually error if there is a real mismatch\n            if remotename not in dremotes:\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but no sibling with name '%s' is configured, \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site, remotename,\n                    )\n                )\n                return\n            elif access in ('ssh', 'ssh+http') \\\n                    and dremotes[remotename].get(\n                        'url', None) != site_project.get(\n                            # use False as a default so that there is a\n                            # mismatch, complain if both are missing\n                            'ssh_url_to_repo', False):\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but SSH access URL '%s' does not match '%s', \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site,\n                        dremotes[remotename].get('url', None),\n                        site_project.get('ssh_url_to_repo', None)\n                    )\n                )\n                return\n            elif access == 'http' \\\n                    and dremotes[remotename].get(\n                        'url', None) != site_project.get(\n                            # use False as a default so that there is a\n                            # mismatch, veen if both are missing\n                            'http_url_to_repo', False):\n                yield dict(\n                    res_kwargs,\n                    project_attributes=site_project,\n                    status='error',\n                    message=(\n                        \"There is already a project at '%s' on site '%s', \"\n                        \"but HTTP access URL '%s' does not match '%s', \"\n                        \"maybe use --existing=reconfigure\",\n                        project, site,\n                        dremotes[remotename].get('url', None),\n                        site_project.get('http_url_to_repo', None)\n                    )\n                )\n                return\n        yield dict(\n            res_kwargs,\n            project_attributes=site_project,\n            status='notneeded',\n            message=(\n                \"There is already a project at '%s' on site '%s'\",\n                project, site,\n            )\n        )\n\n    # first make sure that annex doesn't touch this one\n    # but respect any existing config\n    ignore_var = 'remote.{}.annex-ignore'.format(remotename)\n    if ignore_var not in ds.config:\n        ds.config.add(ignore_var, 'true', scope='local')\n\n    for res in ds.siblings(\n            'configure',\n            name=remotename,\n            url=site_project['http_url_to_repo']\n            if access in ('http', 'ssh+http')\n            else site_project['ssh_url_to_repo'],\n            pushurl=site_project['ssh_url_to_repo']\n            if access in ('ssh', 'ssh+http')\n            else None,\n            recursive=False,\n            publish_depends=depends,\n            result_renderer='disabled',\n            return_type='generator'):\n        yield res\n\n\nclass GitLabSite(object):\n    def __init__(self, site):\n        import gitlab\n        self.gitlab = gitlab\n        try:\n            self.site = gitlab.Gitlab.from_config(site)\n        except gitlab.config.GitlabDataError as e:\n            raise ValueError(\n                '{}, please configure access to this GitLab instance'.format(\n                    str(e)))\n\n    def get_project(self, path):\n        try:\n            return self.site.projects.get(path).attributes\n        except self.gitlab.GitlabGetError as e:\n            lgr.debug(\"Project with path '%s' does not yet exist at site '%s'\",\n                      path, self.site.url)\n            return None\n\n    def create_project(self, path, description=None):\n        path_l = path.split('/')\n        namespace_id = self._obtain_namespace(path_l)\n        # check for options:\n        # https://gitlab.com/help/api/projects.md#create-project\n        props = dict(\n            name=path_l[-1],\n            namespace_id=namespace_id,\n        )\n        if description:\n            props['description'] = description\n        project = self.site.projects.create(props)\n        return project.attributes\n\n    def _obtain_namespace(self, path_l):\n\n        if len(path_l) == 1:\n            # no nesting whatsoever\n            return None\n\n        try:\n            namespace_id = self.site.groups.get(\n                '/'.join(path_l[:-1])).get_id()\n            lgr.debug(\"Found existing parent group '%s' with ID %s\",\n                      '/'.join(path_l[:-1]), namespace_id)\n        except self.gitlab.GitlabGetError as e:\n            try:\n                if len(path_l) > 2:\n                    parent_group = self.site.groups.get(\n                        '/'.join(path_l[:-2]))\n                else:\n                    parent_group = None\n            except self.gitlab.GitlabGetError as e:\n                raise ValueError(\n                    \"No parent group {} for project {} found, \"\n                    \"and a group {} also does not exist. At most one \"\n                    \"parent group would be created.\".format(\n                        '/'.join(path_l[:-1]),\n                        '/'.join(path_l),\n                        '/'.join(path_l[:-2]),\n                    ))\n            # create the group for the target project\n            try:\n                # prevent failure due to specification of a users personal\n                # group, always exists, cannot and must not be created\n                self.site.auth()\n                if len(path_l) == 2 \\\n                        and path_l[0] == self.site.user.attributes.get(\n                            'username', None):\n                    # attempt to create a personal project in the users\n                    # top-level personal group-- this is the same as\n                    # having no parent namespace, don't attempt to\n                    # create the group\n                    return None\n                namespace_id = self.site.groups.create(dict(\n                    name=path_l[-2],\n                    path=path_l[-2],\n                    parent_id=parent_group.get_id() if parent_group else None)\n                ).get_id()\n            except self.gitlab.GitlabCreateError as e:\n                raise RuntimeError(\n                    \"Failed to create parent group '{}' under {}: {}\".format(\n                        path_l[-2],\n                        repr(parent_group.attributes['full_path'])\n                        if parent_group else 'the account root',\n                        str(e)),\n                )\n        return namespace_id\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Incorrect Project Path Construction with Non-Flat Layout in `create_sibling_gitlab`

**Description:**
When creating a GitLab sibling with a non-flat layout, the project path is not constructed correctly. Specifically, the `project` field does not include the necessary hierarchy, leading to incorrect project paths.

**Buggy Code Example:**
```python
# Attempting to create a GitLab sibling with a hierarchical layout
result = create_sibling_gitlab(
    site='dummy',
    sibling='dummy',
    project='here',
    layout='collection',
    dry_run=True
)

print(result['project'])  # Expected: 'here/project', but got 'here'
```

**Expected Behavior:**
When using a hierarchical layout (e.g., `layout='collection'`), the `project` field should include the hierarchy, resulting in paths like `'here/project'`. This ensures that the projects are correctly organized within the GitLab hierarchy.

**Actual Behavior:**
The `project` field is incorrectly set to `'here'` without appending `'/project'`, resulting in an incorrect project path. This leads to inconsistencies and causes related operations to fail.

**Steps to Reproduce:**
1. Configure the project to use a non-flat layout (e.g., `layout='collection'`).
2. Attempt to create a GitLab sibling using the `create_sibling_gitlab` function with the specified layout.
3. Observe that the `project` field does not include the expected hierarchical path.

**Impact:**
This issue prevents proper organization of projects within GitLab when using hierarchical layouts, potentially causing configuration errors and workflow disruptions for users relying on structured project paths.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/distributed/create_sibling_gitlab.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""High-level interface for creating a publication target on a GitLab instance
"""

__docformat__ = 'restructuredtext'


import logging
import warnings

from datalad.support.exceptions import CapturedException

from ..distribution.dataset import (
    EnsureDataset,
    datasetmethod,
    require_dataset,
    resolve_path,
)
# bound methods
from ..distribution.siblings import Siblings
from ..dochelpers import exc_str
from ..interface.base import (
    Interface,
    build_doc,
    eval_results,
)
from ..interface.common_opts import (
    publish_depends,
    recursion_flag,
    recursion_limit,
)
from ..local.subdatasets import Subdatasets
from ..support.constraints import (
    EnsureChoice,
    EnsureNone,
    EnsureStr,
)
from ..support.param import Parameter
from ..utils import ensure_list

lgr = logging.getLogger('datalad.distributed.create_sibling_gitlab')

known_layout_labels = ('hierarchy', 'collection', 'flat')
known_access_labels = ('http', 'ssh', 'ssh+http')


@build_doc
class CreateSiblingGitlab(Interface):
    """Create dataset sibling at a GitLab site

    An existing GitLab project, or a project created via the GitLab web
    interface can be configured as a sibling with the :command:`siblings`
    command. Alternatively, this command can create a GitLab project at any
    location/path a given user has appropriate permissions for. This is
    particularly helpful for recursive sibling creation for subdatasets. API
    access and authentication are implemented via python-gitlab, and all its
    features are supported. A particular GitLab site must be configured in a
    named section of a python-gitlab.cfg file (see
    https://python-gitlab.readthedocs.io/en/stable/cli.html#configuration for
    details), such as::

      [mygit]
      url = https://git.example.com
      api_version = 4
      private_token = abcdefghijklmnopqrst

    Subsequently, this site is identified by its name ('mygit' in the example
    above).

    (Recursive) sibling creation for all, or a selected subset of subdatasets
    is supported with three different project layouts (see --layout):

    "hierarchy"
      Each dataset is placed into its own group, and the actual GitLab
      project for a dataset is put in a project named "project" inside
      this group. This project name is configurable (see Configuration).
      Using this layout, arbitrarily deep hierarchies of
      nested datasets can be represented, while the hierarchical structure
      is reflected in the project path. This is the default layout, if
      no project path is specified.
    "flat"
      All datasets are placed in the same group. The name of a project
      is its relative path within the root dataset, with all path separator
      characters replaced by '-'. This path separator is configurable
      (see Configuration).
    "collection"
      This is a hybrid layout, where the root dataset is placed in a "project"
      project inside a group, and all nested subdatasets are represented
      inside the group using a "flat" layout. The project name is configurable
      (see Configuration).

    GitLab cannot host dataset content. However, in combination with
    other data sources (and siblings), publishing a dataset to GitLab can
    facilitate distribution and exchange, while still allowing any dataset
    consumer to obtain actual data content from alternative sources.

    *Configuration*

    Many configuration switches and options for GitLab sibling creation can
    be provided arguments to the command. However, it is also possible to
    specify a particular setup in a dataset's configuration. This is
    particularly important when managing large collections of datasets.
    Configuration options are:

    "datalad.gitlab-default-site"
        Name of the default GitLab site (see --site)
    "datalad.gitlab-SITENAME-siblingname"
        Name of the sibling configured for the local dataset that points
        to the GitLab instance SITENAME (see --name)
    "datalad.gitlab-SITENAME-layout"
        Project layout used at the GitLab instance SITENAME (see --layout)
    "datalad.gitlab-SITENAME-access"
        Access method used for the GitLab instance SITENAME (see --access)
    "datalad.gitlab-SITENAME-project"
        Project location/path used for a datasets at GitLab instance
        SITENAME (see --project). Configuring this is useful for deriving
        project paths for subdatasets, relative to superdataset.
    "datalad.gitlab-default-projectname"
        The hierarchy and collection layouts publish (sub)datasets as projects
        with a custom name. The default name "project" can be overridden with
        this configuration.
    "datalad.gitlab-default-pathseparator"
        The flat and collection layout represent subdatasets with project names
        that correspond to the path, with the regular path separator replaced
        with a "-": superdataset-subdataset. This configuration can override
        this default separator.

    This command can be configured with
    "datalad.create-sibling-ghlike.extra-remote-settings.NETLOC.KEY=VALUE" in
    order to add any local KEY = VALUE configuration to the created sibling in
    the local `.git/config` file. NETLOC is the domain of the Gitlab instance to
    apply the configuration for.
    This leads to a behavior that is equivalent to calling datalad's
    ``siblings('configure', ...)``||``siblings configure`` command with the
    respective KEY-VALUE pair after creating the sibling.
    The configuration, like any other, could be set at user- or system level, so
    users do not need to add this configuration to every sibling created with
    the service at NETLOC themselves.

    """
    _params_ = dict(
        path=Parameter(
            args=('path',),
            metavar='PATH',
            nargs='*',
            doc="""selectively create siblings for any datasets underneath a given
            path. By default only the root dataset is considered."""),
        dataset=Parameter(
            args=("--dataset", "-d",),
            doc="""reference or root dataset. If no path constraints are given,
            a sibling for this dataset will be created. In this and all other
            cases, the reference dataset is also consulted for the GitLab
            configuration, and desired project layout. If no dataset is given,
            an attempt is made to identify the dataset based on the current
            working directory""",
            constraints=EnsureDataset() | EnsureNone()),
        site=Parameter(
            args=('--site',),
            metavar='SITENAME',
            doc="""name of the GitLab site to create a sibling at. Must match an
            existing python-gitlab configuration section with location and
            authentication settings (see
            https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#configuration).
            By default the dataset configuration is consulted.
            """,
            constraints=EnsureNone() | EnsureStr()),
        project=Parameter(
            args=('--project',),
            metavar='NAME/LOCATION',
            doc="""project name/location at the GitLab site. If a subdataset of the
            reference dataset is processed, its project path is automatically
            determined by the `layout` configuration, by default.
            """,
            constraints=EnsureNone() | EnsureStr()),
        layout=Parameter(
            args=('--layout',),
            constraints=EnsureChoice(None, *known_layout_labels),
            doc="""layout of projects at the GitLab site, if a collection, or
            a hierarchy of datasets and subdatasets is to be created.
            By default the dataset configuration is consulted.
            """),
        recursive=recursion_flag,
        recursion_limit=recursion_limit,
        name=Parameter(
            args=('-s', '--name',),
            metavar='NAME',
            doc="""name to represent the GitLab sibling remote in the local
            dataset installation. If not specified a name is looked up in the
            dataset configuration, or defaults to the `site` name""",
            constraints=EnsureStr() | EnsureNone()),
        existing=Parameter(
            args=("--existing",),
            constraints=EnsureChoice('skip', 'error', 'reconfigure'),
            doc="""desired behavior when already existing or configured
            siblings are discovered. 'skip': ignore; 'error': fail, if access
            URLs differ; 'reconfigure': use the existing repository and
            reconfigure the local dataset to use it as a sibling""",),
        access=Parameter(
            args=("--access",),
            constraints=EnsureChoice(None, *known_access_labels),
            doc="""access method used for data transfer to and from the sibling.
            'ssh': read and write access used the SSH protocol; 'http': read and
            write access use HTTP requests; 'ssh+http': read access is done via
            HTTP and write access performed with SSH. Dataset configuration is
            consulted for a default, 'http' is used otherwise.""",),
        description=Parameter(
            args=("--description",),
            doc="""brief description for the GitLab project (displayed on the
            site)""",
            constraints=EnsureStr() | EnsureNone()),
        publish_depends=publish_depends,
        dry_run=Parameter(
            args=("--dry-run",),
            action="store_true",
            doc="""if set, no repository will be created, only tests for
            name collisions will be performed, and would-be repository names
            are reported for all relevant datasets"""),
        dryrun=Parameter(
            args=("--dryrun",),
            action="store_true",
            doc="""Deprecated. Use the renamed
            ``dry_run||--dry-run`` parameter""")
    )

    @staticmethod
    @datasetmethod(name='create_sibling_gitlab')
    @eval_results
    def __call__(
            path=None,
            *,
            site=None,
            project=None,
            layout=None,
            dataset=None,
            recursive=False,
            recursion_limit=None,
            name=None,
            existing='error',
            access=None,
            publish_depends=None,
            description=None,
            dryrun=False,
            dry_run=False):
        if dryrun and not dry_run:
            # the old one is used, and not in agreement with the new one
            warnings.warn(
                "datalad-create-sibling-github's `dryrun` option is "
                "deprecated and will be removed in a future release, "
                "use the renamed `dry_run/--dry-run` option instead.",
                DeprecationWarning)
            dry_run = dryrun
        path = resolve_path(ensure_list(path), ds=dataset) \
            if path else None

        if project and (recursive or (path and len(path) > 1)):
            raise ValueError(
                'Providing a GitLab project name/location cannot be combined '
                'with recursive operation or multiple paths, as each dataset '
                'needs to be mapped onto its own individual project.')
        # what to operate on
        ds = require_dataset(
            dataset, check_installed=True, purpose='create GitLab sibling(s)')

        # cache for objects of gitlab sites (we could face different ones
        # in a single hierarchy, cache them to avoid duplicate initialization
        # while still being able to process each dataset individually
        siteobjs = dict()

        # which datasets to process?
        if path is None or ds.pathobj in path:
            for r in _proc_dataset(
                    ds, ds,
                    site, project, name, layout, existing, access,
                    dry_run, siteobjs, publish_depends, description):
                yield r
        # we need to find a subdataset when recursing, or when there is a path that
        # could point to one, we have to exclude the parent dataset in this test
        # to avoid undesired level-1 recursion into subdatasets
        if any(p != ds.pathobj for p in (path or [])) or recursive:
            # also include any matching subdatasets
            subds = ds.subdatasets(
                    path=path,
                    # we can only operate on present datasets
                    state='present',
                    recursive=recursive,
                    recursion_limit=recursion_limit,
                    contains=None,
                    bottomup=False,
                    result_xfm='datasets',
                    result_renderer='disabled',
                    return_type='list')
            if not subds:
                # we didn't find anything to operate on, let the user know
                for p in path:
                    yield dict(
                          action='create_sibling_gitlab',
                          status='impossible',
                          refds=ds.path,
                          path=p,
                          message=('No dataset found under %s' % p),
                          type='dataset',
                          logger=lgr,
                    )
            else:
                for sub in subds:
                    for r in _proc_dataset(
                            ds, sub,
                            site, project, name, layout, existing, access,
                            dry_run, siteobjs, publish_depends, description):
                        yield r

        return


def _proc_dataset(refds, ds, site, project, remotename, layout, existing,
                  access, dry_run, siteobjs, depends, description):
    # basic result setup
    res_kwargs = dict(
        action='create_sibling_gitlab',
        refds=refds.path,
        path=ds.path,
        type='dataset',
        logger=lgr,
    )
    if description:
        res_kwargs['description'] = description

    if site is None:
        # always try pulling the base config from a parent dataset
        # even if paths were given (may be overwritten later)
        basecfgsite = ds.config.get('datalad.gitlab-default-site', None)

    # let the dataset config overwrite the target site, if none
    # was given
    site = refds.config.get(
        'datalad.gitlab-default-site', basecfgsite) \
        if site is None else site
    if site is None:
        # this means the most top-level dataset has no idea about
        # gitlab, and no site was specified as an argument
        # fail rather then give an error result, as this is very
        # unlikely to be intentional
        raise ValueError(
            'No GitLab site was specified (--site) or configured '
            'in {} (datalad.gitlab.default-site)'.format(ds))
    res_kwargs['site'] = site

    # determine target remote name, unless given
    if remotename is None:
        remotename_var = 'datalad.gitlab-{}-siblingname'.format(site)
        remotename = ds.config.get(
            remotename_var,
            # use config from parent, if needed
            refds.config.get(
                remotename_var,
                # fall back on site name, if nothing else can be used
                site))
    res_kwargs['sibling'] = remotename
    # check against existing remotes
    dremotes = {
        r['name']: r
        for r in ds.siblings(
            action='query',
            # fastest possible
            get_annex_info=False,
            recursive=False,
            return_type='generator',
            result_renderer='disabled')
    }
    if remotename in dremotes and existing not in ['replace', 'reconfigure']:
        # we already know a sibling with this name
        yield dict(
            res_kwargs,
            status='error' if existing == 'error' else 'notneeded',
            message=('already has a configured sibling "%s"', remotename),
        )
        return

    if layout is None:
        # figure out the layout of projects on the site
        # use the reference dataset as default, and fall back
        # on 'hierarchy' as the most generic method of representing
        # the filesystem in a group/subgroup structure
        layout_var = 'datalad.gitlab-{}-layout'.format(site)
        layout = ds.config.get(
            layout_var, refds.config.get(
                layout_var, 'hierarchy'))
    if layout not in known_layout_labels:
        raise ValueError(
            "Unknown site layout '{}' given or configured, "
            "known ones are: {}".format(layout, known_layout_labels))

    if access is None:
        access_var = 'datalad.gitlab-{}-access'.format(site)
        access = ds.config.get(
            access_var, refds.config.get(
                access_var, 'http'))
    if access not in known_access_labels:
        raise ValueError(
            "Unknown site access '{}' given or configured, "
            "known ones are: {}".format(access, known_access_labels))

    pathsep = ds.config.get("datalad.gitlab-default-pathseparator", None) or "-"
    project_stub = \
        ds.config.get("datalad.gitlab-default-projectname", None) or "project"
    project_var = 'datalad.gitlab-{}-project'.format(site)
    process_root = refds == ds
    if project is None:
        # look for a specific config in the dataset
        project = ds.config.get(project_var, None)

    if project and process_root and layout == 'collection':
        # the root of a collection
        project = f'{project}/{project_stub}'
    elif project is None and not process_root:
        # check if we can build one from the refds config
        ref_project = refds.config.get(project_var, None)
        if ref_project:
            # layout-specific derivation of a path from
            # the reference dataset configuration
            rproject = ds.pathobj.relative_to(refds.pathobj).as_posix()
            if layout == 'hierarchy':
                project = f'{ref_project}/{rproject}/{project_stub}'
            elif layout == 'collection':
                project = '{}/{}'.format(
                    ref_project,
                    rproject.replace('/', pathsep))
            else:
                project = '{}-{}'.format(
                    ref_project,
                    rproject.replace('/', pathsep))

    if project is None:
        yield dict(
            res_kwargs,
            status='error',
            message='No project name/location specified, and no configuration '
                    'to derive one',
        )
        return

    res_kwargs['project'] = project

    if dry_run:
        # this is as far as we can get without talking to GitLab
        yield dict(
            res_kwargs,
            status='ok',
            dryrun=True,
        )
        return

    # and now talk to GitLab for real
    site_api = siteobjs[site] if site in siteobjs else GitLabSite(site)

    site_project = site_api.get_project(project)
    if site_project is None:
        try:
            site_project = site_api.create_project(project, description)
            # report success
            message = "sibling repository '%s' created at %s",\
                      remotename, site_project.get('web_url', None)
            yield dict(
                res_kwargs,
                # relay all attributes
                project_attributes=site_project,
                message=message,
                status='ok',
            )
        except Exception as e:
            ce = CapturedException(e)
            yield dict(
                res_kwargs,
                # relay all attributes
                status='error',
                message=('Failed to create GitLab project: %s', ce),
                exception=ce
            )
            return
    else:
        # there already is a project
        if existing == 'error':
            # be nice and only actually error if there is a real mismatch
            if remotename not in dremotes:
                yield dict(
                    res_kwargs,
                    project_attributes=site_project,
                    status='error',
                    message=(
                        "There is already a project at '%s' on site '%s', "
                        "but no sibling with name '%s' is configured, "
                        "maybe use --existing=reconfigure",
                        project, site, remotename,
                    )
                )
                return
            elif access in ('ssh', 'ssh+http') \
                    and dremotes[remotename].get(
                        'url', None) != site_project.get(
                            # use False as a default so that there is a
                            # mismatch, complain if both are missing
                            'ssh_url_to_repo', False):
                yield dict(
                    res_kwargs,
                    project_attributes=site_project,
                    status='error',
                    message=(
                        "There is already a project at '%s' on site '%s', "
                        "but SSH access URL '%s' does not match '%s', "
                        "maybe use --existing=reconfigure",
                        project, site,
                        dremotes[remotename].get('url', None),
                        site_project.get('ssh_url_to_repo', None)
                    )
                )
                return
            elif access == 'http' \
                    and dremotes[remotename].get(
                        'url', None) != site_project.get(
                            # use False as a default so that there is a
                            # mismatch, veen if both are missing
                            'http_url_to_repo', False):
                yield dict(
                    res_kwargs,
                    project_attributes=site_project,
                    status='error',
                    message=(
                        "There is already a project at '%s' on site '%s', "
                        "but HTTP access URL '%s' does not match '%s', "
                        "maybe use --existing=reconfigure",
                        project, site,
                        dremotes[remotename].get('url', None),
                        site_project.get('http_url_to_repo', None)
                    )
                )
                return
        yield dict(
            res_kwargs,
            project_attributes=site_project,
            status='notneeded',
            message=(
                "There is already a project at '%s' on site '%s'",
                project, site,
            )
        )

    # first make sure that annex doesn't touch this one
    # but respect any existing config
    ignore_var = 'remote.{}.annex-ignore'.format(remotename)
    if ignore_var not in ds.config:
        ds.config.add(ignore_var, 'true', scope='local')

    for res in ds.siblings(
            'configure',
            name=remotename,
            url=site_project['http_url_to_repo']
            if access in ('http', 'ssh+http')
            else site_project['ssh_url_to_repo'],
            pushurl=site_project['ssh_url_to_repo']
            if access in ('ssh', 'ssh+http')
            else None,
            recursive=False,
            publish_depends=depends,
            result_renderer='disabled',
            return_type='generator'):
        yield res


class GitLabSite(object):
    def __init__(self, site):
        import gitlab
        self.gitlab = gitlab
        try:
            self.site = gitlab.Gitlab.from_config(site)
        except gitlab.config.GitlabDataError as e:
            raise ValueError(
                '{}, please configure access to this GitLab instance'.format(
                    str(e)))

    def get_project(self, path):
        try:
            return self.site.projects.get(path).attributes
        except self.gitlab.GitlabGetError as e:
            lgr.debug("Project with path '%s' does not yet exist at site '%s'",
                      path, self.site.url)
            return None

    def create_project(self, path, description=None):
        path_l = path.split('/')
        namespace_id = self._obtain_namespace(path_l)
        # check for options:
        # https://gitlab.com/help/api/projects.md#create-project
        props = dict(
            name=path_l[-1],
            namespace_id=namespace_id,
        )
        if description:
            props['description'] = description
        project = self.site.projects.create(props)
        return project.attributes

    def _obtain_namespace(self, path_l):

        if len(path_l) == 1:
            # no nesting whatsoever
            return None

        try:
            namespace_id = self.site.groups.get(
                '/'.join(path_l[:-1])).get_id()
            lgr.debug("Found existing parent group '%s' with ID %s",
                      '/'.join(path_l[:-1]), namespace_id)
        except self.gitlab.GitlabGetError as e:
            try:
                if len(path_l) > 2:
                    parent_group = self.site.groups.get(
                        '/'.join(path_l[:-2]))
                else:
                    parent_group = None
            except self.gitlab.GitlabGetError as e:
                raise ValueError(
                    "No parent group {} for project {} found, "
                    "and a group {} also does not exist. At most one "
                    "parent group would be created.".format(
                        '/'.join(path_l[:-1]),
                        '/'.join(path_l),
                        '/'.join(path_l[:-2]),
                    ))
            # create the group for the target project
            try:
                # prevent failure due to specification of a users personal
                # group, always exists, cannot and must not be created
                self.site.auth()
                if len(path_l) == 2 \
                        and path_l[0] == self.site.user.attributes.get(
                            'username', None):
                    # attempt to create a personal project in the users
                    # top-level personal group-- this is the same as
                    # having no parent namespace, don't attempt to
                    # create the group
                    return None
                namespace_id = self.site.groups.create(dict(
                    name=path_l[-2],
                    path=path_l[-2],
                    parent_id=parent_group.get_id() if parent_group else None)
                ).get_id()
            except self.gitlab.GitlabCreateError as e:
                raise RuntimeError(
                    "Failed to create parent group '{}' under {}: {}".format(
                        path_l[-2],
                        repr(parent_group.attributes['full_path'])
                        if parent_group else 'the account root',
                        str(e)),
                )
        return namespace_id

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user