Alien-SVN

 view release on metacpan or  search on metacpan

src/subversion/subversion/libsvn_client/prop_commands.c  view on Meta::CPAN

/*
 * prop_commands.c:  Implementation of propset, propget, and proplist.
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

/* ==================================================================== */



/*** Includes. ***/

#define APR_WANT_STRFUNC
#include <apr_want.h>

#include "svn_error.h"
#include "svn_client.h"
#include "client.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_pools.h"
#include "svn_props.h"
#include "svn_hash.h"
#include "svn_sorts.h"

#include "svn_private_config.h"
#include "private/svn_wc_private.h"
#include "private/svn_ra_private.h"
#include "private/svn_client_private.h"


/*** Code. ***/

/* Return an SVN_ERR_CLIENT_PROPERTY_NAME error if NAME is a wcprop,
   else return SVN_NO_ERROR. */
static svn_error_t *
error_if_wcprop_name(const char *name)
{
  if (svn_property_kind2(name) == svn_prop_wc_kind)
    {
      return svn_error_createf
        (SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
         _("'%s' is a wcprop, thus not accessible to clients"),
         name);
    }

  return SVN_NO_ERROR;
}


struct getter_baton
{
  svn_ra_session_t *ra_session;
  svn_revnum_t base_revision_for_url;
};


static svn_error_t *
get_file_for_validation(const svn_string_t **mime_type,
                        svn_stream_t *stream,
                        void *baton,
                        apr_pool_t *pool)
{
  struct getter_baton *gb = baton;
  svn_ra_session_t *ra_session = gb->ra_session;
  apr_hash_t *props;

  SVN_ERR(svn_ra_get_file(ra_session, "", gb->base_revision_for_url,
                          stream, NULL,
                          (mime_type ? &props : NULL),
                          pool));

  if (mime_type)
    *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);

  return SVN_NO_ERROR;
}


static
svn_error_t *
do_url_propset(const char *url,
               const char *propname,
               const svn_string_t *propval,
               const svn_node_kind_t kind,
               const svn_revnum_t base_revision_for_url,
               const svn_delta_editor_t *editor,
               void *edit_baton,
               apr_pool_t *pool)
{
  void *root_baton;

  SVN_ERR(editor->open_root(edit_baton, base_revision_for_url, pool,
                            &root_baton));

  if (kind == svn_node_file)
    {
      void *file_baton;
      const char *uri_basename = svn_uri_basename(url, pool);

      SVN_ERR(editor->open_file(uri_basename, root_baton,
                                base_revision_for_url, pool, &file_baton));
      SVN_ERR(editor->change_file_prop(file_baton, propname, propval, pool));
      SVN_ERR(editor->close_file(file_baton, NULL, pool));
    }
  else
    {
      SVN_ERR(editor->change_dir_prop(root_baton, propname, propval, pool));
    }

  return editor->close_directory(root_baton, pool);
}

