code: def test_siblings(origin=None, repo_path=None, local_clone_path=None):
      ca = dict(result_renderer="disabled")
      # a remote dataset with a subdataset underneath
      origds = Dataset(origin).create(**ca)
      _ = origds.create("subm 1", **ca)
  
      sshurl = "ssh://push-remote.example.com"
      httpurl1 = "http://remote1.example.com/location"
      httpurl2 = "http://remote2.example.com/location"
  
      # insufficient arguments
      # we need a dataset to work at
      with chpwd(repo_path):  # not yet there
          assert_raises(
              InsufficientArgumentsError,
              siblings,
              "add",
              url=httpurl1,
              **ca,
          )
  
      # prepare src
      source = install(repo_path, source=origin, recursive=True, **ca)
      # pollute config
      depvar = "remote.test-remote.datalad-publish-depends"
      source.config.add(depvar, "stupid", scope="local")
  
      # cannot configure unknown remotes as dependencies
      res = siblings(
          "configure",
          dataset=source,
          name="test-remote",
          url=httpurl1,
          publish_depends=["r1", "r2"],
          on_failure="ignore",
          **ca,
      )
      assert_status("error", res)
      eq_(
          res[0]["message"],
          (
              "unknown sibling(s) specified as publication dependency: %s",
              set(("r1", "r2")),
          ),
      )
      # prior config was not changed by failed call above
      eq_(source.config.get(depvar, None), "stupid")
  
      res = siblings(
          "configure",
          dataset=source,
          name="test-remote",
          url=httpurl1,
          result_xfm="paths",
          **ca,
      )
  
      eq_(res, [source.path])
      assert_in("test-remote", source.repo.get_remotes())
      eq_(httpurl1, source.repo.get_remote_url("test-remote"))
  
      # reconfiguring doesn't change anything
      siblings(
          "configure",
          dataset=source,
          name="test-remote",
          url=httpurl1,
          **ca,
      )
      assert_in("test-remote", source.repo.get_remotes())
      eq_(httpurl1, source.repo.get_remote_url("test-remote"))
      # re-adding doesn't work
      res = siblings(
          "add",
          dataset=source,
          name="test-remote",
          url=httpurl1,
          on_failure="ignore",
          **ca,
      )
      assert_status("error", res)
      # only after removal
      res = siblings("remove", dataset=source, name="test-remote", **ca)
      assert_status("ok", res)
      assert_not_in("test-remote", source.repo.get_remotes())
      # remove again (with result renderer to smoke-test a renderer
      # special case for this too)
      res = siblings("remove", dataset=source, name="test-remote", **ca)
      assert_status("notneeded", res)
  
      res = siblings(
          "add",
          dataset=source,
          name="test-remote",
          url=httpurl1,
          on_failure="ignore",
          **ca,
      )
      assert_status("ok", res)
  
      # add another remove with a publication dependency
      # again pre-pollute config
      depvar = "remote.test-remote2.datalad-publish-depends"
      pushvar = "remote.test-remote2.push"
      source.config.add(depvar, "stupid", scope="local")
      source.config.add(pushvar, "senseless", scope="local")
      res = siblings(
          "configure",
          dataset=source,
          name="test-remote2",
          url=httpurl2,
          on_failure="ignore",
          publish_depends="test-remote",
          # just for smoke testing
          publish_by_default=DEFAULT_BRANCH,
          **ca,
      )
      assert_status("ok", res)
      # config replaced with new setup
      # source.config.reload(force=True)
      eq_(source.config.get(depvar, None), "test-remote")
      eq_(source.config.get(pushvar, None), DEFAULT_BRANCH)
  
      # add to another remote automagically taking it from the url
      # and being in the dataset directory
      with chpwd(source.path):
          res = siblings("add", url=httpurl2, **ca)
      assert_result_count(
          res, 1, name="remote2.example.com", type="sibling"
      )
      assert_in("remote2.example.com", source.repo.get_remotes())
  
      # don't fail with conflicting url, when using force:
      res = siblings(
          "configure",
          dataset=source,
          name="test-remote",
          url=httpurl1 + "/elsewhere",
          **ca,
      )
      assert_status("ok", res)
      eq_(
          httpurl1 + "/elsewhere",
          source.repo.get_remote_url("test-remote"),
      )
  
      # no longer a use case, I would need additional convincing that
      # this is anyhow useful other then triple checking other peoples
      # errors. for an actual check use 'query'
      # maybe it could be turned into a set of warnings when `configure`
      # alters an existing setting, but then why call configure, if you
      # want to keep the old values
      # with assert_raises(RuntimeError) as cm:
      #    add_sibling(dataset=source, name="test-remote",
      #                url=httpurl1 + "/elsewhere")
      # assert_in("""'test-remote' already exists with conflicting settings""",
      #          str(cm.value))
      ## add a push url without force fails, since in a way the fetch url is the
      ## configured push url, too, in that case:
      # with assert_raises(RuntimeError) as cm:
      #    add_sibling(dataset=source, name="test-remote",
      #                url=httpurl1 + "/elsewhere",
      #                pushurl=sshurl, force=False)
      # assert_in("""'test-remote' already exists with conflicting settings""",
      #          str(cm.value))
  
      # add push url (force):
      res = siblings(
          "configure",
          dataset=source,
          name="test-remote",
          url=httpurl1 + "/elsewhere",
          pushurl=sshurl,
          **ca,
      )
      assert_status("ok", res)
      eq_(
          httpurl1 + "/elsewhere",
          source.repo.get_remote_url("test-remote"),
      )
      eq_(sshurl, source.repo.get_remote_url("test-remote", push=True))
  
      # recursively:
      for r in siblings(
          "configure",
          dataset=source,
          name="test-remote",
          url=httpurl1 + "/%NAME",
          pushurl=sshurl + "/%NAME",
          recursive=True,
          # we need to disable annex queries, as it will try to access
          # the fake URL configured above
          get_annex_info=False,
          **ca,
      ):
          repo = GitRepo(r["path"], create=False)
          assert_in("test-remote", repo.get_remotes())
          url = repo.get_remote_url("test-remote")
          pushurl = repo.get_remote_url("test-remote", push=True)
          ok_(url.startswith(httpurl1 + "/" + basename(source.path)))
          ok_(url.endswith(basename(repo.path)))
          ok_(pushurl.startswith(sshurl + "/" + basename(source.path)))
          ok_(pushurl.endswith(basename(repo.path)))
          eq_(url, r["url"])
          eq_(pushurl, r["pushurl"])
  
      # recursively without template:
      for r in siblings(
          "configure",
          dataset=source,
          name="test-remote-2",
          url=httpurl1,
          pushurl=sshurl,
          recursive=True,
          # we need to disable annex queries, as it will try to access
          # the fake URL configured above
          get_annex_info=False,
          **ca,
      ):
          repo = GitRepo(r["path"], create=False)
          assert_in("test-remote-2", repo.get_remotes())
          url = repo.get_remote_url("test-remote-2")
          pushurl = repo.get_remote_url("test-remote-2", push=True)
          ok_(url.startswith(httpurl1))
          ok_(pushurl.startswith(sshurl))
          # FIXME: next condition used to compare the *Repo objects instead of
          # there paths. Due to missing annex-init in
          # datalad/tests/utils.py:clone_url this might not be the same, since
          # `source` actually is an annex, but after flavor 'clone' in
          # `with_testrepos` and then `install` any trace of an annex might be
          # gone in v5 (branch 'master' only), while in direct mode it still is
          # considered an annex. `repo` is forced to be a `GitRepo`, so we might
          # compare two objects of different classes while they actually are
          # pointing to the same repository.
          # See github issue #1854
          if repo.path != source.repo.path:
              ok_(url.endswith("/" + basename(repo.path)))
              ok_(pushurl.endswith(basename(repo.path)))
          eq_(url, r["url"])
          eq_(pushurl, r["pushurl"])
  
      # recursively without template and pushurl but full "hierarchy"
      # to a local clone
      for r in siblings(
          "configure",
          dataset=source,
          name="test-remote-3",
          url=local_clone_path,
          recursive=True,
          # we need to disable annex queries, as it will try to access
          # the fake URL configured above
          get_annex_info=False,
          **ca,
      ):
          repo = GitRepo(r["path"], create=False)
          assert_in("test-remote-3", repo.get_remotes())
          url = repo.get_remote_url("test-remote-3")
          pushurl = repo.get_remote_url("test-remote-3", push=True)
  
          eq_(
              normpath(url),
              normpath(
                  opj(
                      local_clone_path,
                      relpath(str(r["path"]), source.path),
                  )
              ),
          )
          # https://github.com/datalad/datalad/issues/3951
          ok_(not pushurl)  # no pushurl should be defined
      # 5621: Users shouldn't pass identical names for remote & common data source
      assert_raises(
          ValueError,
          siblings,
          "add",
          dataset=source,
          name="howdy",
          url=httpurl1,
          as_common_datasrc="howdy",
      )
  
file_path: datalad/datalad/distribution/tests/test_siblings.py
imports: from datalad.api import Dataset, clone, create, install, siblings
  from datalad.support.exceptions import InsufficientArgumentsError
  from datalad.support.gitrepo import GitRepo
  from datalad.support.path import basename
  from datalad.support.path import join
  from datalad.support.path import normpath, relpath
  from datalad.tests.utils_pytest import DEFAULT_BRANCH, DEFAULT_REMOTE, assert_false, assert_in, assert_in_results, assert_not_in, assert_raises, assert_result_count, assert_status, chpwd, create_tree, eq_, ok_, on_appveyor, serve_path_via_http, with_sameas_remote, with_tempfile
  from datalad.utils import Path, on_windows
  import pytest
label_desc: Use image node_id 1 for calling a global function with example usage: test_siblings(origin, repo_path, local_clone_path) without return types