1251881Speter/*
2251881Speter * cmdline.c:  command-line processing
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter/*** Includes. ***/
28251881Speter#include "svn_client.h"
29251881Speter#include "svn_error.h"
30251881Speter#include "svn_dirent_uri.h"
31251881Speter#include "svn_path.h"
32251881Speter#include "svn_opt.h"
33251881Speter#include "svn_utf.h"
34251881Speter
35251881Speter#include "client.h"
36251881Speter
37251881Speter#include "private/svn_opt_private.h"
38251881Speter
39251881Speter#include "svn_private_config.h"
40251881Speter
41251881Speter
42251881Speter/*** Code. ***/
43251881Speter
44251881Speter#define DEFAULT_ARRAY_SIZE 5
45251881Speter
46251881Speter
47251881Speter/* Attempt to find the repository root url for TARGET, possibly using CTX for
48251881Speter * authentication.  If one is found and *ROOT_URL is not NULL, then just check
49251881Speter * that the root url for TARGET matches the value given in *ROOT_URL and
50251881Speter * return an error if it does not.  If one is found and *ROOT_URL is NULL then
51251881Speter * set *ROOT_URL to the root url for TARGET, allocated from POOL.
52251881Speter * If a root url is not found for TARGET because it does not exist in the
53251881Speter * repository, then return with no error.
54251881Speter *
55251881Speter * TARGET is a UTF-8 encoded string that is fully canonicalized and escaped.
56251881Speter */
57251881Speterstatic svn_error_t *
58251881Spetercheck_root_url_of_target(const char **root_url,
59251881Speter                         const char *target,
60251881Speter                         svn_client_ctx_t *ctx,
61251881Speter                         apr_pool_t *pool)
62251881Speter{
63251881Speter  svn_error_t *err;
64251881Speter  const char *tmp_root_url;
65251881Speter  const char *truepath;
66251881Speter  svn_opt_revision_t opt_rev;
67251881Speter
68251881Speter  SVN_ERR(svn_opt_parse_path(&opt_rev, &truepath, target, pool));
69251881Speter  if (!svn_path_is_url(truepath))
70251881Speter    SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, pool));
71251881Speter
72251881Speter  err = svn_client_get_repos_root(&tmp_root_url, NULL, truepath,
73251881Speter                                  ctx, pool, pool);
74251881Speter
75251881Speter  if (err)
76251881Speter    {
77251881Speter      /* It is OK if the given target does not exist, it just means
78251881Speter       * we will not be able to determine the root url from this particular
79251881Speter       * argument.
80251881Speter       *
81251881Speter       * If the target itself is a URL to a repository that does not exist,
82251881Speter       * that's fine, too. The callers will deal with this argument in an
83251881Speter       * appropriate manner if it does not make any sense.
84251881Speter       *
85251881Speter       * Also tolerate locally added targets ("bad revision" error).
86251881Speter       */
87251881Speter      if ((err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
88251881Speter          || (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
89251881Speter          || (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
90299742Sdim          || (err->apr_err == SVN_ERR_RA_CANNOT_CREATE_SESSION)
91251881Speter          || (err->apr_err == SVN_ERR_CLIENT_BAD_REVISION))
92251881Speter        {
93251881Speter          svn_error_clear(err);
94251881Speter          return SVN_NO_ERROR;
95251881Speter        }
96251881Speter      else
97251881Speter        return svn_error_trace(err);
98251881Speter     }
99251881Speter
100251881Speter   if (*root_url && tmp_root_url)
101251881Speter     {
102251881Speter       if (strcmp(*root_url, tmp_root_url) != 0)
103251881Speter         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
104251881Speter                                  _("All non-relative targets must have "
105251881Speter                                    "the same root URL"));
106251881Speter     }
107251881Speter   else
108251881Speter     *root_url = tmp_root_url;
109251881Speter
110251881Speter   return SVN_NO_ERROR;
111251881Speter}
112251881Speter
113251881Speter/* Note: This is substantially copied from svn_opt__args_to_target_array() in
114251881Speter * order to move to libsvn_client while maintaining backward compatibility. */
115251881Spetersvn_error_t *
116251881Spetersvn_client_args_to_target_array2(apr_array_header_t **targets_p,
117251881Speter                                 apr_getopt_t *os,
118251881Speter                                 const apr_array_header_t *known_targets,
119251881Speter                                 svn_client_ctx_t *ctx,
120251881Speter                                 svn_boolean_t keep_last_origpath_on_truepath_collision,
121251881Speter                                 apr_pool_t *pool)
122251881Speter{
123251881Speter  int i;
124251881Speter  svn_boolean_t rel_url_found = FALSE;
125251881Speter  const char *root_url = NULL;
126251881Speter  apr_array_header_t *input_targets =
127251881Speter    apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
128251881Speter  apr_array_header_t *output_targets =
129251881Speter    apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
130251881Speter  apr_array_header_t *reserved_names = NULL;
131251881Speter
132251881Speter  /* Step 1:  create a master array of targets that are in UTF-8
133251881Speter     encoding, and come from concatenating the targets left by apr_getopt,
134251881Speter     plus any extra targets (e.g., from the --targets switch.)
135251881Speter     If any of the targets are relative urls, then set the rel_url_found
136251881Speter     flag.*/
137251881Speter
138251881Speter  for (; os->ind < os->argc; os->ind++)
139251881Speter    {
140251881Speter      /* The apr_getopt targets are still in native encoding. */
141251881Speter      const char *raw_target = os->argv[os->ind];
142251881Speter      const char *utf8_target;
143251881Speter
144251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target,
145251881Speter                                      raw_target, pool));
146251881Speter
147251881Speter      if (svn_path_is_repos_relative_url(utf8_target))
148251881Speter        rel_url_found = TRUE;
149251881Speter
150251881Speter      APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
151251881Speter    }
152251881Speter
153251881Speter  if (known_targets)
154251881Speter    {
155251881Speter      for (i = 0; i < known_targets->nelts; i++)
156251881Speter        {
157251881Speter          /* The --targets array have already been converted to UTF-8,
158251881Speter             because we needed to split up the list with svn_cstring_split. */
159251881Speter          const char *utf8_target = APR_ARRAY_IDX(known_targets,
160251881Speter                                                  i, const char *);
161251881Speter
162251881Speter          if (svn_path_is_repos_relative_url(utf8_target))
163251881Speter            rel_url_found = TRUE;
164251881Speter
165251881Speter          APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
166251881Speter        }
167251881Speter    }
168251881Speter
169251881Speter  /* Step 2:  process each target.  */
170251881Speter
171251881Speter  for (i = 0; i < input_targets->nelts; i++)
172251881Speter    {
173251881Speter      const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
174251881Speter
175251881Speter      /* Relative urls will be canonicalized when they are resolved later in
176251881Speter       * the function
177251881Speter       */
178251881Speter      if (svn_path_is_repos_relative_url(utf8_target))
179251881Speter        {
180251881Speter          APR_ARRAY_PUSH(output_targets, const char *) = utf8_target;
181251881Speter        }
182251881Speter      else
183251881Speter        {
184251881Speter          const char *true_target;
185251881Speter          const char *peg_rev;
186251881Speter          const char *target;
187251881Speter
188251881Speter          /*
189251881Speter           * This is needed so that the target can be properly canonicalized,
190251881Speter           * otherwise the canonicalization does not treat a ".@BASE" as a "."
191251881Speter           * with a BASE peg revision, and it is not canonicalized to "@BASE".
192251881Speter           * If any peg revision exists, it is appended to the final
193251881Speter           * canonicalized path or URL.  Do not use svn_opt_parse_path()
194251881Speter           * because the resulting peg revision is a structure that would have
195251881Speter           * to be converted back into a string.  Converting from a string date
196251881Speter           * to the apr_time_t field in the svn_opt_revision_value_t and back to
197251881Speter           * a string would not necessarily preserve the exact bytes of the
198251881Speter           * input date, so its easier just to keep it in string form.
199251881Speter           */
200251881Speter          SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
201251881Speter                                                     utf8_target, pool));
202251881Speter
203299742Sdim          /* Reject the form "@abc", a peg specifier with no path. */
204299742Sdim          if (true_target[0] == '\0' && peg_rev[0] != '\0')
205299742Sdim            {
206299742Sdim              return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
207299742Sdim                                       _("'%s' is just a peg revision. "
208299742Sdim                                         "Maybe try '%s@' instead?"),
209299742Sdim                                       utf8_target, utf8_target);
210299742Sdim            }
211299742Sdim
212251881Speter          /* URLs and wc-paths get treated differently. */
213251881Speter          if (svn_path_is_url(true_target))
214251881Speter            {
215251881Speter              SVN_ERR(svn_opt__arg_canonicalize_url(&true_target,
216251881Speter                                                    true_target, pool));
217251881Speter            }
218251881Speter          else  /* not a url, so treat as a path */
219251881Speter            {
220251881Speter              const char *base_name;
221251881Speter              const char *original_target;
222251881Speter
223251881Speter              original_target = svn_dirent_internal_style(true_target, pool);
224251881Speter              SVN_ERR(svn_opt__arg_canonicalize_path(&true_target,
225251881Speter                                                     true_target, pool));
226251881Speter
227251881Speter              /* There are two situations in which a 'truepath-conversion'
228251881Speter                 (case-canonicalization to on-disk path on case-insensitive
229251881Speter                 filesystem) needs to be undone:
230251881Speter
231251881Speter                 1. If KEEP_LAST_ORIGPATH_ON_TRUEPATH_COLLISION is TRUE, and
232251881Speter                    this is the last target of a 2-element target list, and
233251881Speter                    both targets have the same truepath. */
234251881Speter              if (keep_last_origpath_on_truepath_collision
235251881Speter                  && input_targets->nelts == 2 && i == 1
236251881Speter                  && strcmp(original_target, true_target) != 0)
237251881Speter                {
238251881Speter                  const char *src_truepath = APR_ARRAY_IDX(output_targets,
239251881Speter                                                           0,
240251881Speter                                                           const char *);
241251881Speter                  if (strcmp(src_truepath, true_target) == 0)
242251881Speter                    true_target = original_target;
243251881Speter                }
244251881Speter
245251881Speter              /* 2. If there is an exact match in the wc-db without a
246251881Speter                    corresponding on-disk path (e.g. a scheduled-for-delete
247251881Speter                    file only differing in case from an on-disk file). */
248251881Speter              if (strcmp(original_target, true_target) != 0)
249251881Speter                {
250251881Speter                  const char *target_abspath;
251251881Speter                  svn_node_kind_t kind;
252251881Speter                  svn_error_t *err2;
253251881Speter
254251881Speter                  SVN_ERR(svn_dirent_get_absolute(&target_abspath,
255251881Speter                                                  original_target, pool));
256251881Speter                  err2 = svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
257251881Speter                                           TRUE, FALSE, pool);
258251881Speter                  if (err2
259251881Speter                      && (err2->apr_err == SVN_ERR_WC_NOT_WORKING_COPY
260251881Speter                          || err2->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
261251881Speter                    {
262251881Speter                      svn_error_clear(err2);
263251881Speter                    }
264251881Speter                  else
265251881Speter                    {
266251881Speter                      SVN_ERR(err2);
267251881Speter                      /* We successfully did a lookup in the wc-db. Now see
268251881Speter                         if it's something interesting. */
269251881Speter                      if (kind == svn_node_file || kind == svn_node_dir)
270251881Speter                        true_target = original_target;
271251881Speter                    }
272251881Speter                }
273251881Speter
274251881Speter              /* If the target has the same name as a Subversion
275251881Speter                 working copy administrative dir, skip it. */
276251881Speter              base_name = svn_dirent_basename(true_target, pool);
277251881Speter
278251881Speter              if (svn_wc_is_adm_dir(base_name, pool))
279251881Speter                {
280251881Speter                  if (!reserved_names)
281251881Speter                    reserved_names = apr_array_make(pool, DEFAULT_ARRAY_SIZE,
282251881Speter                                                    sizeof(const char *));
283251881Speter
284251881Speter                  APR_ARRAY_PUSH(reserved_names, const char *) = utf8_target;
285251881Speter
286251881Speter                  continue;
287251881Speter                }
288251881Speter            }
289251881Speter
290299742Sdim          target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL);
291251881Speter
292251881Speter          if (rel_url_found)
293251881Speter            {
294251881Speter              /* Later targets have priority over earlier target, I
295251881Speter                 don't know why, see basic_relative_url_multi_repo. */
296251881Speter              SVN_ERR(check_root_url_of_target(&root_url, target,
297251881Speter                                               ctx, pool));
298251881Speter            }
299251881Speter
300251881Speter          APR_ARRAY_PUSH(output_targets, const char *) = target;
301251881Speter        }
302251881Speter    }
303251881Speter
304251881Speter  /* Only resolve relative urls if there were some actually found earlier. */
305251881Speter  if (rel_url_found)
306251881Speter    {
307251881Speter      /*
308251881Speter       * Use the current directory's root url if one wasn't found using the
309251881Speter       * arguments.
310251881Speter       */
311251881Speter      if (root_url == NULL)
312251881Speter        {
313251881Speter          const char *current_abspath;
314251881Speter          svn_error_t *err;
315251881Speter
316251881Speter          SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
317251881Speter          err = svn_client_get_repos_root(&root_url, NULL /* uuid */,
318251881Speter                                          current_abspath, ctx, pool, pool);
319251881Speter          if (err || root_url == NULL)
320251881Speter            return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, err,
321251881Speter                                    _("Resolving '^/': no repository root "
322251881Speter                                      "found in the target arguments or "
323251881Speter                                      "in the current directory"));
324251881Speter        }
325251881Speter
326251881Speter      *targets_p = apr_array_make(pool, output_targets->nelts,
327251881Speter                                  sizeof(const char *));
328251881Speter
329251881Speter      for (i = 0; i < output_targets->nelts; i++)
330251881Speter        {
331251881Speter          const char *target = APR_ARRAY_IDX(output_targets, i,
332251881Speter                                             const char *);
333251881Speter
334251881Speter          if (svn_path_is_repos_relative_url(target))
335251881Speter            {
336251881Speter              const char *abs_target;
337251881Speter              const char *true_target;
338251881Speter              const char *peg_rev;
339251881Speter
340251881Speter              SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
341251881Speter                                                         target, pool));
342251881Speter
343251881Speter              SVN_ERR(svn_path_resolve_repos_relative_url(&abs_target,
344251881Speter                                                          true_target,
345251881Speter                                                          root_url, pool));
346251881Speter
347251881Speter              SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, abs_target,
348251881Speter                                                    pool));
349251881Speter
350299742Sdim              target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL);
351251881Speter            }
352251881Speter
353251881Speter          APR_ARRAY_PUSH(*targets_p, const char *) = target;
354251881Speter        }
355251881Speter    }
356251881Speter  else
357251881Speter    *targets_p = output_targets;
358251881Speter
359251881Speter  if (reserved_names)
360251881Speter    {
361251881Speter      svn_error_t *err = SVN_NO_ERROR;
362251881Speter
363251881Speter      for (i = 0; i < reserved_names->nelts; ++i)
364251881Speter        err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, err,
365251881Speter                                _("'%s' ends in a reserved name"),
366251881Speter                                APR_ARRAY_IDX(reserved_names, i,
367251881Speter                                              const char *));
368251881Speter      return svn_error_trace(err);
369251881Speter    }
370251881Speter
371251881Speter  return SVN_NO_ERROR;
372251881Speter}
373