]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_client/cmdline.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_client / cmdline.c
1 /*
2  * cmdline.c:  command-line processing
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 \f
27 /*** Includes. ***/
28 #include "svn_client.h"
29 #include "svn_error.h"
30 #include "svn_dirent_uri.h"
31 #include "svn_path.h"
32 #include "svn_opt.h"
33 #include "svn_utf.h"
34
35 #include "client.h"
36
37 #include "private/svn_opt_private.h"
38
39 #include "svn_private_config.h"
40
41 \f
42 /*** Code. ***/
43
44 #define DEFAULT_ARRAY_SIZE 5
45
46
47 /* Attempt to find the repository root url for TARGET, possibly using CTX for
48  * authentication.  If one is found and *ROOT_URL is not NULL, then just check
49  * that the root url for TARGET matches the value given in *ROOT_URL and
50  * return an error if it does not.  If one is found and *ROOT_URL is NULL then
51  * set *ROOT_URL to the root url for TARGET, allocated from POOL.
52  * If a root url is not found for TARGET because it does not exist in the
53  * repository, then return with no error.
54  *
55  * TARGET is a UTF-8 encoded string that is fully canonicalized and escaped.
56  */
57 static svn_error_t *
58 check_root_url_of_target(const char **root_url,
59                          const char *target,
60                          svn_client_ctx_t *ctx,
61                          apr_pool_t *pool)
62 {
63   svn_error_t *err;
64   const char *tmp_root_url;
65   const char *truepath;
66   svn_opt_revision_t opt_rev;
67
68   SVN_ERR(svn_opt_parse_path(&opt_rev, &truepath, target, pool));
69   if (!svn_path_is_url(truepath))
70     SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, pool));
71
72   err = svn_client_get_repos_root(&tmp_root_url, NULL, truepath,
73                                   ctx, pool, pool);
74
75   if (err)
76     {
77       /* It is OK if the given target does not exist, it just means
78        * we will not be able to determine the root url from this particular
79        * argument.
80        *
81        * If the target itself is a URL to a repository that does not exist,
82        * that's fine, too. The callers will deal with this argument in an
83        * appropriate manner if it does not make any sense.
84        *
85        * Also tolerate locally added targets ("bad revision" error).
86        */
87       if ((err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
88           || (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
89           || (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
90           || (err->apr_err == SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED)
91           || (err->apr_err == SVN_ERR_CLIENT_BAD_REVISION))
92         {
93           svn_error_clear(err);
94           return SVN_NO_ERROR;
95         }
96       else
97         return svn_error_trace(err);
98      }
99
100    if (*root_url && tmp_root_url)
101      {
102        if (strcmp(*root_url, tmp_root_url) != 0)
103          return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
104                                   _("All non-relative targets must have "
105                                     "the same root URL"));
106      }
107    else
108      *root_url = tmp_root_url;
109
110    return SVN_NO_ERROR;
111 }
112
113 /* Note: This is substantially copied from svn_opt__args_to_target_array() in
114  * order to move to libsvn_client while maintaining backward compatibility. */
115 svn_error_t *
116 svn_client_args_to_target_array2(apr_array_header_t **targets_p,
117                                  apr_getopt_t *os,
118                                  const apr_array_header_t *known_targets,
119                                  svn_client_ctx_t *ctx,
120                                  svn_boolean_t keep_last_origpath_on_truepath_collision,
121                                  apr_pool_t *pool)
122 {
123   int i;
124   svn_boolean_t rel_url_found = FALSE;
125   const char *root_url = NULL;
126   apr_array_header_t *input_targets =
127     apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
128   apr_array_header_t *output_targets =
129     apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
130   apr_array_header_t *reserved_names = NULL;
131
132   /* Step 1:  create a master array of targets that are in UTF-8
133      encoding, and come from concatenating the targets left by apr_getopt,
134      plus any extra targets (e.g., from the --targets switch.)
135      If any of the targets are relative urls, then set the rel_url_found
136      flag.*/
137
138   for (; os->ind < os->argc; os->ind++)
139     {
140       /* The apr_getopt targets are still in native encoding. */
141       const char *raw_target = os->argv[os->ind];
142       const char *utf8_target;
143
144       SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target,
145                                       raw_target, pool));
146
147       if (svn_path_is_repos_relative_url(utf8_target))
148         rel_url_found = TRUE;
149
150       APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
151     }
152
153   if (known_targets)
154     {
155       for (i = 0; i < known_targets->nelts; i++)
156         {
157           /* The --targets array have already been converted to UTF-8,
158              because we needed to split up the list with svn_cstring_split. */
159           const char *utf8_target = APR_ARRAY_IDX(known_targets,
160                                                   i, const char *);
161
162           if (svn_path_is_repos_relative_url(utf8_target))
163             rel_url_found = TRUE;
164
165           APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
166         }
167     }
168
169   /* Step 2:  process each target.  */
170
171   for (i = 0; i < input_targets->nelts; i++)
172     {
173       const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
174
175       /* Relative urls will be canonicalized when they are resolved later in
176        * the function
177        */
178       if (svn_path_is_repos_relative_url(utf8_target))
179         {
180           APR_ARRAY_PUSH(output_targets, const char *) = utf8_target;
181         }
182       else
183         {
184           const char *true_target;
185           const char *peg_rev;
186           const char *target;
187
188           /*
189            * This is needed so that the target can be properly canonicalized,
190            * otherwise the canonicalization does not treat a ".@BASE" as a "."
191            * with a BASE peg revision, and it is not canonicalized to "@BASE".
192            * If any peg revision exists, it is appended to the final
193            * canonicalized path or URL.  Do not use svn_opt_parse_path()
194            * because the resulting peg revision is a structure that would have
195            * to be converted back into a string.  Converting from a string date
196            * to the apr_time_t field in the svn_opt_revision_value_t and back to
197            * a string would not necessarily preserve the exact bytes of the
198            * input date, so its easier just to keep it in string form.
199            */
200           SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
201                                                      utf8_target, pool));
202
203           /* URLs and wc-paths get treated differently. */
204           if (svn_path_is_url(true_target))
205             {
206               SVN_ERR(svn_opt__arg_canonicalize_url(&true_target,
207                                                     true_target, pool));
208             }
209           else  /* not a url, so treat as a path */
210             {
211               const char *base_name;
212               const char *original_target;
213
214               original_target = svn_dirent_internal_style(true_target, pool);
215               SVN_ERR(svn_opt__arg_canonicalize_path(&true_target,
216                                                      true_target, pool));
217
218               /* There are two situations in which a 'truepath-conversion'
219                  (case-canonicalization to on-disk path on case-insensitive
220                  filesystem) needs to be undone:
221
222                  1. If KEEP_LAST_ORIGPATH_ON_TRUEPATH_COLLISION is TRUE, and
223                     this is the last target of a 2-element target list, and
224                     both targets have the same truepath. */
225               if (keep_last_origpath_on_truepath_collision
226                   && input_targets->nelts == 2 && i == 1
227                   && strcmp(original_target, true_target) != 0)
228                 {
229                   const char *src_truepath = APR_ARRAY_IDX(output_targets,
230                                                            0,
231                                                            const char *);
232                   if (strcmp(src_truepath, true_target) == 0)
233                     true_target = original_target;
234                 }
235
236               /* 2. If there is an exact match in the wc-db without a
237                     corresponding on-disk path (e.g. a scheduled-for-delete
238                     file only differing in case from an on-disk file). */
239               if (strcmp(original_target, true_target) != 0)
240                 {
241                   const char *target_abspath;
242                   svn_node_kind_t kind;
243                   svn_error_t *err2;
244
245                   SVN_ERR(svn_dirent_get_absolute(&target_abspath,
246                                                   original_target, pool));
247                   err2 = svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
248                                            TRUE, FALSE, pool);
249                   if (err2
250                       && (err2->apr_err == SVN_ERR_WC_NOT_WORKING_COPY
251                           || err2->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
252                     {
253                       svn_error_clear(err2);
254                     }
255                   else
256                     {
257                       SVN_ERR(err2);
258                       /* We successfully did a lookup in the wc-db. Now see
259                          if it's something interesting. */
260                       if (kind == svn_node_file || kind == svn_node_dir)
261                         true_target = original_target;
262                     }
263                 }
264
265               /* If the target has the same name as a Subversion
266                  working copy administrative dir, skip it. */
267               base_name = svn_dirent_basename(true_target, pool);
268
269               if (svn_wc_is_adm_dir(base_name, pool))
270                 {
271                   if (!reserved_names)
272                     reserved_names = apr_array_make(pool, DEFAULT_ARRAY_SIZE,
273                                                     sizeof(const char *));
274
275                   APR_ARRAY_PUSH(reserved_names, const char *) = utf8_target;
276
277                   continue;
278                 }
279             }
280
281           target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
282
283           if (rel_url_found)
284             {
285               /* Later targets have priority over earlier target, I
286                  don't know why, see basic_relative_url_multi_repo. */
287               SVN_ERR(check_root_url_of_target(&root_url, target,
288                                                ctx, pool));
289             }
290
291           APR_ARRAY_PUSH(output_targets, const char *) = target;
292         }
293     }
294
295   /* Only resolve relative urls if there were some actually found earlier. */
296   if (rel_url_found)
297     {
298       /*
299        * Use the current directory's root url if one wasn't found using the
300        * arguments.
301        */
302       if (root_url == NULL)
303         {
304           const char *current_abspath;
305           svn_error_t *err;
306
307           SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
308           err = svn_client_get_repos_root(&root_url, NULL /* uuid */,
309                                           current_abspath, ctx, pool, pool);
310           if (err || root_url == NULL)
311             return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, err,
312                                     _("Resolving '^/': no repository root "
313                                       "found in the target arguments or "
314                                       "in the current directory"));
315         }
316
317       *targets_p = apr_array_make(pool, output_targets->nelts,
318                                   sizeof(const char *));
319
320       for (i = 0; i < output_targets->nelts; i++)
321         {
322           const char *target = APR_ARRAY_IDX(output_targets, i,
323                                              const char *);
324
325           if (svn_path_is_repos_relative_url(target))
326             {
327               const char *abs_target;
328               const char *true_target;
329               const char *peg_rev;
330
331               SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
332                                                          target, pool));
333
334               SVN_ERR(svn_path_resolve_repos_relative_url(&abs_target,
335                                                           true_target,
336                                                           root_url, pool));
337
338               SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, abs_target,
339                                                     pool));
340
341               target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
342             }
343
344           APR_ARRAY_PUSH(*targets_p, const char *) = target;
345         }
346     }
347   else
348     *targets_p = output_targets;
349
350   if (reserved_names)
351     {
352       svn_error_t *err = SVN_NO_ERROR;
353
354       for (i = 0; i < reserved_names->nelts; ++i)
355         err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, err,
356                                 _("'%s' ends in a reserved name"),
357                                 APR_ARRAY_IDX(reserved_names, i,
358                                               const char *));
359       return svn_error_trace(err);
360     }
361
362   return SVN_NO_ERROR;
363 }