static svn_error_t *
propset_on_url(const char *propname,
               const svn_string_t *propval,
               const char *target,
               svn_boolean_t skip_checks,
               svn_revnum_t base_revision_for_url,
               const apr_hash_t *revprop_table,
               svn_commit_callback2_t commit_callback,
               void *commit_baton,
               svn_client_ctx_t *ctx,
               apr_pool_t *pool)
{
  enum svn_prop_kind prop_kind = svn_property_kind2(propname);
  svn_ra_session_t *ra_session;
  svn_node_kind_t node_kind;
  const char *message;
  const svn_delta_editor_t *editor;
  void *edit_baton;
  apr_hash_t *commit_revprops;
  svn_error_t *err;

  if (prop_kind != svn_prop_regular_kind)
    return svn_error_createf
      (SVN_ERR_BAD_PROP_KIND, NULL,
       _("Property '%s' is not a regular property"), propname);

  /* Open an RA session for the URL. Note that we don't have a local
     directory, nor a place to put temp files. */
  SVN_ERR(svn_client_open_ra_session2(&ra_session, target, NULL,
                                      ctx, pool, pool));

  SVN_ERR(svn_ra_check_path(ra_session, "", base_revision_for_url,
                            &node_kind, pool));
  if (node_kind == svn_node_none)
    return svn_error_createf
      (SVN_ERR_FS_NOT_FOUND, NULL,
       _("Path '%s' does not exist in revision %ld"),
       target, base_revision_for_url);

  if (node_kind == svn_node_file)
    {
      /* We need to reparent our session one directory up, since editor
         semantics require the root is a directory.

         ### How does this interact with authz? */
      const char *parent_url;
      parent_url = svn_uri_dirname(target, pool);

      SVN_ERR(svn_ra_reparent(ra_session, parent_url, pool));
    }

  /* Setting an inappropriate property is not allowed (unless
     overridden by 'skip_checks', in some circumstances).  Deleting an
     inappropriate property is allowed, however, since older clients
     allowed (and other clients possibly still allow) setting it in
     the first place. */
  if (propval && svn_prop_is_svn_prop(propname))
    {
      const svn_string_t *new_value;
      struct getter_baton gb;

      gb.ra_session = ra_session;
      gb.base_revision_for_url = base_revision_for_url;
      SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, propname, propval,
                                           target, node_kind, skip_checks,
                                           get_file_for_validation, &gb, pool));
      propval = new_value;
    }

  /* Create a new commit item and add it to the array. */
  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
    {
      svn_client_commit_item3_t *item;
      const char *tmp_file;
      apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(item));

      item = svn_client_commit_item3_create(pool);
      item->url = target;
      item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
      APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
                                      ctx, pool));
      if (! message)
        return SVN_NO_ERROR;
    }
  else
    message = "";

  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
                                           message, ctx, pool));

  /* Fetch RA commit editor. */
  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
                        svn_client__get_shim_callbacks(ctx->wc_ctx,
                                                       NULL, pool)));
  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
                                    commit_revprops,
                                    commit_callback,
                                    commit_baton,
                                    NULL, TRUE, /* No lock tokens */
                                    pool));

  err = do_url_propset(target, propname, propval, node_kind,
                       base_revision_for_url, editor, edit_baton, pool);

  if (err)
    {
      /* At least try to abort the edit (and fs txn) before throwing err. */
      svn_error_clear(editor->abort_edit(edit_baton, pool));
      return svn_error_trace(err);
    }

  /* Close the edit. */
  return editor->close_edit(edit_baton, pool);
}

/* Check that PROPNAME is a valid name for a versioned property.  Return an
 * error if it is not valid, specifically if it is:
 *   - the name of a standard Subversion rev-prop; or
 *   - in the namespace of WC-props; or
 *   - not a well-formed property name (except if PROPVAL is NULL: in other
 *     words we do allow deleting a prop with an ill-formed name).
 *
 * Since Subversion controls the "svn:" property namespace, we don't honor
 * a 'skip_checks' flag here.  Checks for unusual property combinations such
 * as svn:eol-style with a non-text svn:mime-type might understandably be
 * skipped, but things such as using a property name reserved for revprops
 * on a local target are never allowed.
 */
static svn_error_t *
check_prop_name(const char *propname,
                const svn_string_t *propval)
{
  if (svn_prop_is_known_svn_rev_prop(propname))
    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
                             _("Revision property '%s' not allowed "
                               "in this context"), propname);

  SVN_ERR(error_if_wcprop_name(propname));

  if (propval && ! svn_prop_name_is_valid(propname))
    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
                             _("Bad property name: '%s'"), propname);

  return SVN_NO_ERROR;
}

svn_error_t *
svn_client_propset_local(const char *propname,
                         const svn_string_t *propval,
                         const apr_array_header_t *targets,
                         svn_depth_t depth,
                         svn_boolean_t skip_checks,
                         const apr_array_header_t *changelists,
                         svn_client_ctx_t *ctx,
                         apr_pool_t *scratch_pool)
{
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  svn_boolean_t targets_are_urls;
  int i;

  if (targets->nelts == 0)
    return SVN_NO_ERROR;

  /* Check for homogeneity among our targets. */
  targets_are_urls = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
  SVN_ERR(svn_client__assert_homogeneous_target_type(targets));

  if (targets_are_urls)
    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
                            _("Targets must be working copy paths"));

  SVN_ERR(check_prop_name(propname, propval));

  for (i = 0; i < targets->nelts; i++)
    {
      svn_node_kind_t kind;
      const char *target_abspath;
      const char *target = APR_ARRAY_IDX(targets, i, const char *);

      svn_pool_clear(iterpool);

      /* Check for cancellation */
      if (ctx->cancel_func)
        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));

      SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, iterpool));

      /* Call prop_set for deleted nodes to have special errors */
      SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
                                FALSE, FALSE, iterpool));

      if (kind == svn_node_unknown || kind == svn_node_none)
        {
          if (ctx->notify_func2)
            {
              svn_wc_notify_t *notify = svn_wc_create_notify(
                                          target_abspath,
                                          svn_wc_notify_path_nonexistent,
                                          iterpool);

              ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
            }
        }

      SVN_WC__CALL_WITH_WRITE_LOCK(
        svn_wc_prop_set4(ctx->wc_ctx, target_abspath, propname,
                         propval, depth, skip_checks, changelists,
                         ctx->cancel_func, ctx->cancel_baton,
                         ctx->notify_func2, ctx->notify_baton2, iterpool),
        ctx->wc_ctx, target_abspath, FALSE /* lock_anchor */, iterpool);
    }
  svn_pool_destroy(iterpool);

  return SVN_NO_ERROR;
}

