Repository: datalad/datalad

Issue:
`create-sibling-gitlab` fails with error: path 'must not start or end with a special character and must not contain consecutive special characters.'
### What is the problem?

When creating GitLab siblings recursively using `--layout=flat` or `--layout=collection`, the creation of the subdatasets' siblings fails with the following error:

```
create_sibling_gitlab(error): code/common (dataset) [Failed to create GitLab project: GitlabCreateError(400: {'path': ['must not start or end with a special character and must not contain consecutive special characters.']})]
```

It seems that GitLab 14.9 [introduced a new regex for project name validation](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80055) (only for newly created projects).

So the following project name formats used by DataLad in `create-sibling-gitlab` are invalid:
* `_repo_` (starts with special character)
* `{}--{}` (has consecutive special characters)

### What steps will reproduce the problem?

1. Configure a GitLab site `gitlab` in `.python-gitlab.cfg`
2. Create a dataset with nested subdatasets
3. From the superdataset directory, run `datalad create-sibling-gitlab -r --site gitlab`

### Datalad information

datalad 0.16.2

### Additional context

_No response_

### Have you had any success using DataLad before?

Still trying it out, but very excited about it!
Also, you did a truly amazing job on the handbook 👍 
(The link to the testimonials redirects me to the main repo -- needs to be fixed?)

Discussion:

Patch:
diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml
index d6c11f253..10708d90c 100644
--- a/.github/workflows/update-contributors.yml
+++ b/.github/workflows/update-contributors.yml
@@ -15,7 +15,7 @@ jobs:
         uses: actions/checkout@v3
 
       - name: Tributors Update
