2 * props.c: Utility functions for property handling
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
34 #include "svn_cmdline.h"
35 #include "svn_string.h"
36 #include "svn_error.h"
37 #include "svn_sorts.h"
38 #include "svn_subst.h"
39 #include "svn_props.h"
40 #include "svn_string.h"
43 #include "svn_base64.h"
46 #include "private/svn_string_private.h"
47 #include "private/svn_cmdline_private.h"
49 #include "svn_private_config.h"
53 svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
54 const apr_array_header_t *targets,
56 svn_client_ctx_t *ctx,
61 if (revision->kind != svn_opt_revision_number
62 && revision->kind != svn_opt_revision_date
63 && revision->kind != svn_opt_revision_head)
64 return svn_error_create
65 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
66 _("Must specify the revision as a number, a date or 'HEAD' "
67 "when operating on a revision property"));
69 /* There must be exactly one target at this point. If it was optional and
70 unspecified by the user, the caller has already added the implicit '.'. */
71 if (targets->nelts != 1)
72 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
73 _("Wrong number of targets specified"));
75 /* (The docs say the target must be either a URL or implicit '.', but
76 explicit WC targets are also accepted.) */
77 target = APR_ARRAY_IDX(targets, 0, const char *);
78 SVN_ERR(svn_client_url_from_path2(URL, target, ctx, pool, pool));
80 return svn_error_create
81 (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
82 _("Either a URL or versioned item is required"));
88 svn_cl__check_boolean_prop_val(const char *propname, const char *propval,
91 svn_stringbuf_t *propbuf;
93 if (!svn_prop_is_boolean(propname))
96 propbuf = svn_stringbuf_create(propval, pool);
97 svn_stringbuf_strip_whitespace(propbuf);
99 if (propbuf->data[0] == '\0'
100 || svn_cstring_casecmp(propbuf->data, "0") == 0
101 || svn_cstring_casecmp(propbuf->data, "no") == 0
102 || svn_cstring_casecmp(propbuf->data, "off") == 0
103 || svn_cstring_casecmp(propbuf->data, "false") == 0)
105 svn_error_t *err = svn_error_createf
106 (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
107 _("To turn off the %s property, use 'svn propdel';\n"
108 "setting the property to '%s' will not turn it off."),
110 svn_handle_warning2(stderr, err, "svn: ");
111 svn_error_clear(err);
116 /* Context for sorting property names */
117 struct simprop_context_t
119 svn_string_t name; /* The name of the property we're comparing with */
120 svn_membuf_t buffer; /* Buffer for similarity testing */
125 const char *propname; /* The original svn: property name */
126 svn_string_t name; /* The property name without the svn: prefix */
127 unsigned int score; /* The similarity score */
128 apr_size_t diff; /* Number of chars different from context.name */
129 struct simprop_context_t *context; /* Sorting context for qsort() */
132 /* Similarity test between two property names */
133 static APR_INLINE unsigned int
134 simprop_key_diff(const svn_string_t *key, const svn_string_t *ctx,
135 svn_membuf_t *buffer, apr_size_t *diff)
138 const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs);
139 if (key->len > ctx->len)
140 *diff = key->len - lcs;
142 *diff = ctx->len - lcs;
146 /* Key comparator for qsort for simprop_t */
148 simprop_compare(const void *pkeya, const void *pkeyb)
150 struct simprop_t *const keya = *(struct simprop_t *const *)pkeya;
151 struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb;
152 struct simprop_context_t *const context = keya->context;
154 if (keya->score == -1)
155 keya->score = simprop_key_diff(&keya->name, &context->name,
156 &context->buffer, &keya->diff);
157 if (keyb->score == -1)
158 keyb->score = simprop_key_diff(&keyb->name, &context->name,
159 &context->buffer, &keyb->diff);
161 return (keya->score < keyb->score ? 1
162 : (keya->score > keyb->score ? -1
163 : (keya->diff > keyb->diff ? 1
164 : (keya->diff < keyb->diff ? -1 : 0))));
169 force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name,
170 apr_pool_t *scratch_pool)
174 case svn_cl__prop_use_set:
177 _("(To set the '%s' property, re-run with '--force'.)"),
179 case svn_cl__prop_use_edit:
182 _("(To edit the '%s' property, re-run with '--force'.)"),
184 case svn_cl__prop_use_use:
188 _("(To use the '%s' property, re-run with '--force'.)"),
194 wrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name,
195 apr_pool_t *scratch_pool)
199 case svn_cl__prop_use_set:
202 _("'%s' is not a valid %s property name;"
203 " re-run with '--force' to set it"),
204 prop_name, SVN_PROP_PREFIX);
205 case svn_cl__prop_use_edit:
208 _("'%s' is not a valid %s property name;"
209 " re-run with '--force' to edit it"),
210 prop_name, SVN_PROP_PREFIX);
211 case svn_cl__prop_use_use:
215 _("'%s' is not a valid %s property name;"
216 " re-run with '--force' to use it"),
217 prop_name, SVN_PROP_PREFIX);
222 svn_cl__check_svn_prop_name(const char *propname,
223 svn_boolean_t revprop,
224 svn_cl__prop_use_t prop_use,
225 apr_pool_t *scratch_pool)
227 static const char *const nodeprops[] =
229 SVN_PROP_NODE_ALL_PROPS
231 static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops);
233 static const char *const revprops[] =
235 SVN_PROP_REVISION_ALL_PROPS
237 static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops);
239 const char *const *const proplist = (revprop ? revprops : nodeprops);
240 const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len);
242 struct simprop_t **propkeys;
243 struct simprop_t *propbuf;
246 struct simprop_context_t context;
249 context.name.data = propname;
250 context.name.len = strlen(propname);
251 prefix.data = SVN_PROP_PREFIX;
252 prefix.len = strlen(SVN_PROP_PREFIX);
254 svn_membuf__create(&context.buffer, 0, scratch_pool);
256 /* First, check if the name is even close to being in the svn: namespace.
257 It must contain a colon in the right place, and we only allow
258 one-char typos or a single transposition. */
259 if (context.name.len < prefix.len
260 || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1])
261 return SVN_NO_ERROR; /* Wrong prefix, ignore */
265 const apr_size_t name_len = context.name.len;
266 context.name.len = prefix.len; /* Only check up to the prefix length */
267 svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs);
268 context.name.len = name_len; /* Restore the original propname length */
269 if (lcs < prefix.len - 1)
270 return SVN_NO_ERROR; /* Wrong prefix, ignore */
272 /* If the prefix is slightly different, the rest must be
273 identical in order to trigger the error. */
274 if (lcs == prefix.len - 1)
276 for (i = 0; i < numprops; ++i)
278 if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len))
279 return svn_error_createf(
280 SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
281 _("'%s' is not a valid %s property name;"
282 " did you mean '%s'?\n%s"),
283 propname, SVN_PROP_PREFIX, proplist[i],
284 force_prop_option_message(prop_use, propname, scratch_pool));
290 /* Now find the closest match from amongst the set of reserved
291 node or revision property names. Skip the prefix while matching,
292 we already know that it's the same and looking at it would only
294 propkeys = apr_palloc(scratch_pool,
295 numprops * sizeof(struct simprop_t*));
296 propbuf = apr_palloc(scratch_pool,
297 numprops * sizeof(struct simprop_t));
298 context.name.data += prefix.len;
299 context.name.len -= prefix.len;
300 for (i = 0; i < numprops; ++i)
302 propkeys[i] = &propbuf[i];
303 propbuf[i].propname = proplist[i];
304 propbuf[i].name.data = proplist[i] + prefix.len;
305 propbuf[i].name.len = strlen(propbuf[i].name.data);
306 propbuf[i].score = (unsigned int)-1;
307 propbuf[i].context = &context;
310 qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare);
312 if (0 == propkeys[0]->diff)
313 return SVN_NO_ERROR; /* We found an exact match. */
315 /* See if we can suggest a sane alternative spelling */
316 for (i = 0; i < numprops; ++i)
317 if (propkeys[i]->score < 666) /* 2/3 similarity required */
323 /* The best alternative isn't good enough */
324 return svn_error_create(
325 SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
326 wrong_prop_error_message(prop_use, propname, scratch_pool));
329 /* There is only one good candidate */
330 return svn_error_createf(
331 SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
332 _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"),
333 propname, SVN_PROP_PREFIX, propkeys[0]->propname,
334 force_prop_option_message(prop_use, propname, scratch_pool));
337 /* Suggest a list of the most likely candidates */
338 return svn_error_createf(
339 SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
340 _("'%s' is not a valid %s property name\n"
341 "Did you mean '%s' or '%s'?\n%s"),
342 propname, SVN_PROP_PREFIX,
343 propkeys[0]->propname, propkeys[1]->propname,
344 force_prop_option_message(prop_use, propname, scratch_pool));
347 /* Never suggest more than three candidates */
348 return svn_error_createf(
349 SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
350 _("'%s' is not a valid %s property name\n"
351 "Did you mean '%s', '%s' or '%s'?\n%s"),
352 propname, SVN_PROP_PREFIX,
353 propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname,
354 force_prop_option_message(prop_use, propname, scratch_pool));