class_docstr: Create a dataset sibling on a UNIX-like Shell (local or SSH)-accessible machine
  
  Given a local dataset, and a path or SSH login information this command
  creates a remote dataset repository and configures it as a dataset sibling
  to be used as a publication target (see `publish` command).
  
  Various properties of the remote sibling can be configured (e.g. name
  location on the server, read and write access URLs, and access
  permissions.
  
  Optionally, a basic web-viewer for DataLad datasets can be installed
  at the remote location.
  
  This command supports recursive processing of dataset hierarchies, creating
  a remote sibling for each dataset in the hierarchy. By default, remote
  siblings are created in hierarchical structure that reflects the
  organization on the local file system. However, a simple templating
  mechanism is provided to produce a flat list of datasets (see
  --target-dir).
code: def __call__(
      sshurl,
      *,
      name=None,
      target_dir=None,
      target_url=None,
      target_pushurl=None,
      dataset=None,
      recursive=False,
      recursion_limit=None,
      existing="error",
      shared=None,
      group=None,
      ui=False,
      as_common_datasrc=None,
      publish_by_default=None,
      publish_depends=None,
      annex_wanted=None,
      annex_group=None,
      annex_groupwanted=None,
      inherit=False,
      since=None,
  ):
      if ui:
          # the webui has been moved to the deprecated extension
          try:
              from datalad_deprecated.sibling_webui import (
                  upload_web_interface,
              )
          except Exception as e:
              # we could just test for ModuleNotFoundError (which should be
              # all that would happen with PY3.6+, but be a little more robust
              # and use the pattern from duecredit
              if type(e).__name__ not in (
                  "ImportError",
                  "ModuleNotFoundError",
              ):
                  lgr.error(
                      "Failed to import datalad_deprecated.sibling_webui "
                      "due to %s",
                      str(e),
                  )
              raise RuntimeError(
                  "The DataLad web UI has been moved to an extension "
                  "package. Please install the Python package "
                  "`datalad_deprecated` to be able to deploy it."
              )
  
      # push uses '^' to annotate the previous pushed committish, and None for default
      # behavior. '' was/is (to be deprecated) used in `publish` and 'create-sibling'.
      # Alert user about the mistake
      if since == "":
          # deprecation was added prior 0.16.0
          import warnings
  
          warnings.warn(
              "'since' should point to commitish or use '^'.",
              DeprecationWarning,
          )
          since = "^"
  
      #
      # nothing without a base dataset
      #
      ds = require_dataset(
          dataset, check_installed=True, purpose="create sibling(s)"
      )
      refds_path = ds.path
  
      #
      # all checks that are possible before we start parsing the dataset
      #
      if since and not recursive:
          raise ValueError("The use of 'since' requires 'recursive'")
      # possibly use sshurl to get the name in case if not specified
      if not sshurl:
          if not inherit:
              raise InsufficientArgumentsError(
                  "needs at least an SSH URL, if no inherit option"
              )
          if name is None:
              raise ValueError(
                  "Neither SSH URL, nor the name of sibling to inherit from "
                  "was specified"
              )
          # It might well be that we already have this remote setup
          try:
              sshurl = CreateSibling._get_remote_url(ds, name)
          except Exception as exc:
              ce = CapturedException(exc)
              lgr.debug(
                  "%s does not know about url for %s: %s", ds, name, ce
              )
      elif inherit:
          raise ValueError(
              "For now, for clarity not allowing specifying a custom sshurl "
              "while inheriting settings"
          )
          # may be could be safely dropped -- still WiP
  
      if not sshurl:
          # TODO: may be more back up before _prep?
          super_ds = ds.get_superdataset()
          if not super_ds:
              raise ValueError(
                  "Could not determine super dataset for %s to inherit URL"
                  % ds
              )
          super_url = CreateSibling._get_remote_url(super_ds, name)
          # for now assuming hierarchical setup
          # (TODO: to be able to distinguish between the two, probably
          # needs storing datalad.*.target_dir to have %RELNAME in there)
          sshurl = slash_join(
              super_url, relpath(refds_path, super_ds.path)
          )
  
      # check the login URL
      sibling_ri = RI(sshurl)
      ssh_sibling = is_ssh(sibling_ri)
      if not (ssh_sibling or isinstance(sibling_ri, PathRI)):
          raise ValueError(
              "Unsupported SSH URL or path: '{0}', "
              "use ssh://host/path, host:path or path syntax".format(
                  sshurl
              )
          )
  
      if not name:
          name = sibling_ri.hostname if ssh_sibling else "local"
          lgr.info(
              "No sibling name given. Using %s'%s' as sibling name",
              "URL hostname " if ssh_sibling else "",
              name,
          )
      if since == "^":
          # consider creating siblings only since the point of
          # the last update
          # XXX here we assume one to one mapping of names from local branches
          # to the remote
          active_branch = ds.repo.get_active_branch()
          since = "%s/%s" % (name, active_branch)
  
      to_process = []
      if recursive:
          #
          # parse the base dataset to find all subdatasets that need processing
          #
          cand_ds = [
              Dataset(r["path"])
              for r in diff_dataset(
                  ds,
                  fr=since,
                  to="HEAD",
                  # w/o False we might not follow into new subdatasets
                  # which do not have that remote yet setup,
                  # see https://github.com/datalad/datalad/issues/6596
                  constant_refs=False,
                  # save cycles, we are only looking for datasets
                  annex=None,
                  untracked="no",
                  recursive=True,
                  datasets_only=True,
              )
              # not installed subdatasets would be 'clean' so we would skip them
              if r.get("type") == "dataset"
              and r.get("state", None) != "clean"
          ]
          if not since:
              # not only subdatasets
              cand_ds = [ds] + cand_ds
      else:
          # only the current ds
          cand_ds = [ds]
      # check remotes setup()
      for d in cand_ds:
          d_repo = d.repo
          if d_repo is None:
              continue
          checkds_remotes = d.repo.get_remotes()
          res = dict(
              action="create_sibling",
              path=d.path,
              type="dataset",
          )
  
          if publish_depends:
              # make sure dependencies are valid
              # TODO: inherit -- we might want to automagically create
              # those dependents as well???
              unknown_deps = set(
                  ensure_list(publish_depends)
              ).difference(checkds_remotes)
              if unknown_deps:
                  yield dict(
                      res,
                      status="error",
                      message=(
                          "unknown sibling(s) specified as publication "
                          "dependency: %s",
                          unknown_deps,
                      ),
                  )
                  continue
          if name in checkds_remotes and existing in ("error", "skip"):
              yield dict(
                  res,
                  sibling_name=name,
                  status="error"
                  if existing == "error"
                  else "notneeded",
                  message=(
                      "sibling '%s' already configured (specify alternative "
                      "name, or force reconfiguration via --existing",
                      name,
                  ),
              )
              continue
          to_process.append(res)
  
      if not to_process:
          # we ruled out all possibilities
          # TODO wait for gh-1218 and make better return values
          lgr.info(
              "No datasets qualify for sibling creation. "
              "Consider different settings for --existing "
              "or --since if this is unexpected"
          )
          return
  
      if ssh_sibling:
          # request ssh connection:
          lgr.info("Connecting ...")
          shell = ssh_manager.get_connection(sshurl)
      else:
          shell = _RunnerAdapter()
          sibling_ri.path = str(resolve_path(sibling_ri.path, dataset))
          if target_dir:
              target_dir = opj(sibling_ri.path, target_dir)
  
      if target_dir is None:
          if sibling_ri.path:
              target_dir = sibling_ri.path
          else:
              target_dir = "."
  
      # TODO: centralize and generalize template symbol handling
      replicate_local_structure = "%RELNAME" not in target_dir
  
      if not shell.get_annex_version():
          raise MissingExternalDependency(
              "git-annex",
              msg="It's required on the {} machine to create a sibling".format(
                  "remote" if ssh_sibling else "local"
              ),
          )
  
      #
      # all checks done and we have a connection, now do something
      #
  
      # loop over all datasets, ordered from top to bottom to make test
      # below valid (existing directories would cause the machinery to halt)
      # But we need to run post-update hook in depth-first fashion, so
      # would only collect first and then run (see gh #790)
      yielded = set()
      remote_repos_to_run_hook_for = []
      for currentds_ap in sorted(
          to_process, key=lambda x: x["path"].count("/")
      ):
          current_ds = Dataset(currentds_ap["path"])
  
          path = _create_dataset_sibling(
              name,
              current_ds,
              refds_path,
              shell,
              replicate_local_structure,
              sibling_ri,
              target_dir,
              target_url,
              target_pushurl,
              existing,
              shared,
              group,
              publish_depends,
              publish_by_default,
              ui,
              as_common_datasrc,
              annex_wanted,
              annex_group,
              annex_groupwanted,
              inherit,
          )
          currentds_ap["sibling_name"] = name
          if not path:
              # nothing new was created
              # TODO is 'notneeded' appropriate in this case?
              currentds_ap["status"] = "notneeded"
              # TODO explain status in 'message'
              yield currentds_ap
              yielded.add(currentds_ap["path"])
              continue
          remote_repos_to_run_hook_for.append((path, currentds_ap))
  
          # publish web-interface to root dataset on publication server
          if current_ds.path == refds_path and ui:
              from datalad_deprecated.sibling_webui import (
                  upload_web_interface,
              )
  
              lgr.info("Uploading web interface to %s", path)
              try:
                  upload_web_interface(path, shell, shared, ui)
              except CommandError as e:
                  ce = CapturedException(e)
                  currentds_ap["status"] = "error"
                  currentds_ap["message"] = (
                      "failed to push web interface to the remote datalad repository (%s)",
                      ce,
                  )
                  currentds_ap["exception"] = ce
                  yield currentds_ap
                  yielded.add(currentds_ap["path"])
                  continue
  
      # in reverse order would be depth first
      lgr.info("Running post-update hooks in all created siblings")
      # TODO: add progressbar
      for path, currentds_ap in remote_repos_to_run_hook_for[::-1]:
          # Trigger the hook
          lgr.debug(
              "Running hook for %s (if exists and executable)", path
          )
          try:
              shell(
                  "cd {} "
                  "&& ( [ -x hooks/post-update ] && hooks/post-update || true )"
                  "".format(sh_quote(_path_(path, ".git")))
              )
          except CommandError as e:
              ce = CapturedException(e)
              currentds_ap["status"] = "error"
              currentds_ap["message"] = (
                  "failed to run post-update hook under remote path %s (%s)",
                  path,
                  ce,
              )
              currentds_ap["exception"] = ce
              yield currentds_ap
              yielded.add(currentds_ap["path"])
              continue
          if not currentds_ap["path"] in yielded:
              # if we were silent until now everything is just splendid
              currentds_ap["status"] = "ok"
              yield currentds_ap
  
imports: import logging
  import os
  from os.path import curdir
  import shlex
  from os.path import join
  from os.path import normpath, relpath
  from looseversion import LooseVersion
  from datalad import ssh_manager
  from datalad.cmd import CommandError, StdOutErrCapture, WitlessRunner
  from datalad.consts import TIMESTAMP_FMT, WEB_META_LOG
  from datalad.core.local.diff import diff_dataset
  from datalad.distribution.dataset import Dataset, EnsureDataset, datasetmethod, require_dataset, resolve_path
  from datalad.distribution.siblings import Siblings, _DelayedSuper
  from datalad.interface.base import Interface, build_doc, eval_results
  from datalad.interface.common_opts import annex_group_opt, annex_groupwanted_opt, annex_wanted_opt, as_common_datasrc, inherit_opt, publish_by_default, publish_depends, recursion_flag, recursion_limit
  from datalad.support.annexrepo import AnnexRepo
  from datalad.support.constraints import EnsureBool, EnsureChoice, EnsureNone, EnsureStr
  from datalad.support.exceptions import CapturedException, InsufficientArgumentsError, MissingExternalDependency
  from datalad.support.external_versions import external_versions
  from datalad.support.network import RI, PathRI, is_ssh
  from datalad.support.param import Parameter
  from datalad.ui import ui
  from datalad.utils import _path_, ensure_list, make_tempfile, on_windows
  from datalad.utils import quote_cmdlinearg
  from datalad.utils import slash_join
label_desc: Use image node_id 1 for calling the CreateSibling obj's underlying member method code with example usage: obj.__call__(sshurl) without return types