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, ¤t, 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 )