]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/svn/props.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / svn / props.c
1 /*
2  * props.c: Utility functions for property handling
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24 /* ==================================================================== */
25
26
27 \f
28 /*** Includes. ***/
29
30 #include <stdlib.h>
31
32 #include <apr_hash.h>
33 #include "svn_hash.h"
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"
41 #include "svn_opt.h"
42 #include "svn_xml.h"
43 #include "svn_base64.h"
44 #include "cl.h"
45
46 #include "private/svn_string_private.h"
47 #include "private/svn_cmdline_private.h"
48
49 #include "svn_private_config.h"
50 \f
51
52 svn_error_t *
53 svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
54                         const apr_array_header_t *targets,
55                         const char **URL,
56                         svn_client_ctx_t *ctx,
57                         apr_pool_t *pool)
58 {
59   const char *target;
60
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"));
68
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"));
74
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));
79   if (*URL == NULL)
80     return svn_error_create
81       (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
82        _("Either a URL or versioned item is required"));
83
84   return SVN_NO_ERROR;
85 }
86
87 void
88 svn_cl__check_boolean_prop_val(const char *propname, const char *propval,
89                                apr_pool_t *pool)
90 {
91   svn_stringbuf_t *propbuf;
92
93   if (!svn_prop_is_boolean(propname))
94     return;
95
96   propbuf = svn_stringbuf_create(propval, pool);
97   svn_stringbuf_strip_whitespace(propbuf);
98
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)
104     {
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."),
109            propname, propval);
110       svn_handle_warning2(stderr, err, "svn: ");
111       svn_error_clear(err);
112     }
113 }
114
115
116 /* Context for sorting property names */
117 struct simprop_context_t
118 {
119   svn_string_t name;    /* The name of the property we're comparing with */
120   svn_membuf_t buffer;  /* Buffer for similarity testing */
121 };
122
123 struct simprop_t
124 {
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() */
130 };
131
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)
136 {
137   apr_size_t lcs;
138   const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs);
139   if (key->len > ctx->len)
140     *diff = key->len - lcs;
141   else
142     *diff = ctx->len - lcs;
143   return score;
144 }
145
146 /* Key comparator for qsort for simprop_t */
147 static int
148 simprop_compare(const void *pkeya, const void *pkeyb)
149 {
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;
153
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);
160
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))));
165 }
166
167
168 static const char*
169 force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name,
170                           apr_pool_t *scratch_pool)
171 {
172   switch (prop_use)
173     {
174     case svn_cl__prop_use_set:
175       return apr_psprintf(
176           scratch_pool,
177           _("(To set the '%s' property, re-run with '--force'.)"),
178           prop_name);
179     case svn_cl__prop_use_edit:
180       return apr_psprintf(
181           scratch_pool,
182           _("(To edit the '%s' property, re-run with '--force'.)"),
183           prop_name);
184     case svn_cl__prop_use_use:
185     default:
186       return apr_psprintf(
187           scratch_pool,
188           _("(To use the '%s' property, re-run with '--force'.)"),
189           prop_name);
190     }
191 }
192
193 static const char*
194 wrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name,
195                          apr_pool_t *scratch_pool)
196 {
197   switch (prop_use)
198     {
199     case svn_cl__prop_use_set:
200       return apr_psprintf(
201           scratch_pool,
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:
206       return apr_psprintf(
207           scratch_pool,
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:
212     default:
213       return apr_psprintf(
214           scratch_pool,
215           _("'%s' is not a valid %s property name;"
216             " re-run with '--force' to use it"),
217           prop_name, SVN_PROP_PREFIX);
218     }
219 }
220
221 svn_error_t *
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)
226 {
227   static const char *const nodeprops[] =
228     {
229       SVN_PROP_NODE_ALL_PROPS
230     };
231   static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops);
232
233   static const char *const revprops[] =
234     {
235       SVN_PROP_REVISION_ALL_PROPS
236     };
237   static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops);
238
239   const char *const *const proplist = (revprop ? revprops : nodeprops);
240   const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len);
241
242   struct simprop_t **propkeys;
243   struct simprop_t *propbuf;
244   apr_size_t i;
245
246   struct simprop_context_t context;
247   svn_string_t prefix;
248
249   context.name.data = propname;
250   context.name.len = strlen(propname);
251   prefix.data = SVN_PROP_PREFIX;
252   prefix.len = strlen(SVN_PROP_PREFIX);
253
254   svn_membuf__create(&context.buffer, 0, scratch_pool);
255
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 */
262   else
263     {
264       apr_size_t lcs;
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 */
271
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)
275         {
276           for (i = 0; i < numprops; ++i)
277             {
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));
285             }
286           return SVN_NO_ERROR;
287         }
288     }
289
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
293      skew the results. */
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)
301     {
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;
308     }
309
310   qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare);
311
312   if (0 == propkeys[0]->diff)
313     return SVN_NO_ERROR;        /* We found an exact match. */
314
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 */
318       break;
319
320   switch (i)
321     {
322     case 0:
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));
327
328     case 1:
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));
335
336     case 2:
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));
345
346     default:
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));
355     }
356 }