class_docstr: Push a dataset to a known :term:`sibling`.
  
  This makes a saved state of a dataset available to a sibling or special
  remote data store of a dataset. Any target sibling must already exist and
  be known to the dataset.
  
  || REFLOW >>
  By default, all files tracked in the last saved state (of the current
  branch) will be copied to the target location. Optionally, it is possible
  to limit a push to changes relative to a particular point in the version
  history of a dataset (e.g. a release tag) using the
  [CMD: --since CMD][PY: since PY] option in conjunction with the
  specification of a reference dataset. In recursive mode subdatasets will also be
  evaluated, and only those subdatasets are pushed where a change was
  recorded that is reflected in the current state of the top-level reference
  dataset.
  << REFLOW ||
  
  .. note::
    Power-user info: This command uses :command:`git push`, and :command:`git
    annex copy` to push a dataset. Publication targets are either configured
    remote Git repositories, or git-annex special remotes (if they support
    data upload).
class_docstr_tok: ["Push","a","dataset","to","a","known",":","term",":","`","sibling","`",".","This","makes","a","saved","state","of","a","dataset","available","to","a","sibling","or","special","remote","data","store","of","a","dataset",".","Any","target","sibling","must","already","exist","and","be","known","to","the","dataset",".","||","REFLOW",">",">","By","default",",","all","files","tracked","in","the","last","saved","state","(","of","the","current","branch",")","will","be","copied","to","the","target","location",".","Optionally",",","it","is","possible","to","limit","a","push","to","changes","relative","to","a","particular","point","in","the","version","history","of","a","dataset","(","e.g",".","a","release","tag",")","using","the","[","CMD",":","--","since","CMD","]","[","PY",":","since","PY","]","option","in","conjunction","with","the","specification","of","a","reference","dataset",".","In","recursive","mode","subdatasets","will","also","be","evaluated",",","and","only","those","subdatasets","are","pushed","where","a","change","was","recorded","that","is","reflected","in","the","current","state","of","the","top-level","reference","dataset",".","<","<","REFLOW","||","..","note",":",":","Power-user","info",":","This","command","uses",":","command",":","`","git","push","`",",","and",":","command",":","`","git","annex","copy","`","to","push","a","dataset",".","Publication","targets","are","either","configured","remote","Git","repositories",",","or","git-annex","special","remotes","(","if","they","support","data","upload",")","."]
code: def __call__(
      path=None,
      *,
      dataset=None,
      to=None,
      since=None,
      data="auto-if-wanted",
      force=None,
      recursive=False,
      recursion_limit=None,
      jobs=None,
  ):
      # push uses '^' to annotate the previous pushed committish, and None for default
      # behavior. '' was/is (to be deprecated) used in `publish`. Alert user about the mistake
      if since == "":
          raise ValueError(
              "'since' should point to commitish or use '^'."
          )
      # we resolve here, because we need to perform inspection on what was given
      # as an input argument further down
      paths = [resolve_path(p, dataset) for p in ensure_list(path)]
  
      ds = require_dataset(
          dataset, check_installed=True, purpose="push"
      )
      ds_repo = ds.repo
  
      res_kwargs = dict(
          action="publish",
          refds=ds.path,
          logger=lgr,
      )
  
      get_remote_kwargs = (
          {"exclude_special_remotes": False}
          if isinstance(ds_repo, AnnexRepo)
          else {}
      )
      if to and to not in ds_repo.get_remotes(**get_remote_kwargs):
          # get again for proper error:
          sr = ds_repo.get_remotes(**get_remote_kwargs)
          # yield an error result instead of raising a ValueError,
          # to enable the use case of pushing to a target that
          # a superdataset doesn't know, but some subdatasets to
          # (in combination with '--on-failure ignore')
          yield dict(
              res_kwargs,
              status="error",
              path=ds.path,
              message="Unknown push target '{}'. {}".format(
                  to,
                  "Known targets: {}.".format(
                      ", ".join(repr(s) for s in sr)
                  )
                  if sr
                  else "No targets configured in dataset.",
              ),
          )
          return
      if since == "^":
          # figure out state of remote branch and set `since`
          since = _get_corresponding_remote_state(ds_repo, to)
          if not since:
              lgr.info(
                  "No tracked remote for active branch, "
                  "detection of last pushed state not in effect."
              )
      elif since:
          # will blow with ValueError if unusable
          ds_repo.get_hexsha(since)
  
      # obtain a generator for information on the datasets to process
      # idea is to turn the `paths` argument into per-dataset
      # content listings that can be acted upon
      ds_spec = _datasets_since_(
          # important to pass unchanged dataset arg
          dataset,
          since,
          paths,
          recursive,
          recursion_limit,
      )
  
      # instead of a loop, this could all be done in parallel
      matched_anything = False
      for dspath, dsrecords in ds_spec:
          matched_anything = True
          lgr.debug("Pushing Dataset at %s", dspath)
          pbars = {}
          yield from _push(
              dspath,
              dsrecords,
              to,
              data,
              force,
              jobs,
              res_kwargs.copy(),
              pbars,
              got_path_arg=True if path else False,
          )
          # take down progress bars for this dataset
          for i, ds in pbars.items():
              log_progress(lgr.info, i, "Finished push of %s", ds)
      if not matched_anything:
          potential_remote = False
          if not to and len(paths) == 1:
              # if we get a remote name without --to, provide a hint
              sr = ds_repo.get_remotes(**get_remote_kwargs)
              potential_remote = [
                  p for p in ensure_list(path) if p in sr
              ]
          if potential_remote:
              if len(potential_remote) == 1:
                  # present as a single value to make hint even more human
                  # friendly
                  potential_remote = potential_remote[0]
              hint = (
                  "{} matches a sibling name and not a path. "
                  "Forgot --to?".format(potential_remote)
              )
              yield dict(
                  res_kwargs,
                  status="notneeded",
                  message=hint,
                  hints=hint,
                  type="dataset",
                  path=ds.path,
              )
              # there's no matching path and we have generated a hint on
              # fixing the call - we can return now
              return
          yield dict(
              res_kwargs,
              status="notneeded",
              message="Given constraints did not match any changes to publish",
              type="dataset",
              path=ds.path,
          )
  
imports: from itertools import chain
  import logging
  import re
  from datalad.interface.base import Interface, build_doc, eval_results
  from datalad.interface.common_opts import jobs_opt, recursion_limit, recursion_flag
  from datalad.interface.utils import render_action_summary
  from datalad.interface.results import annexjson2result
  from datalad.log import log_progress
  from datalad.support.annexrepo import AnnexRepo
  from datalad.support.gitrepo import GitRepo
  from datalad.support.param import Parameter
  from datalad.support.constraints import EnsureStr, EnsureNone, EnsureChoice
  from datalad.support.exceptions import CommandError
  from datalad.utils import Path, ensure_list, todo_interface_for_extensions
  from datalad.distribution.dataset import Dataset, EnsureDataset, datasetmethod, require_dataset, resolve_path
  from datalad.core.local.diff import diff_dataset
label_desc: Use image node_id 1 for calling the Push obj's underlying member method code with example usage: obj.__call__(path) without return types