-        uses: con/tributors@0.1.1
+        uses: con/tributors@0.0.21
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
diff --git a/.tributors b/.tributors
index e3b00f19b..ec2434b30 100644
--- a/.tributors
+++ b/.tributors
@@ -31,11 +31,9 @@
         "bio": "Postdoc 🧠👨🏻‍💻 in the Gallant Lab at UC Berkeley"
     },
     "mih": {
-        "name": "Michael Hanke",
+        "name": "mih",
         "blog": "https://github.com/mih",
-        "email": "michael.hanke@gmail.com",
-        "orcid": "0000-0001-6398-6370",
-        "affiliation": "Institute of Neuroscience and Medicine, Brain & Behaviour (INM-7), Research Centre Jülich, Jülich, Germany and Institute of Systems Neuroscience, Medical Faculty, Heinrich Heine University Düsseldorf, Düsseldorf, Germany"
+        "email": "michael.hanke@gmail.com"
     },
     "yarikoptic": {
         "name": "Yaroslav Halchenko",
@@ -46,12 +44,12 @@
         "affiliation": "Dartmouth College"
     },
     "loj": {
-        "name": "Laura Waite",
+        "name": "loj",
         "blog": "https://github.com/loj",
         "email": "laura@waite.eu"
     },
     "feilong": {
-        "name": "Feilong Ma",
+        "name": "feilong",
         "blog": "https://github.com/feilong"
     },
     "jhpoelen": {
@@ -59,83 +57,74 @@
         "blog": "https://github.com/jhpoelen"
     },
     "andycon": {
-        "name": "Andrew Connolly",
+        "name": "andycon",
         "blog": "https://github.com/andycon"
     },
     "nicholsn": {
-        "name": "Nolan Nichols",
+        "name": "nicholsn",
         "blog": "https://github.com/nicholsn"
     },
     "adelavega": {
         "name": "Alejandro de la Vega",
         "blog": "https://github.com/adelavega",
         "email": "delavega@utexas.edu",
-        "bio": "Research Scientist @ UT Austin. ",
-        "orcid": "0000-0001-9062-3778",
-        "affiliation": "The University of Texas at Austin"
+        "bio": "Research Scientist @ UT Austin. "
     },
     "kskyten": {
         "name": "kskyten",
         "blog": "https://github.com/kskyten"
     },
     "TheChymera": {
-        "name": "Horea-Ioan Ioanas",
+        "name": "TheChymera",
         "blog": "https://github.com/TheChymera",
         "email": "chr@chymera.eu",
-        "bio": "I bring Reproducibility, Freedom, and Open Source to Neuroscience, Academic Publishing, Software, and Life.",
-        "orcid": "0000-0001-7037-2449",
-        "affiliation": "Dartmouth College"
+        "bio": "I bring Reproducibility, Freedom, and Open Source to Neuroscience, Academic Publishing, Software, and Life."
     },
     "effigies": {
-        "name": "Chris Markiewicz",
+        "name": "effigies",
         "blog": "https://github.com/effigies",
         "email": "effigies@gmail.com"
     },
     "jgors": {
-        "name": "Jasong Gors",
+        "name": "jgors",
         "blog": "https://github.com/jgors"
     },
     "debanjum": {
-        "name": "Debanjum Singh Solanky",
-        "blog": "https://github.com/debanjum",
-        "orcid": "0000-0002-0774-6564",
-        "affiliation": "Dartmouth College, Hanover, NH, United States"
+        "name": "debanjum",
+        "blog": "https://github.com/debanjum"
     },
     "nellh": {
         "name": "nellh",
         "blog": "https://github.com/nellh"
     },
     "emdupre": {
-        "name": "Elizabeth DuPre",
+        "name": "emdupre",
         "blog": "https://github.com/emdupre",
         "bio": "Neuroscience graduate student at McGill University"
     },
     "aqw": {
-        "name": "Alex Waite",
+        "name": "aqw",
         "blog": "https://github.com/aqw",
         "email": "alex@waite.eu"
     },
     "vsoch": {
-        "name": "Vanessa Sochat",
+        "name": "vsoch",
         "blog": "https://github.com/vsoch",
-        "bio": "I'm the Vanessasaurus!",
-        "orcid": "0000-0002-4387-3819"
+        "bio": "I'm the Vanessasaurus!"
     },
     "kyleam": {
-        "name": "Kyle Meyer",
+        "name": "kyleam",
         "blog": "https://github.com/kyleam",
         "email": "kyle@kyleam.com"
     },
     "driusan": {
-        "name": "Dave MacFarlane",
+        "name": "driusan",
         "blog": "https://github.com/driusan",
         "email": "driusan@gmail.com",
-        "bio": "Professional Rubber Duck",
-        "orcid": "0000-0001-9258-8099",
-        "affiliation": "McGill University"
+        "bio": "Professional Rubber Duck"
     },
     "overlake333": {
-        "name": "Taylor M. Olson",
+        "name": "overlake333",
         "blog": "https://github.com/overlake333",
         "email": "taylor.m.olson.21@dartmouth.edu"
     },