svn_error_t *
svn_client_propset_remote(const char *propname,
                          const svn_string_t *propval,
                          const char *url,
                          svn_boolean_t skip_checks,
                          svn_revnum_t base_revision_for_url,
                          const apr_hash_t *revprop_table,
                          svn_commit_callback2_t commit_callback,
                          void *commit_baton,
                          svn_client_ctx_t *ctx,
                          apr_pool_t *scratch_pool)
{
  if (!svn_path_is_url(url))
    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
                            _("Targets must be URLs"));

  SVN_ERR(check_prop_name(propname, propval));

  /* The rationale for requiring the base_revision_for_url
     argument is that without it, it's too easy to possibly
     overwrite someone else's change without noticing.  (See also
     tools/examples/svnput.c). */
  if (! SVN_IS_VALID_REVNUM(base_revision_for_url))
    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
                            _("Setting property on non-local targets "
                              "needs a base revision"));

  /* ### When you set svn:eol-style or svn:keywords on a wc file,
     ### Subversion sends a textdelta at commit time to properly
     ### normalize the file in the repository.  If we want to
     ### support editing these properties on URLs, then we should
     ### generate the same textdelta; for now, we won't support
     ### editing these properties on URLs.  (Admittedly, this
     ### means that all the machinery with get_file_for_validation
     ### is unused.)
   */
  if ((strcmp(propname, SVN_PROP_EOL_STYLE) == 0) ||
      (strcmp(propname, SVN_PROP_KEYWORDS) == 0))
    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
                             _("Setting property '%s' on non-local "
                               "targets is not supported"), propname);

  SVN_ERR(propset_on_url(propname, propval, url, skip_checks,
                         base_revision_for_url, revprop_table,
                         commit_callback, commit_baton, ctx, scratch_pool));

  return SVN_NO_ERROR;
}

static svn_error_t *
check_and_set_revprop(svn_revnum_t *set_rev,
                      svn_ra_session_t *ra_session,
                      const char *propname,
                      const svn_string_t *original_propval,
                      const svn_string_t *propval,
                      apr_pool_t *pool)
{
  if (original_propval)
    {
      /* Ensure old value hasn't changed behind our back. */
      svn_string_t *current;
      SVN_ERR(svn_ra_rev_prop(ra_session, *set_rev, propname, &current, pool));

      if (original_propval->data && (! current))
        {
          return svn_error_createf(
                  SVN_ERR_RA_OUT_OF_DATE, NULL,
                  _("revprop '%s' in r%ld is unexpectedly absent "
                    "in repository (maybe someone else deleted it?)"),
                  propname, *set_rev);
        }
      else if (original_propval->data
               && (! svn_string_compare(original_propval, current)))
        {
          return svn_error_createf(
                  SVN_ERR_RA_OUT_OF_DATE, NULL,
                  _("revprop '%s' in r%ld has unexpected value "
                    "in repository (maybe someone else changed it?)"),
                  propname, *set_rev);
        }
      else if ((! original_propval->data) && current)
        {
          return svn_error_createf(
                  SVN_ERR_RA_OUT_OF_DATE, NULL,
                  _("revprop '%s' in r%ld is unexpectedly present "
                    "in repository (maybe someone else set it?)"),
                  propname, *set_rev);
        }
    }

  SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname,
                                  NULL, propval, pool));

  return SVN_NO_ERROR;
}

svn_error_t *
svn_client_revprop_set2(const char *propname,
                        const svn_string_t *propval,
                        const svn_string_t *original_propval,
                        const char *URL,
                        const svn_opt_revision_t *revision,
                        svn_revnum_t *set_rev,



( run in 0.619 second using v1.01-cache-2.11-cpan-71847e10f99 )