@@ -144,14 +133,12 @@
         "blog": "https://github.com/akeshavan"
     },
     "jwodder": {
-        "name": "John T. Wodder II",
+        "name": "jwodder",
         "blog": "https://github.com/jwodder"
     },
     "bpoldrack": {
-        "name": "Benjamin Poldrack",
-        "blog": "https://github.com/bpoldrack",
-        "orcid": "0000-0001-7628-0801",
-        "affiliation": "Institute of Neuroscience and Medicine, Brain & Behaviour (INM-7), Research Centre Jülich, Jülich, Germany"
+        "name": "bpoldrack",
+        "blog": "https://github.com/bpoldrack"
     },
     "yetanothertestuser": {
         "name": "yetanothertestuser",
@@ -160,9 +147,7 @@
     "christian-monch": {
         "name": "Christian Mönch",
         "email": "christian.moench@web.de",
-        "blog": "https://github.com/christian-monch",
-        "orcid": "0000-0002-3092-0612",
-        "affiliation": "Institute of Neuroscience and Medicine, Brain & Behaviour (INM-7), Research Centre Jülich, Jülich, Germany"
+        "blog": "https://github.com/christian-monch"
     },
     "mikapfl": {
         "name": "Mika Pflüger",
@@ -186,8 +171,7 @@
     "kimsin98": {
         "name": "Sin Kim",
         "blog": "https://orcid.org/0000-0003-4652-3758",
-        "bio": "I like coding, data, and open science. Current interests are adolescent development and psychiatry.",
-        "orcid": "0000-0003-4652-3758"
+        "bio": "I like coding, data, and open science. Current interests are adolescent development and psychiatry."
     },
     "DisasterMo": {
         "name": "Michael Burgardt",
@@ -205,9 +189,7 @@
     "mslw": {
         "name": "Michał Szczepanik",
         "bio": "Neuroinformatician.\r\nPhD candidate at Laboratory of Brain Imaging, Nencki Institute; \r\nResearch Associate at Psychoinformatics Lab, INM-7, FZ Jülich",
-        "blog": "https://github.com/mslw",
-        "orcid": "0000-0002-4028-2087",
-        "affiliation": "Forschungszentrum Jülich"
+        "blog": "https://github.com/mslw"
     },
     "bpinsard": {
         "name": "Basile",
@@ -222,15 +204,13 @@
         "name": "James Kent",
         "email": "jamesdkent21@gmail.com",
         "bio": "Postdoc",
-        "orcid": "0000-0002-4892-2659",
         "blog": "https://jdkent.github.io/"
     },
     "jsheunis": {
         "name": "Stephan Heunis",
         "bio": "DataLad, decentralised (research) data management, real-time fMRI analysis, data quality control, Matlab and Python bits and pieces",
         "blog": "https://jsheunis.github.io/",
-        "orcid": "0000-0003-3503-9872",
-        "affiliation": "Institute of Neuroscience and Medicine, Brain & Behavior (INM-7), Research Center Juelich, Germany"
+        "orcid": "0000-0003-3503-9872"
     },
     "lamby": {
         "name": "Chris Lamb",
@@ -240,9 +220,7 @@
     },
     "thewtex": {
         "name": "Matt McCormick",
-        "blog": "https://www.mmmccormick.com",
-        "orcid": "0000-0001-9475-3756",
-        "affiliation": "Kitware Inc"
+        "blog": "https://www.mmmccormick.com"
     },
     "tstoeter": {
         "name": "tstoeter",
@@ -251,8 +229,7 @@
     "vickychenglau": {
         "name": "Vicky C Lau",
         "email": "vickychenglau@gmail.com",
-        "blog": "https://github.com/vickychenglau",
-        "orcid": "0000-0002-1197-1887"
+        "blog": "https://github.com/vickychenglau"
     },
     "xgui3783": {
         "name": "xgui3783",
@@ -260,9 +237,7 @@
     },
     "asmacdo": {
         "name": "Austin Macdonald",
-        "blog": "https://github.com/asmacdo",
-        "orcid": "0000-0002-8124-807X",
-        "affiliation": "Dartmouth College"
+        "blog": "https://github.com/asmacdo"
     },
     "nobodyinperson": {
         "name": "Yann Büchau",
@@ -274,8 +249,6 @@
     },
     "matrss": {
         "name": "Matthias Riße",
-        "blog": "https://github.com/matrss",
-        "orcid": "0009-0006-6081-9327",
-        "affiliation": "Institute of Energy and Climate Research - Stratosphere (IEK-7), Research Centre Jülich, Jülich, Germany"
+        "blog": "https://github.com/matrss"
     }
 }
\ No newline at end of file
diff --git a/.zenodo.json b/.zenodo.json
index d7bba8a0c..b5481a5f0 100644
--- a/.zenodo.json
+++ b/.zenodo.json
@@ -129,8 +129,7 @@
         },
         {
             "affiliation": "Institute of Energy and Climate Research - Stratosphere (IEK-7), Research Centre Jülich, Jülich, Germany",
-            "name": "Riße, Matthias",
-            "orcid": "0009-0006-6081-9327"
+            "name": "Riße, Matthias"
         },
         {
             "affiliation": "Eberhard Karls Universität Tübingen",
diff --git a/changelog.d/pr-7390.md b/changelog.d/pr-7390.md
deleted file mode 100644
index 53ad523d6..000000000
--- a/changelog.d/pr-7390.md
+++ /dev/null
@@ -1,7 +0,0 @@
-### 🐛 Bug Fixes
-
-- Ensure that data generated by timeout handlers in the asynchronous
-  runner are accessible via the result generator, even if no other
-  other events occur.  
-  [PR #7390](https://github.com/datalad/datalad/pull/7390)
-  (by [@christian-monch](https://github.com/christian-monch))
diff --git a/changelog.d/pr-7392.md b/changelog.d/pr-7392.md
deleted file mode 100644
index af2a493a2..000000000
--- a/changelog.d/pr-7392.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### 🏠 Internal
-
-- Discontinue ConfigManager abuse for Git identity warning.  [PR #7378](https://github.com/datalad/datalad/pull/7378) (by [@mih](https://github.com/mih)) and [PR #7392](https://github.com/datalad/datalad/pull/7392) (by [@yarikoptic](https://github.com/yarikoptic))
diff --git a/changelog.d/pr-7398.md b/changelog.d/pr-7398.md
deleted file mode 100644
index fcdae5507..000000000
--- a/changelog.d/pr-7398.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### 🐛 Bug Fixes
-
-- BF: Run an actual git invocation with interactive commit config.  [PR #7398](https://github.com/datalad/datalad/pull/7398) (by [@adswa](https://github.com/adswa))
diff --git a/changelog.d/pr-7407.md b/changelog.d/pr-7407.md
new file mode 100644
index 000000000..9dc17a01e
--- /dev/null
+++ b/changelog.d/pr-7407.md
@@ -0,0 +1,3 @@
+### 🚀 Enhancements and New Features
+
+- Address gitlab API special character restrictions.  [PR #7407](https://github.com/datalad/datalad/pull/7407) (by [@jsheunis](https://github.com/jsheunis))
diff --git a/changelog.d/pr-7413.md b/changelog.d/pr-7413.md
deleted file mode 100644
index 119d51bf5..000000000
--- a/changelog.d/pr-7413.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### 🧪 Tests
-
-- Boost python to 3.8 during extensions testing.  [PR #7413](https://github.com/datalad/datalad/pull/7413) (by [@yarikoptic](https://github.com/yarikoptic))
diff --git a/datalad/__init__.py b/datalad/__init__.py
index a8dd88195..7f47f993d 100644
--- a/datalad/__init__.py
+++ b/datalad/__init__.py
@@ -105,12 +105,9 @@ except ImportError as e:
 # Other imports are interspersed with lgr.debug to ease troubleshooting startup
 # delays etc.
 
-from .config import (
-    ConfigManager,
-    warn_on_undefined_git_identity,
-)
+from .config import ConfigManager
+
 cfg = ConfigManager()
-warn_on_undefined_git_identity(cfg)
 
 
 # must come after config manager
diff --git a/datalad/cmd.py b/datalad/cmd.py
index 51e5b02d7..c515b8dcc 100644
--- a/datalad/cmd.py
+++ b/datalad/cmd.py
@@ -496,10 +496,7 @@ class BatchedCommand(SafeDelCloseMixin):
                 #  when handling multiple instructions at once. Until this is
                 #  done properly, communication timeouts are ignored in order
                 #  to avoid errors.
-                if data is None:
-                    lgr.debug('BatchedCommand: timeout on process')
-                else:
-                    lgr.debug('BatchedCommand: timeout on file descriptor %d', data)
+                pass
             else:
                 raise ValueError(f"{self}: unknown source: {source}")
 
diff --git a/datalad/config.py b/datalad/config.py
index eda51a84f..291de339c 100644
--- a/datalad/config.py
+++ b/datalad/config.py
@@ -205,7 +205,7 @@ def _gitcfg_rec_to_keyvalue(rec):
     -------
     str, str
       Parsed key and value. Key and/or value could be None
-      if not syntax-compliant (former) or absent (latter).
+      if not snytax-compliant (former) or absent (latter).
     """
     kv_match = cfg_kv_regex.match(rec)
     if kv_match:
@@ -304,6 +304,9 @@ class ConfigManager(object):
       Note: 'dataset' and 'dataset-local' are deprecated in favor of 'branch'
       and 'branch-local'.
     """
+
+    _checked_git_identity = False
+
     # Lock for running changing operation across multiple threads.
     # Since config itself to the same path could
     # potentially be created independently in multiple threads, and we might be
@@ -397,6 +400,20 @@ class ConfigManager(object):
 
         self.reload(force=True)
 
+        if not ConfigManager._checked_git_identity:
+            for cfg, envs in (
+                    ('user.name', ('GIT_AUTHOR_NAME', 'GIT_COMMITTER_NAME')),
+                    ('user.email', ('GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL'))):
+                if cfg not in self \
+                        and not any(e in os.environ for e in envs):
+                    lgr.warning(
+                        "It is highly recommended to configure Git before using "
+                        "DataLad. Set both 'user.name' and 'user.email' "
+                        "configuration variables."
+                    )
+                    break  # one warning enough
+            ConfigManager._checked_git_identity = True
+
     def reload(self, force=False):
         """Reload all configuration items from the configured sources
 
@@ -1149,18 +1166,3 @@ def write_config_section(fobj, suite, name, props):
             _q_='' if quoted_name.startswith('"') else '"',
             _name_=quoted_name,
             **{k: quote_config(v) for k, v in props.items()}))
-
-
-def warn_on_undefined_git_identity(cfg: ConfigManager):
-    """Check whether a Git identity is defined, and warn if not"""
-    for cfgkey, envs in (
-            ('user.name', ('GIT_AUTHOR_NAME', 'GIT_COMMITTER_NAME')),
-            ('user.email', ('GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL'))):
-        if cfgkey not in cfg \
-                and not any(e in os.environ for e in envs):
-            lgr.warning(
-                "It is highly recommended to configure Git before using "
-                "DataLad. Set both 'user.name' and 'user.email' "
-                "configuration variables."
-            )
-            break  # one warning enough
diff --git a/datalad/distributed/create_sibling_gitlab.py b/datalad/distributed/create_sibling_gitlab.py
index b32fb9d8c..023eb02fb 100644
--- a/datalad/distributed/create_sibling_gitlab.py
+++ b/datalad/distributed/create_sibling_gitlab.py
@@ -63,8 +63,8 @@ class CreateSiblingGitlab(Interface):
     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-usage.html#configuration-file-format
-    for details), such as::
+    https://python-gitlab.readthedocs.io/en/stable/cli.html#configuration for
+    details), such as::
 
       [mygit]
       url = https://git.example.com
@@ -79,19 +79,22 @@ class CreateSiblingGitlab(Interface):
 
     "hierarchy"
       Each dataset is placed into its own group, and the actual GitLab
-      project for a dataset is put in a project named "_repo_" inside
-      this group. Using this layout, arbitrarily deep hierarchies of
+      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 '--'.
+      characters replaced by '-'. This path separator is configurable
+      (see Configuration).
     "collection"
-      This is a hybrid layout, where the root dataset is placed in a "_repo_"
+      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.
+      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
@@ -100,8 +103,8 @@ class CreateSiblingGitlab(Interface):
 
     *Configuration*
 
-    All configuration switches and options for GitLab sibling creation can
-    be provided arguments to the command. However, it is also possible to
+    Many configuration switches and options for GitLab sibling creation can
+    be provided as 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:
@@ -116,9 +119,18 @@ class CreateSiblingGitlab(Interface):
     "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
+        Project location/path used for datasets at GitLab instance
         SITENAME (see --project). Configuring this is useful for deriving
-        project paths for subdatasets, relative to superdataset.
+        project paths for subdatasets, relative to a superdataset.
+    "datalad.gitlab-default-projectname"
+        The hierarchy and collection layouts publish (super)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 their path within the superdataset, with the regular path separator replaced
+        with a "-": superdataset-subdataset. This configuration can be used to override
+        this default separator.
 
     This command can be configured with
     "datalad.create-sibling-ghlike.extra-remote-settings.NETLOC.KEY=VALUE" in
@@ -395,6 +407,9 @@ def _proc_dataset(refds, ds, site, project, remotename, layout, existing,
             "Unknown site access '{}' given or configured, "
             "known ones are: {}".format(access, known_access_labels))
 
+    pathsep = ds.config.get("datalad.gitlab-default-pathseparator", "-")
+    project_stub = \
+        ds.config.get("datalad.gitlab-default-projectname", "project")
     project_var = 'datalad.gitlab-{}-project'.format(site)
     process_root = refds == ds
     if project is None:
@@ -403,7 +418,7 @@ def _proc_dataset(refds, ds, site, project, remotename, layout, existing,
 
     if project and process_root and layout == 'collection':
         # the root of a collection
-        project = '{}/_repo_'.format(project)
+        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)
@@ -412,15 +427,16 @@ def _proc_dataset(refds, ds, site, project, remotename, layout, existing,
             # the reference dataset configuration
             rproject = ds.pathobj.relative_to(refds.pathobj).as_posix()
             if layout == 'hierarchy':
-                project = '{}/{}/_repo_'.format(ref_project, rproject)
+                project = f'{ref_project}/{rproject}/{project_stub}'
             elif layout == 'collection':
                 project = '{}/{}'.format(
                     ref_project,
-                    rproject.replace('/', '--'))
+                    rproject.replace('/', pathsep))
             else:
-                project = '{}--{}'.format(
+                project = '{}{}{}'.format(
                     ref_project,
-                    rproject.replace('/', '--'))
+                    pathsep,
+                    rproject.replace('/', pathsep))
 
     if project is None:
         yield dict(
diff --git a/datalad/runner/nonasyncrunner.py b/datalad/runner/nonasyncrunner.py
index 8340ab1df..3bad170ef 100644
--- a/datalad/runner/nonasyncrunner.py
+++ b/datalad/runner/nonasyncrunner.py
@@ -552,35 +552,21 @@ class ThreadedRunner:
         else:
             self._handle_file_timeout(source)
 
-    def _update_timeouts(self) -> bool:
+    def _update_timeouts(self):
         last_touched = list(self.last_touched.items())
         new_times = dict()
         current_time = time.time()
-        timeout_occurred = False
         for source, last_time in last_touched:
-            if self.timeout is not None and current_time - last_time >= self.timeout:
+            if current_time - last_time >= self.timeout:
                 new_times[source] = current_time
                 self._handle_source_timeout(source)
-                timeout_occurred = True
         self.last_touched = {
             **self.last_touched,
             **new_times}
-        return timeout_occurred
 
-    def process_timeouts(self) -> bool:
-        """Check for timeouts
-
-        This method checks whether a timeout occurred since
-        it was called last. If a timeout occurred, the timeout
-        handler is called.
-
-        Returns: bool
-            Return `True` if at least one timeout occurred,
-            `False` if no timeout occurred.
-        """
+    def process_timeouts(self):
         if self.timeout is not None:
-            return self._update_timeouts()
-        return False
+            self._update_timeouts()
 
     def should_continue(self) -> bool:
         # Continue with queue processing if there is still a process or
@@ -640,8 +626,7 @@ class ThreadedRunner:
             except Empty:
                 if self.check_for_stall() is True:
                     return
-                if self.process_timeouts():
-                    return
+                self.process_timeouts()
                 continue
 
         if state == IOState.process_exit:
diff --git a/datalad/support/gitrepo.py b/datalad/support/gitrepo.py
index 977a13a9e..fed91cd52 100644
--- a/datalad/support/gitrepo.py
+++ b/datalad/support/gitrepo.py
@@ -1545,7 +1545,7 @@ class GitRepo(CoreGitRepo):
 
         # handle interactive message entry by running another `git-commit`
         self._git_runner.run(
-            self._git_cmd_prefix + cmd + ['--amend', '--edit'],
+            cmd + ['--amend', '--edit'],
             protocol=NoCapture,
             stdin=None,
             env=env,


Test patch:
diff --git a/.github/workflows/test_extensions.yml b/.github/workflows/test_extensions.yml
index bc9610fa8..b51906dc8 100644
--- a/.github/workflows/test_extensions.yml
+++ b/.github/workflows/test_extensions.yml
@@ -39,10 +39,10 @@ jobs:
       with:
         fetch-depth: 0
 
-    - name: Set up Python 3.8
+    - name: Set up Python 3.7
       uses: actions/setup-python@v4
       with:
-        python-version: 3.8
+        python-version: 3.7
 
     - name: Install DataLad and dependencies
       run: |
diff --git a/datalad/distributed/tests/test_create_sibling_gitlab.py b/datalad/distributed/tests/test_create_sibling_gitlab.py
index 5095d9d72..d6fed6617 100644
--- a/datalad/distributed/tests/test_create_sibling_gitlab.py
+++ b/datalad/distributed/tests/test_create_sibling_gitlab.py
@@ -40,7 +40,7 @@ def _get_nested_collections(path):
     return dict(
         root=ds,
         c1=c1,
-        c1s1=c1s2,
+        c1s1=c1s1,
         c1s2=c1s2,
         c2=c2,
         c2s1=c2s1,
@@ -160,10 +160,10 @@ def test_dryrun(path=None):
         site='theone', sibling='dieter',
         # hierarchical setup: directories becomes groups
         # which implies each dataset is in its own group
-        # project itself is placed at '_repo'_ to give URLs like
-        # http://site/dir/dir/dir/_repo_.git
+        # project itself is placed at 'project' to give URLs like
+        # http://site/dir/dir/dir/project.git
         # as a balance between readability and name conflict minimization
-        project='secret/{}/_repo_'.format(
+        project='secret/{}/project'.format(
             ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj).as_posix()),
     )
     # we get the same result with an explicit layout request
@@ -176,27 +176,27 @@ def test_dryrun(path=None):
         path='subdir', dry_run=True)
     assert_result_count(
         res, 1, path=ctlg['c1'].path, type='dataset', status='ok',
-        # http://site/group/dir--dir--dir--name.git
+        # http://site/group/dir-dir-dir-name.git
         project='secret/{}'.format(str(
             ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(
-                os.sep, '--')),
+                os.sep, '-')),
     )
     # make sure the reference dataset does not conflict with its group in this
     # case
     res = ctlg['root'].create_sibling_gitlab(dry_run=True)
     assert_result_count(
         res, 1, path=ctlg['root'].path, type='dataset', status='ok',
-        project='secret/_repo_')
+        project='secret/project')
     # "flat" does GitHub-style
     ctlg['root'].config.set('datalad.gitlab-theone-layout', 'flat')
     res = ctlg['root'].create_sibling_gitlab(
         path='subdir', dry_run=True)
     assert_result_count(
         res, 1, path=ctlg['c1'].path, type='dataset', status='ok',
-        # http://site/base--dir--dir--dir--name.git
-        project='secret--{}'.format(str(
+        # http://site/base-dir-dir-dir-name.git
+        project='secret-{}'.format(str(
             ctlg['c1'].pathobj.relative_to(ctlg['root'].pathobj)).replace(
-                os.sep, '--')),
+                os.sep, '-')),
     )
 
     # the results do not depend on explicitly given datasets, if we just enter
@@ -227,12 +227,12 @@ def test_dryrun(path=None):
         sorted(r['project'] for r in res),
         [
             'secret',
-            'secret/collection2/_repo_',
-            'secret/collection2/sub1/_repo_',
-            'secret/collection2/sub1/deepsub1/_repo_',
-            'secret/subdir/collection1/_repo_',
-            'secret/subdir/collection1/sub1/_repo_',
-            'secret/subdir/collection1/sub2/_repo_',
+            'secret/collection2/project',
+            'secret/collection2/sub1/deepsub1/project',
+            'secret/collection2/sub1/project',
+            'secret/subdir/collection1/project',
+            'secret/subdir/collection1/sub1/project',
+            'secret/subdir/collection1/sub2/project',
         ]
     )
     res = ctlg['root'].create_sibling_gitlab(
@@ -241,13 +241,13 @@ def test_dryrun(path=None):
     eq_(
         sorted(r['project'] for r in res),
         [
-            'secret/_repo_',
             'secret/collection2',
-            'secret/collection2--sub1',
-            'secret/collection2--sub1--deepsub1',
-            'secret/subdir--collection1',
-            'secret/subdir--collection1--sub1',
-            'secret/subdir--collection1--sub2',
+            'secret/collection2-sub1',
+            'secret/collection2-sub1-deepsub1',
+            'secret/project',
+            'secret/subdir-collection1',
+            'secret/subdir-collection1-sub1',
+            'secret/subdir-collection1-sub2',
         ],
     )
     res = ctlg['root'].create_sibling_gitlab(
@@ -257,14 +257,63 @@ def test_dryrun(path=None):
         sorted(r['project'] for r in res),
         [
             'secret',
-            'secret--collection2',
-            'secret--collection2--sub1',
-            'secret--collection2--sub1--deepsub1',
-            'secret--subdir--collection1',
-            'secret--subdir--collection1--sub1',
-            'secret--subdir--collection1--sub2',
+            'secret-collection2',
+            'secret-collection2-sub1',
+            'secret-collection2-sub1-deepsub1',
+            'secret-subdir-collection1',
+            'secret-subdir-collection1-sub1',
+            'secret-subdir-collection1-sub2',
         ],
     )
+    # test that the configurations work
+    ctlg['root'].config.set("datalad.gitlab-default-projectname", 'myownname')
+    ctlg['c1s1'].config.set("datalad.gitlab-default-pathseparator", '+')
+    #import pdb; pdb.set_trace()
+    res = ctlg['root'].create_sibling_gitlab(
+        recursive=True, layout='flat', dry_run=True)
+    assert_result_count(res, len(ctlg))
+    eq_(
+        sorted(r['project'] for r in res),
+        [
+            'secret',
+            'secret+subdir+collection1+sub1',
+            'secret-collection2',
+            'secret-collection2-sub1',
+            'secret-collection2-sub1-deepsub1',
+            'secret-subdir-collection1',
+            'secret-subdir-collection1-sub2',
+        ],
+    )
+    res = ctlg['root'].create_sibling_gitlab(
+        recursive=True, layout='collection', dry_run=True)
+    assert_result_count(res, len(ctlg))
+    eq_(
+        sorted(r['project'] for r in res),
+        [
+            'secret/collection2',
+            'secret/collection2-sub1',
+            'secret/collection2-sub1-deepsub1',
+            'secret/myownname',
+            'secret/subdir+collection1+sub1',
+            'secret/subdir-collection1',
+            'secret/subdir-collection1-sub2',
+        ],
+    )
+    ctlg['c1s1'].config.set("datalad.gitlab-default-projectname", 'myownname')
+    res = ctlg['root'].create_sibling_gitlab(recursive=True, dry_run=True)
+    assert_result_count(res, len(ctlg))
+    eq_(
+        sorted(r['project'] for r in res),
+        [
+            'secret',
+            'secret/collection2/project',
+            'secret/collection2/sub1/deepsub1/project',
+            'secret/collection2/sub1/project',
+            'secret/subdir/collection1/project',
+            'secret/subdir/collection1/sub1/myownname',
+            'secret/subdir/collection1/sub2/project',
+        ]
+    )
 
 
 class _FakeGitLab(object):
diff --git a/datalad/tests/test_base.py b/datalad/tests/test_base.py
index 30b782186..7dd5c127b 100644
--- a/datalad/tests/test_base.py
+++ b/datalad/tests/test_base.py
@@ -9,10 +9,8 @@
 
 import os
 import os.path as op
-import sys
 from unittest.mock import patch
 
-from datalad.cmd import WitlessRunner, StdOutErrCapture
 from datalad.tests.utils_pytest import (
     SkipTest,
     assert_in,
@@ -85,7 +83,10 @@ def test_git_config_warning(path=None):
     with chpwd(path), \
             patch.dict('os.environ', patched_env, clear=True), \
             swallow_logs(new_level=30) as cml:
-        out = WitlessRunner().run(
-            [sys.executable, '-c', 'import datalad'],
-            protocol=StdOutErrCapture)
-        assert_in("configure Git before", out['stderr'])
+        # no configs in that empty HOME
+        from datalad.config import ConfigManager
+        # reach into the class and disable the "checked" flag that
+        # has already been tripped before we get here
+        with patch.object(ConfigManager, "_checked_git_identity", False):
+            ConfigManager()
+            assert_in("configure Git before", cml.out)