1251881Speter/*
2251881Speter * add.c:  wrappers around wc add/mkdir functionality.
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
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <string.h>
31251881Speter#include <apr_lib.h>
32251881Speter#include <apr_fnmatch.h>
33251881Speter#include "svn_wc.h"
34251881Speter#include "svn_client.h"
35251881Speter#include "svn_string.h"
36251881Speter#include "svn_pools.h"
37251881Speter#include "svn_error.h"
38251881Speter#include "svn_dirent_uri.h"
39251881Speter#include "svn_path.h"
40251881Speter#include "svn_io.h"
41251881Speter#include "svn_config.h"
42251881Speter#include "svn_props.h"
43251881Speter#include "svn_hash.h"
44251881Speter#include "svn_sorts.h"
45251881Speter#include "client.h"
46251881Speter#include "svn_ctype.h"
47251881Speter
48251881Speter#include "private/svn_client_private.h"
49251881Speter#include "private/svn_wc_private.h"
50251881Speter#include "private/svn_ra_private.h"
51299742Sdim#include "private/svn_sorts_private.h"
52251881Speter#include "private/svn_magic.h"
53251881Speter
54251881Speter#include "svn_private_config.h"
55251881Speter
56251881Speter
57251881Speter
58251881Speter/*** Code. ***/
59251881Speter
60251881Speter/* Remove leading and trailing white space from a C string, in place. */
61251881Speterstatic void
62251881Spetertrim_string(char **pstr)
63251881Speter{
64251881Speter  char *str = *pstr;
65251881Speter  size_t i;
66251881Speter
67251881Speter  while (svn_ctype_isspace(*str))
68251881Speter    str++;
69251881Speter  *pstr = str;
70251881Speter  i = strlen(str);
71251881Speter  while ((i > 0) && svn_ctype_isspace(str[i-1]))
72251881Speter    i--;
73251881Speter  str[i] = '\0';
74251881Speter}
75251881Speter
76251881Speter/* Remove leading and trailing single- or double quotes from a C string,
77251881Speter * in place. */
78251881Speterstatic void
79251881Speterunquote_string(char **pstr)
80251881Speter{
81251881Speter  char *str = *pstr;
82251881Speter  size_t i = strlen(str);
83251881Speter
84251881Speter  if (i > 0 && ((*str == '"' && str[i - 1] == '"') ||
85251881Speter                (*str == '\'' && str[i - 1] == '\'')))
86251881Speter    {
87251881Speter      str[i - 1] = '\0';
88251881Speter      str++;
89251881Speter    }
90251881Speter  *pstr = str;
91251881Speter}
92251881Speter
93251881Speter/* Split PROPERTY and store each individual value in PROPS.
94251881Speter   Allocates from POOL. */
95251881Speterstatic void
96251881Spetersplit_props(apr_array_header_t **props,
97251881Speter            const char *property,
98251881Speter            apr_pool_t *pool)
99251881Speter{
100251881Speter  apr_array_header_t *temp_props;
101251881Speter  char *new_prop;
102251881Speter  int i = 0;
103251881Speter  int j = 0;
104251881Speter
105251881Speter  temp_props = apr_array_make(pool, 4, sizeof(char *));
106251881Speter  new_prop = apr_palloc(pool, strlen(property)+1);
107251881Speter
108251881Speter  for (i = 0; property[i] != '\0'; i++)
109251881Speter    {
110251881Speter      if (property[i] != ';')
111251881Speter        {
112251881Speter          new_prop[j] = property[i];
113251881Speter          j++;
114251881Speter        }
115251881Speter      else if (property[i] == ';')
116251881Speter        {
117251881Speter          /* ";;" becomes ";" */
118251881Speter          if (property[i+1] == ';')
119251881Speter            {
120251881Speter              new_prop[j] = ';';
121251881Speter              j++;
122251881Speter              i++;
123251881Speter            }
124251881Speter          else
125251881Speter            {
126251881Speter              new_prop[j] = '\0';
127251881Speter              APR_ARRAY_PUSH(temp_props, char *) = new_prop;
128251881Speter              new_prop += j + 1;
129251881Speter              j = 0;
130251881Speter            }
131251881Speter        }
132251881Speter    }
133251881Speter  new_prop[j] = '\0';
134251881Speter  APR_ARRAY_PUSH(temp_props, char *) = new_prop;
135251881Speter  *props = temp_props;
136251881Speter}
137251881Speter
138251881Speter/* PROPVALS is a hash mapping char * property names to const char * property
139251881Speter   values.  PROPERTIES can be empty but not NULL.
140251881Speter
141251881Speter   If FILENAME doesn't match the filename pattern PATTERN case insensitively,
142251881Speter   the do nothing.  Otherwise for each 'name':'value' pair in PROPVALS, add
143251881Speter   a new entry mappying 'name' to a svn_string_t * wrapping the 'value' in
144251881Speter   PROPERTIES.  The svn_string_t is allocated in the pool used to allocate
145251881Speter   PROPERTIES, but the char *'s from PROPVALS are re-used in PROPERTIES.
146251881Speter   If PROPVALS contains a 'svn:mime-type' mapping, then set *MIMETYPE to
147251881Speter   the mapped value.  Likewise if PROPVALS contains a mapping for
148251881Speter   svn:executable, then set *HAVE_EXECUTABLE to TRUE.
149251881Speter
150251881Speter   Use SCRATCH_POOL for temporary allocations.
151251881Speter*/
152251881Speterstatic void
153251881Speterget_auto_props_for_pattern(apr_hash_t *properties,
154251881Speter                           const char **mimetype,
155251881Speter                           svn_boolean_t *have_executable,
156251881Speter                           const char *filename,
157251881Speter                           const char *pattern,
158251881Speter                           apr_hash_t *propvals,
159251881Speter                           apr_pool_t *scratch_pool)
160251881Speter{
161251881Speter  apr_hash_index_t *hi;
162251881Speter
163251881Speter  /* check if filename matches and return if it doesn't */
164251881Speter  if (apr_fnmatch(pattern, filename,
165251881Speter                  APR_FNM_CASE_BLIND) == APR_FNM_NOMATCH)
166251881Speter    return;
167251881Speter
168251881Speter  for (hi = apr_hash_first(scratch_pool, propvals);
169251881Speter       hi != NULL;
170251881Speter       hi = apr_hash_next(hi))
171251881Speter    {
172299742Sdim      const char *propname = apr_hash_this_key(hi);
173299742Sdim      const char *propval = apr_hash_this_val(hi);
174251881Speter      svn_string_t *propval_str =
175251881Speter        svn_string_create_empty(apr_hash_pool_get(properties));
176251881Speter
177251881Speter      propval_str->data = propval;
178251881Speter      propval_str->len = strlen(propval);
179251881Speter
180251881Speter      svn_hash_sets(properties, propname, propval_str);
181251881Speter      if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
182251881Speter        *mimetype = propval;
183251881Speter      else if (strcmp(propname, SVN_PROP_EXECUTABLE) == 0)
184251881Speter        *have_executable = TRUE;
185251881Speter    }
186251881Speter}
187251881Speter
188251881Spetersvn_error_t *
189251881Spetersvn_client__get_paths_auto_props(apr_hash_t **properties,
190251881Speter                                 const char **mimetype,
191251881Speter                                 const char *path,
192251881Speter                                 svn_magic__cookie_t *magic_cookie,
193251881Speter                                 apr_hash_t *autoprops,
194251881Speter                                 svn_client_ctx_t *ctx,
195251881Speter                                 apr_pool_t *result_pool,
196251881Speter                                 apr_pool_t *scratch_pool)
197251881Speter{
198251881Speter  apr_hash_index_t *hi;
199251881Speter  svn_boolean_t have_executable = FALSE;
200251881Speter
201251881Speter  *properties = apr_hash_make(result_pool);
202251881Speter  *mimetype = NULL;
203251881Speter
204251881Speter  if (autoprops)
205251881Speter    {
206251881Speter      for (hi = apr_hash_first(scratch_pool, autoprops);
207251881Speter           hi != NULL;
208251881Speter           hi = apr_hash_next(hi))
209251881Speter        {
210299742Sdim          const char *pattern = apr_hash_this_key(hi);
211299742Sdim          apr_hash_t *propvals = apr_hash_this_val(hi);
212251881Speter
213251881Speter          get_auto_props_for_pattern(*properties, mimetype, &have_executable,
214251881Speter                                     svn_dirent_basename(path, scratch_pool),
215251881Speter                                     pattern, propvals, scratch_pool);
216251881Speter        }
217251881Speter    }
218251881Speter
219251881Speter  /* if mimetype has not been set check the file */
220251881Speter  if (! *mimetype)
221251881Speter    {
222251881Speter      SVN_ERR(svn_io_detect_mimetype2(mimetype, path, ctx->mimetypes_map,
223251881Speter                                      result_pool));
224251881Speter
225251881Speter      /* If we got no mime-type, or if it is "application/octet-stream",
226251881Speter       * try to get the mime-type from libmagic. */
227251881Speter      if (magic_cookie &&
228251881Speter          (!*mimetype ||
229251881Speter           strcmp(*mimetype, "application/octet-stream") == 0))
230251881Speter        {
231251881Speter          const char *magic_mimetype;
232251881Speter
233251881Speter         /* Since libmagic usually treats UTF-16 files as "text/plain",
234251881Speter          * svn_magic__detect_binary_mimetype() will return NULL for such
235251881Speter          * files. This is fine for now since we currently don't support
236251881Speter          * UTF-16-encoded text files (issue #2194).
237251881Speter          * Once we do support UTF-16 this code path will fail to detect
238251881Speter          * them as text unless the svn_io_detect_mimetype2() call above
239251881Speter          * returns "text/plain" for them. */
240251881Speter          SVN_ERR(svn_magic__detect_binary_mimetype(&magic_mimetype,
241251881Speter                                                    path, magic_cookie,
242251881Speter                                                    result_pool,
243251881Speter                                                    scratch_pool));
244251881Speter          if (magic_mimetype)
245251881Speter            *mimetype = magic_mimetype;
246251881Speter        }
247251881Speter
248251881Speter      if (*mimetype)
249251881Speter        apr_hash_set(*properties, SVN_PROP_MIME_TYPE,
250251881Speter                     strlen(SVN_PROP_MIME_TYPE),
251251881Speter                     svn_string_create(*mimetype, result_pool));
252251881Speter    }
253251881Speter
254251881Speter  /* if executable has not been set check the file */
255251881Speter  if (! have_executable)
256251881Speter    {
257251881Speter      svn_boolean_t executable = FALSE;
258251881Speter      SVN_ERR(svn_io_is_file_executable(&executable, path, scratch_pool));
259251881Speter      if (executable)
260251881Speter        apr_hash_set(*properties, SVN_PROP_EXECUTABLE,
261251881Speter                     strlen(SVN_PROP_EXECUTABLE),
262251881Speter                     svn_string_create_empty(result_pool));
263251881Speter    }
264251881Speter
265251881Speter  return SVN_NO_ERROR;
266251881Speter}
267251881Speter
268251881Speter/* Only call this if the on-disk node kind is a file. */
269251881Speterstatic svn_error_t *
270251881Speteradd_file(const char *local_abspath,
271251881Speter         svn_magic__cookie_t *magic_cookie,
272251881Speter         apr_hash_t *autoprops,
273251881Speter         svn_boolean_t no_autoprops,
274251881Speter         svn_client_ctx_t *ctx,
275251881Speter         apr_pool_t *pool)
276251881Speter{
277251881Speter  apr_hash_t *properties;
278251881Speter  const char *mimetype;
279251881Speter  svn_node_kind_t kind;
280251881Speter  svn_boolean_t is_special;
281251881Speter
282251881Speter  /* Check to see if this is a special file. */
283251881Speter  SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, pool));
284251881Speter
285251881Speter  /* Determine the properties that the file should have */
286251881Speter  if (is_special)
287251881Speter    {
288251881Speter      mimetype = NULL;
289251881Speter      properties = apr_hash_make(pool);
290251881Speter      svn_hash_sets(properties, SVN_PROP_SPECIAL,
291251881Speter                    svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool));
292251881Speter    }
293251881Speter  else
294251881Speter    {
295251881Speter      apr_hash_t *file_autoprops = NULL;
296251881Speter
297251881Speter      /* Get automatic properties */
298251881Speter      /* If we are setting autoprops grab the inherited svn:auto-props and
299251881Speter         config file auto-props for this file if we haven't already got them
300251881Speter         when iterating over the file's unversioned parents. */
301251881Speter      if (!no_autoprops)
302251881Speter        {
303251881Speter          if (autoprops == NULL)
304251881Speter            SVN_ERR(svn_client__get_all_auto_props(
305251881Speter              &file_autoprops, svn_dirent_dirname(local_abspath,pool),
306251881Speter              ctx, pool, pool));
307251881Speter          else
308251881Speter            file_autoprops = autoprops;
309251881Speter        }
310251881Speter
311251881Speter      /* This may fail on write-only files:
312251881Speter         we open them to estimate file type. */
313251881Speter      SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype,
314251881Speter                                               local_abspath, magic_cookie,
315251881Speter                                               file_autoprops, ctx, pool,
316251881Speter                                               pool));
317251881Speter    }
318251881Speter
319251881Speter  /* Add the file */
320299742Sdim  SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath, properties,
321299742Sdim                                FALSE /* skip checks */,
322251881Speter                                ctx->notify_func2, ctx->notify_baton2, pool));
323251881Speter
324251881Speter  return SVN_NO_ERROR;
325251881Speter}
326251881Speter
327251881Speter/* Schedule directory DIR_ABSPATH, and some of the tree under it, for
328251881Speter * addition.  DEPTH is the depth at this point in the descent (it may
329251881Speter * be changed for recursive calls).
330251881Speter *
331251881Speter * If DIR_ABSPATH (or any item below DIR_ABSPATH) is already scheduled for
332251881Speter * addition, add will fail and return an error unless FORCE is TRUE.
333251881Speter *
334251881Speter * Use MAGIC_COOKIE (which may be NULL) to detect the mime-type of files
335251881Speter * if necessary.
336251881Speter *
337251881Speter * If not NULL, CONFIG_AUTOPROPS is a hash representing the config file and
338251881Speter * svn:auto-props autoprops which apply to DIR_ABSPATH.  It maps
339251881Speter * const char * file patterns to another hash which maps const char *
340251881Speter * property names to const char *property values.  If CONFIG_AUTOPROPS is
341251881Speter * NULL and the config file and svn:auto-props autoprops are required by this
342251881Speter * function, then such will be obtained.
343251881Speter *
344251881Speter * If IGNORES is not NULL, then it is an array of const char * ignore patterns
345251881Speter * that apply to any children of DIR_ABSPATH.  If REFRESH_IGNORES is TRUE, then
346251881Speter * the passed in value of IGNORES (if any) is itself ignored and this function
347251881Speter * will gather all ignore patterns applicable to DIR_ABSPATH itself (allocated in
348251881Speter * RESULT_POOL).  Any recursive calls to this function get the refreshed ignore
349251881Speter * patterns.  If IGNORES is NULL and REFRESH_IGNORES is FALSE, then all children of DIR_ABSPATH
350251881Speter * are unconditionally added.
351251881Speter *
352251881Speter * If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to allow
353251881Speter * the user to cancel the operation.
354251881Speter *
355251881Speter * Use SCRATCH_POOL for temporary allocations.
356251881Speter */
357251881Speterstatic svn_error_t *
358251881Speteradd_dir_recursive(const char *dir_abspath,
359251881Speter                  svn_depth_t depth,
360251881Speter                  svn_boolean_t force,
361251881Speter                  svn_boolean_t no_autoprops,
362251881Speter                  svn_magic__cookie_t *magic_cookie,
363251881Speter                  apr_hash_t *config_autoprops,
364251881Speter                  svn_boolean_t refresh_ignores,
365251881Speter                  apr_array_header_t *ignores,
366251881Speter                  svn_client_ctx_t *ctx,
367251881Speter                  apr_pool_t *result_pool,
368251881Speter                  apr_pool_t *scratch_pool)
369251881Speter{
370251881Speter  svn_error_t *err;
371251881Speter  apr_pool_t *iterpool;
372251881Speter  apr_hash_t *dirents;
373251881Speter  apr_hash_index_t *hi;
374251881Speter  svn_boolean_t entry_exists = FALSE;
375251881Speter
376251881Speter  /* Check cancellation; note that this catches recursive calls too. */
377251881Speter  if (ctx->cancel_func)
378251881Speter    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
379251881Speter
380251881Speter  iterpool = svn_pool_create(scratch_pool);
381251881Speter
382251881Speter  /* Add this directory to revision control. */
383299742Sdim  err = svn_wc_add_from_disk3(ctx->wc_ctx, dir_abspath, NULL /*props*/,
384299742Sdim                              FALSE /* skip checks */,
385251881Speter                              ctx->notify_func2, ctx->notify_baton2,
386251881Speter                              iterpool);
387251881Speter  if (err)
388251881Speter    {
389251881Speter      if (err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
390251881Speter        {
391251881Speter          svn_error_clear(err);
392251881Speter          entry_exists = TRUE;
393251881Speter        }
394251881Speter      else if (err)
395251881Speter        {
396251881Speter          return svn_error_trace(err);
397251881Speter        }
398251881Speter    }
399251881Speter
400251881Speter  /* Fetch ignores after adding to handle ignores on the directory itself
401251881Speter     and ancestors via the single db optimization in libsvn_wc */
402251881Speter  if (refresh_ignores)
403251881Speter    SVN_ERR(svn_wc_get_ignores2(&ignores, ctx->wc_ctx, dir_abspath,
404251881Speter                                ctx->config, result_pool, iterpool));
405251881Speter
406251881Speter  /* If DIR_ABSPATH is the root of an unversioned subtree then get the
407251881Speter     following "autoprops":
408251881Speter
409251881Speter       1) Explicit and inherited svn:auto-props properties on
410251881Speter          DIR_ABSPATH
411251881Speter       2) auto-props from the CTX->CONFIG hash
412251881Speter
413251881Speter     Since this set of autoprops applies to all unversioned children of
414251881Speter     DIR_ABSPATH, we will pass these along to any recursive calls to
415251881Speter     add_dir_recursive() and calls to add_file() below.  Thus sparing
416251881Speter     these callees from looking up the same information. */
417251881Speter  if (!entry_exists && config_autoprops == NULL)
418251881Speter    {
419251881Speter      SVN_ERR(svn_client__get_all_auto_props(&config_autoprops, dir_abspath,
420251881Speter                                             ctx, scratch_pool, iterpool));
421251881Speter    }
422251881Speter
423251881Speter  SVN_ERR(svn_io_get_dirents3(&dirents, dir_abspath, TRUE, scratch_pool,
424251881Speter                              iterpool));
425251881Speter
426251881Speter  /* Read the directory entries one by one and add those things to
427251881Speter     version control. */
428251881Speter  for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
429251881Speter    {
430299742Sdim      const char *name = apr_hash_this_key(hi);
431299742Sdim      svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
432251881Speter      const char *abspath;
433251881Speter
434251881Speter      svn_pool_clear(iterpool);
435251881Speter
436251881Speter      /* Check cancellation so you can cancel during an
437251881Speter       * add of a directory with lots of files. */
438251881Speter      if (ctx->cancel_func)
439251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
440251881Speter
441251881Speter      /* Skip over SVN admin directories. */
442251881Speter      if (svn_wc_is_adm_dir(name, iterpool))
443251881Speter        continue;
444251881Speter
445251881Speter      if (ignores
446251881Speter          && svn_wc_match_ignore_list(name, ignores, iterpool))
447251881Speter        continue;
448251881Speter
449251881Speter      /* Construct the full path of the entry. */
450251881Speter      abspath = svn_dirent_join(dir_abspath, name, iterpool);
451251881Speter
452251881Speter      /* Recurse on directories; add files; ignore the rest. */
453251881Speter      if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates)
454251881Speter        {
455251881Speter          svn_depth_t depth_below_here = depth;
456251881Speter          if (depth == svn_depth_immediates)
457251881Speter            depth_below_here = svn_depth_empty;
458251881Speter
459251881Speter          /* When DIR_ABSPATH is the root of an unversioned subtree then
460251881Speter             it and all of its children have the same set of ignores.  So
461251881Speter             save any recursive calls the extra work of finding the same
462251881Speter             set of ignores. */
463251881Speter          if (refresh_ignores && !entry_exists)
464251881Speter            refresh_ignores = FALSE;
465251881Speter
466251881Speter          SVN_ERR(add_dir_recursive(abspath, depth_below_here,
467251881Speter                                    force, no_autoprops,
468251881Speter                                    magic_cookie, config_autoprops,
469251881Speter                                    refresh_ignores, ignores, ctx,
470251881Speter                                    result_pool, iterpool));
471251881Speter        }
472251881Speter      else if ((dirent->kind == svn_node_file || dirent->special)
473251881Speter               && depth >= svn_depth_files)
474251881Speter        {
475251881Speter          err = add_file(abspath, magic_cookie, config_autoprops,
476251881Speter                         no_autoprops, ctx, iterpool);
477251881Speter          if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
478251881Speter            svn_error_clear(err);
479251881Speter          else
480251881Speter            SVN_ERR(err);
481251881Speter        }
482251881Speter    }
483251881Speter
484251881Speter  /* Destroy the per-iteration pool. */
485251881Speter  svn_pool_destroy(iterpool);
486251881Speter
487251881Speter  return SVN_NO_ERROR;
488251881Speter}
489251881Speter
490251881Speter/* This structure is used as baton for collecting the config entries
491251881Speter   in the auto-props section and any inherited svn:auto-props
492251881Speter   properties.
493251881Speter*/
494251881Spetertypedef struct collect_auto_props_baton_t
495251881Speter{
496251881Speter  /* the hash table for storing the property name/value pairs */
497251881Speter  apr_hash_t *autoprops;
498251881Speter
499251881Speter  /* a pool used for allocating memory */
500251881Speter  apr_pool_t *result_pool;
501251881Speter} collect_auto_props_baton_t;
502251881Speter
503251881Speter/* Implements svn_config_enumerator2_t callback.
504251881Speter
505251881Speter   For one auto-props config entry (NAME, VALUE), stash a copy of
506251881Speter   NAME and VALUE, allocated in BATON->POOL, in BATON->AUTOPROP.
507251881Speter   BATON must point to an collect_auto_props_baton_t.
508251881Speter*/
509251881Speterstatic svn_boolean_t
510251881Speterall_auto_props_collector(const char *name,
511251881Speter                         const char *value,
512251881Speter                         void *baton,
513251881Speter                         apr_pool_t *pool)
514251881Speter{
515251881Speter  collect_auto_props_baton_t *autoprops_baton = baton;
516251881Speter  apr_array_header_t *autoprops;
517251881Speter  int i;
518251881Speter
519251881Speter  /* nothing to do here without a value */
520251881Speter  if (*value == 0)
521251881Speter    return TRUE;
522251881Speter
523251881Speter  split_props(&autoprops, value, pool);
524251881Speter
525251881Speter  for (i = 0; i < autoprops->nelts; i ++)
526251881Speter    {
527251881Speter      size_t len;
528251881Speter      const char *this_value;
529251881Speter      char *property = APR_ARRAY_IDX(autoprops, i, char *);
530251881Speter      char *equal_sign = strchr(property, '=');
531251881Speter
532251881Speter      if (equal_sign)
533251881Speter        {
534251881Speter          *equal_sign = '\0';
535251881Speter          equal_sign++;
536251881Speter          trim_string(&equal_sign);
537251881Speter          unquote_string(&equal_sign);
538251881Speter          this_value = equal_sign;
539251881Speter        }
540251881Speter      else
541251881Speter        {
542251881Speter          this_value = "";
543251881Speter        }
544251881Speter      trim_string(&property);
545251881Speter      len = strlen(property);
546251881Speter
547251881Speter      if (len > 0)
548251881Speter        {
549251881Speter          apr_hash_t *pattern_hash = svn_hash_gets(autoprops_baton->autoprops,
550251881Speter                                                   name);
551251881Speter          svn_string_t *propval;
552251881Speter
553251881Speter          /* Force reserved boolean property values to '*'. */
554251881Speter          if (svn_prop_is_boolean(property))
555251881Speter            {
556251881Speter              /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
557251881Speter              propval = svn_string_create("*", autoprops_baton->result_pool);
558251881Speter            }
559251881Speter          else
560251881Speter            {
561251881Speter              propval = svn_string_create(this_value,
562251881Speter                                          autoprops_baton->result_pool);
563251881Speter            }
564251881Speter
565251881Speter          if (!pattern_hash)
566251881Speter            {
567251881Speter              pattern_hash = apr_hash_make(autoprops_baton->result_pool);
568251881Speter              svn_hash_sets(autoprops_baton->autoprops,
569251881Speter                            apr_pstrdup(autoprops_baton->result_pool, name),
570251881Speter                            pattern_hash);
571251881Speter            }
572251881Speter          svn_hash_sets(pattern_hash,
573251881Speter                        apr_pstrdup(autoprops_baton->result_pool, property),
574251881Speter                        propval->data);
575251881Speter        }
576251881Speter    }
577251881Speter  return TRUE;
578251881Speter}
579251881Speter
580251881Speter/* Go up the directory tree from LOCAL_ABSPATH, looking for a versioned
581251881Speter * directory.  If found, return its path in *EXISTING_PARENT_ABSPATH.
582251881Speter * Otherwise, return SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */
583251881Speterstatic svn_error_t *
584251881Speterfind_existing_parent(const char **existing_parent_abspath,
585251881Speter                     svn_client_ctx_t *ctx,
586251881Speter                     const char *local_abspath,
587251881Speter                     apr_pool_t *result_pool,
588251881Speter                     apr_pool_t *scratch_pool)
589251881Speter{
590251881Speter  svn_node_kind_t kind;
591251881Speter  const char *parent_abspath;
592251881Speter  svn_wc_context_t *wc_ctx = ctx->wc_ctx;
593251881Speter
594251881Speter  SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, local_abspath,
595251881Speter                            FALSE, FALSE, scratch_pool));
596251881Speter
597251881Speter  if (kind == svn_node_dir)
598251881Speter    {
599251881Speter      *existing_parent_abspath = apr_pstrdup(result_pool, local_abspath);
600251881Speter      return SVN_NO_ERROR;
601251881Speter    }
602251881Speter
603251881Speter  if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
604251881Speter    return svn_error_create(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, NULL);
605251881Speter
606251881Speter  if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, scratch_pool),
607251881Speter                        scratch_pool))
608251881Speter    return svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, NULL,
609251881Speter                             _("'%s' ends in a reserved name"),
610251881Speter                             svn_dirent_local_style(local_abspath,
611251881Speter                                                    scratch_pool));
612251881Speter
613251881Speter  parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
614251881Speter
615251881Speter  if (ctx->cancel_func)
616251881Speter    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
617251881Speter
618251881Speter  SVN_ERR(find_existing_parent(existing_parent_abspath, ctx, parent_abspath,
619251881Speter                               result_pool, scratch_pool));
620251881Speter
621251881Speter  return SVN_NO_ERROR;
622251881Speter}
623251881Speter
624251881Spetersvn_error_t *
625251881Spetersvn_client__get_all_auto_props(apr_hash_t **autoprops,
626251881Speter                               const char *path_or_url,
627251881Speter                               svn_client_ctx_t *ctx,
628251881Speter                               apr_pool_t *result_pool,
629251881Speter                               apr_pool_t *scratch_pool)
630251881Speter{
631251881Speter  int i;
632251881Speter  apr_array_header_t *inherited_config_auto_props;
633251881Speter  apr_hash_t *props;
634251881Speter  svn_opt_revision_t rev;
635251881Speter  svn_string_t *config_auto_prop;
636251881Speter  svn_boolean_t use_autoprops;
637251881Speter  collect_auto_props_baton_t autoprops_baton;
638251881Speter  svn_error_t *err = NULL;
639251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
640251881Speter  svn_boolean_t target_is_url = svn_path_is_url(path_or_url);
641251881Speter  svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config,
642251881Speter                                                  SVN_CONFIG_CATEGORY_CONFIG)
643251881Speter                                  : NULL;
644251881Speter  *autoprops = apr_hash_make(result_pool);
645251881Speter  autoprops_baton.result_pool = result_pool;
646251881Speter  autoprops_baton.autoprops = *autoprops;
647251881Speter
648251881Speter
649251881Speter  /* Are "traditional" auto-props enabled?  If so grab them from the
650251881Speter    config.  This is our starting set auto-props, which may be overriden
651251881Speter    by svn:auto-props. */
652251881Speter  SVN_ERR(svn_config_get_bool(cfg, &use_autoprops,
653251881Speter                              SVN_CONFIG_SECTION_MISCELLANY,
654251881Speter                              SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE));
655251881Speter  if (use_autoprops)
656251881Speter    svn_config_enumerate2(cfg, SVN_CONFIG_SECTION_AUTO_PROPS,
657251881Speter                          all_auto_props_collector, &autoprops_baton,
658251881Speter                          scratch_pool);
659251881Speter
660251881Speter  /* Convert the config file setting (if any) into a hash mapping file
661251881Speter     patterns to as hash of prop-->val mappings. */
662251881Speter  if (svn_path_is_url(path_or_url))
663251881Speter    rev.kind = svn_opt_revision_head;
664251881Speter  else
665251881Speter    rev.kind = svn_opt_revision_working;
666251881Speter
667251881Speter  /* If PATH_OR_URL is a WC path, then it might be unversioned, in which case
668251881Speter     we find it's nearest versioned parent. */
669251881Speter  do
670251881Speter    {
671251881Speter      err = svn_client_propget5(&props, &inherited_config_auto_props,
672251881Speter                                SVN_PROP_INHERITABLE_AUTO_PROPS, path_or_url,
673251881Speter                                &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
674251881Speter                                scratch_pool, iterpool);
675251881Speter      if (err)
676251881Speter        {
677251881Speter          if (target_is_url || err->apr_err != SVN_ERR_UNVERSIONED_RESOURCE)
678251881Speter            return svn_error_trace(err);
679251881Speter
680251881Speter          svn_error_clear(err);
681251881Speter          err = NULL;
682251881Speter          SVN_ERR(find_existing_parent(&path_or_url, ctx, path_or_url,
683251881Speter                                       scratch_pool, iterpool));
684251881Speter        }
685251881Speter      else
686251881Speter        {
687251881Speter          break;
688251881Speter        }
689251881Speter    }
690251881Speter  while (err == NULL);
691251881Speter
692251881Speter  /* Stash any explicit PROPS for PARENT_PATH into the inherited props array,
693251881Speter     since these are actually inherited props for LOCAL_ABSPATH. */
694251881Speter  config_auto_prop = svn_hash_gets(props, path_or_url);
695251881Speter
696251881Speter  if (config_auto_prop)
697251881Speter    {
698251881Speter      svn_prop_inherited_item_t *new_iprop =
699251881Speter        apr_palloc(scratch_pool, sizeof(*new_iprop));
700251881Speter      new_iprop->path_or_url = path_or_url;
701251881Speter      new_iprop->prop_hash = apr_hash_make(scratch_pool);
702251881Speter      svn_hash_sets(new_iprop->prop_hash, SVN_PROP_INHERITABLE_AUTO_PROPS,
703251881Speter                    config_auto_prop);
704251881Speter      APR_ARRAY_PUSH(inherited_config_auto_props,
705251881Speter                     svn_prop_inherited_item_t *) = new_iprop;
706251881Speter    }
707251881Speter
708251881Speter  for (i = 0; i < inherited_config_auto_props->nelts; i++)
709251881Speter    {
710251881Speter      svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
711251881Speter        inherited_config_auto_props, i, svn_prop_inherited_item_t *);
712299742Sdim      const svn_string_t *propval =
713299742Sdim        svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_AUTO_PROPS);
714251881Speter
715251881Speter        {
716251881Speter          const char *ch = propval->data;
717251881Speter          svn_stringbuf_t *config_auto_prop_pattern;
718251881Speter          svn_stringbuf_t *config_auto_prop_val;
719251881Speter
720251881Speter          svn_pool_clear(iterpool);
721251881Speter
722251881Speter          config_auto_prop_pattern = svn_stringbuf_create_empty(iterpool);
723251881Speter          config_auto_prop_val = svn_stringbuf_create_empty(iterpool);
724251881Speter
725251881Speter          /* Parse svn:auto-props value. */
726251881Speter          while (*ch != '\0')
727251881Speter            {
728251881Speter              svn_stringbuf_setempty(config_auto_prop_pattern);
729251881Speter              svn_stringbuf_setempty(config_auto_prop_val);
730251881Speter
731251881Speter              /* Parse the file pattern. */
732251881Speter              while (*ch != '\0' && *ch != '=' && *ch != '\n')
733251881Speter                {
734251881Speter                  svn_stringbuf_appendbyte(config_auto_prop_pattern, *ch);
735251881Speter                  ch++;
736251881Speter                }
737251881Speter
738251881Speter              svn_stringbuf_strip_whitespace(config_auto_prop_pattern);
739251881Speter
740251881Speter              /* Parse the auto-prop group. */
741251881Speter              while (*ch != '\0' && *ch != '\n')
742251881Speter                {
743251881Speter                  svn_stringbuf_appendbyte(config_auto_prop_val, *ch);
744251881Speter                  ch++;
745251881Speter                }
746251881Speter
747251881Speter              /* Strip leading '=' and whitespace from auto-prop group. */
748251881Speter              if (config_auto_prop_val->data[0] == '=')
749251881Speter                svn_stringbuf_remove(config_auto_prop_val, 0, 1);
750251881Speter              svn_stringbuf_strip_whitespace(config_auto_prop_val);
751251881Speter
752251881Speter              all_auto_props_collector(config_auto_prop_pattern->data,
753251881Speter                                       config_auto_prop_val->data,
754251881Speter                                       &autoprops_baton,
755251881Speter                                       scratch_pool);
756251881Speter
757251881Speter              /* Skip to next line if any. */
758251881Speter              while (*ch != '\0' && *ch != '\n')
759251881Speter                ch++;
760251881Speter              if (*ch == '\n')
761251881Speter                ch++;
762251881Speter            }
763251881Speter        }
764251881Speter    }
765251881Speter
766251881Speter  svn_pool_destroy(iterpool);
767251881Speter
768251881Speter  return SVN_NO_ERROR;
769251881Speter}
770251881Speter
771251881Speter/* The main logic of the public svn_client_add5.
772251881Speter *
773251881Speter * EXISTING_PARENT_ABSPATH is the absolute path to the first existing
774251881Speter * parent directory of local_abspath. If not NULL, all missing parents
775251881Speter * of LOCAL_ABSPATH must be created before LOCAL_ABSPATH can be added. */
776251881Speterstatic svn_error_t *
777251881Speteradd(const char *local_abspath,
778251881Speter    svn_depth_t depth,
779251881Speter    svn_boolean_t force,
780251881Speter    svn_boolean_t no_ignore,
781251881Speter    svn_boolean_t no_autoprops,
782251881Speter    const char *existing_parent_abspath,
783251881Speter    svn_client_ctx_t *ctx,
784251881Speter    apr_pool_t *scratch_pool)
785251881Speter{
786251881Speter  svn_node_kind_t kind;
787251881Speter  svn_error_t *err;
788251881Speter  svn_magic__cookie_t *magic_cookie;
789251881Speter  apr_array_header_t *ignores = NULL;
790251881Speter
791299742Sdim  SVN_ERR(svn_magic__init(&magic_cookie, ctx->config, scratch_pool));
792251881Speter
793251881Speter  if (existing_parent_abspath)
794251881Speter    {
795251881Speter      const char *parent_abspath;
796251881Speter      const char *child_relpath;
797251881Speter      apr_array_header_t *components;
798251881Speter      int i;
799251881Speter      apr_pool_t *iterpool;
800251881Speter
801251881Speter      parent_abspath = existing_parent_abspath;
802251881Speter      child_relpath = svn_dirent_is_child(existing_parent_abspath,
803251881Speter                                          local_abspath, NULL);
804251881Speter      components = svn_path_decompose(child_relpath, scratch_pool);
805251881Speter      iterpool = svn_pool_create(scratch_pool);
806251881Speter      for (i = 0; i < components->nelts - 1; i++)
807251881Speter        {
808251881Speter          const char *component;
809251881Speter          svn_node_kind_t disk_kind;
810251881Speter
811251881Speter          svn_pool_clear(iterpool);
812251881Speter
813251881Speter          if (ctx->cancel_func)
814251881Speter            SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
815251881Speter
816251881Speter          component = APR_ARRAY_IDX(components, i, const char *);
817251881Speter          parent_abspath = svn_dirent_join(parent_abspath, component,
818251881Speter                                           scratch_pool);
819251881Speter          SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, iterpool));
820251881Speter          if (disk_kind != svn_node_none && disk_kind != svn_node_dir)
821251881Speter            return svn_error_createf(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL,
822251881Speter                                     _("'%s' prevents creating parent of '%s'"),
823251881Speter                                     parent_abspath, local_abspath);
824251881Speter
825251881Speter          SVN_ERR(svn_io_make_dir_recursively(parent_abspath, scratch_pool));
826299742Sdim          SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, parent_abspath,
827251881Speter                                        NULL /*props*/,
828299742Sdim                                        FALSE /* skip checks */,
829251881Speter                                        ctx->notify_func2, ctx->notify_baton2,
830251881Speter                                        scratch_pool));
831251881Speter        }
832251881Speter      svn_pool_destroy(iterpool);
833251881Speter    }
834251881Speter
835251881Speter  SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
836251881Speter  if (kind == svn_node_dir)
837251881Speter    {
838251881Speter      /* We use add_dir_recursive for all directory targets
839251881Speter         and pass depth along no matter what it is, so that the
840251881Speter         target's depth will be set correctly. */
841251881Speter      err = add_dir_recursive(local_abspath, depth, force,
842251881Speter                              no_autoprops, magic_cookie, NULL,
843251881Speter                              !no_ignore, ignores, ctx,
844251881Speter                              scratch_pool, scratch_pool);
845251881Speter    }
846251881Speter  else if (kind == svn_node_file)
847251881Speter    err = add_file(local_abspath, magic_cookie, NULL,
848251881Speter                   no_autoprops, ctx, scratch_pool);
849251881Speter  else if (kind == svn_node_none)
850251881Speter    {
851251881Speter      svn_boolean_t tree_conflicted;
852251881Speter
853251881Speter      /* Provide a meaningful error message if the node does not exist
854251881Speter       * on disk but is a tree conflict victim. */
855251881Speter      err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
856251881Speter                                 ctx->wc_ctx, local_abspath,
857251881Speter                                 scratch_pool);
858251881Speter      if (err)
859251881Speter        svn_error_clear(err);
860251881Speter      else if (tree_conflicted)
861251881Speter        return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
862251881Speter                                 _("'%s' is an existing item in conflict; "
863251881Speter                                   "please mark the conflict as resolved "
864251881Speter                                   "before adding a new item here"),
865251881Speter                                 svn_dirent_local_style(local_abspath,
866251881Speter                                                        scratch_pool));
867251881Speter
868251881Speter      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
869251881Speter                               _("'%s' not found"),
870251881Speter                               svn_dirent_local_style(local_abspath,
871251881Speter                                                      scratch_pool));
872251881Speter    }
873251881Speter  else
874251881Speter    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
875251881Speter                             _("Unsupported node kind for path '%s'"),
876251881Speter                             svn_dirent_local_style(local_abspath,
877251881Speter                                                    scratch_pool));
878251881Speter
879251881Speter  /* Ignore SVN_ERR_ENTRY_EXISTS when FORCE is set.  */
880251881Speter  if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
881251881Speter    {
882251881Speter      svn_error_clear(err);
883251881Speter      err = SVN_NO_ERROR;
884251881Speter    }
885251881Speter  return svn_error_trace(err);
886251881Speter}
887251881Speter
888251881Speter
889251881Speter
890251881Spetersvn_error_t *
891251881Spetersvn_client_add5(const char *path,
892251881Speter                svn_depth_t depth,
893251881Speter                svn_boolean_t force,
894251881Speter                svn_boolean_t no_ignore,
895251881Speter                svn_boolean_t no_autoprops,
896251881Speter                svn_boolean_t add_parents,
897251881Speter                svn_client_ctx_t *ctx,
898251881Speter                apr_pool_t *scratch_pool)
899251881Speter{
900251881Speter  const char *parent_abspath;
901251881Speter  const char *local_abspath;
902251881Speter  const char *existing_parent_abspath;
903251881Speter  svn_boolean_t is_wc_root;
904251881Speter  svn_error_t *err;
905251881Speter
906251881Speter  if (svn_path_is_url(path))
907251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
908251881Speter                             _("'%s' is not a local path"), path);
909251881Speter
910251881Speter  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
911251881Speter
912251881Speter  /* See if we're being asked to add a wc-root.  That's typically not
913251881Speter     okay, unless we're in "force" mode.  svn_wc__is_wcroot()
914251881Speter     will return TRUE even if LOCAL_ABSPATH is a *symlink* to a working
915251881Speter     copy root, which is a scenario we want to treat differently.  */
916251881Speter  err = svn_wc__is_wcroot(&is_wc_root, ctx->wc_ctx, local_abspath,
917251881Speter                          scratch_pool);
918251881Speter  if (err)
919251881Speter    {
920251881Speter      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
921251881Speter          && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
922251881Speter        {
923251881Speter          return svn_error_trace(err);
924251881Speter        }
925251881Speter
926251881Speter      svn_error_clear(err);
927251881Speter      err = NULL; /* SVN_NO_ERROR */
928251881Speter      is_wc_root = FALSE;
929251881Speter    }
930251881Speter  if (is_wc_root)
931251881Speter    {
932251881Speter#ifdef HAVE_SYMLINK
933251881Speter      svn_node_kind_t disk_kind;
934251881Speter      svn_boolean_t is_special;
935251881Speter
936251881Speter      SVN_ERR(svn_io_check_special_path(local_abspath, &disk_kind, &is_special,
937251881Speter                                        scratch_pool));
938251881Speter
939251881Speter      /* A symlink can be an unversioned target and a wcroot. Lets try to add
940251881Speter         the symlink, which can't be a wcroot. */
941251881Speter      if (is_special)
942251881Speter        is_wc_root = FALSE;
943251881Speter      else
944251881Speter#endif
945251881Speter        {
946251881Speter          if (! force)
947251881Speter            return svn_error_createf(
948251881Speter                                 SVN_ERR_ENTRY_EXISTS, NULL,
949251881Speter                                 _("'%s' is already under version control"),
950251881Speter                                 svn_dirent_local_style(local_abspath,
951251881Speter                                                        scratch_pool));
952251881Speter        }
953251881Speter    }
954251881Speter
955251881Speter  if (is_wc_root)
956251881Speter    parent_abspath = local_abspath; /* We will only add children */
957251881Speter  else
958251881Speter    parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
959251881Speter
960251881Speter  existing_parent_abspath = NULL;
961251881Speter  if (add_parents && !is_wc_root)
962251881Speter    {
963251881Speter      apr_pool_t *subpool;
964251881Speter      const char *existing_parent_abspath2;
965251881Speter
966251881Speter      subpool = svn_pool_create(scratch_pool);
967251881Speter      SVN_ERR(find_existing_parent(&existing_parent_abspath2, ctx,
968251881Speter                                   parent_abspath, scratch_pool, subpool));
969251881Speter      if (strcmp(existing_parent_abspath2, parent_abspath) != 0)
970251881Speter        existing_parent_abspath = existing_parent_abspath2;
971251881Speter      svn_pool_destroy(subpool);
972251881Speter    }
973251881Speter
974251881Speter  SVN_WC__CALL_WITH_WRITE_LOCK(
975251881Speter    add(local_abspath, depth, force, no_ignore, no_autoprops,
976251881Speter        existing_parent_abspath, ctx, scratch_pool),
977251881Speter    ctx->wc_ctx, (existing_parent_abspath ? existing_parent_abspath
978251881Speter                                          : parent_abspath),
979251881Speter    FALSE /* lock_anchor */, scratch_pool);
980251881Speter  return SVN_NO_ERROR;
981251881Speter}
982251881Speter
983251881Speter
984251881Speterstatic svn_error_t *
985251881Speterpath_driver_cb_func(void **dir_baton,
986251881Speter                    void *parent_baton,
987251881Speter                    void *callback_baton,
988251881Speter                    const char *path,
989251881Speter                    apr_pool_t *pool)
990251881Speter{
991251881Speter  const svn_delta_editor_t *editor = callback_baton;
992251881Speter  SVN_ERR(svn_path_check_valid(path, pool));
993251881Speter  return editor->add_directory(path, parent_baton, NULL,
994251881Speter                               SVN_INVALID_REVNUM, pool, dir_baton);
995251881Speter}
996251881Speter
997251881Speter/* Append URL, and all it's non-existent parent directories, to TARGETS.
998251881Speter   Use TEMPPOOL for temporary allocations and POOL for any additions to
999251881Speter   TARGETS. */
1000251881Speterstatic svn_error_t *
1001251881Speteradd_url_parents(svn_ra_session_t *ra_session,
1002251881Speter                const char *url,
1003251881Speter                apr_array_header_t *targets,
1004251881Speter                apr_pool_t *temppool,
1005251881Speter                apr_pool_t *pool)
1006251881Speter{
1007251881Speter  svn_node_kind_t kind;
1008251881Speter  const char *parent_url = svn_uri_dirname(url, pool);
1009251881Speter
1010251881Speter  SVN_ERR(svn_ra_reparent(ra_session, parent_url, temppool));
1011251881Speter  SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1012251881Speter                            temppool));
1013251881Speter
1014251881Speter  if (kind == svn_node_none)
1015251881Speter    SVN_ERR(add_url_parents(ra_session, parent_url, targets, temppool, pool));
1016251881Speter
1017251881Speter  APR_ARRAY_PUSH(targets, const char *) = url;
1018251881Speter
1019251881Speter  return SVN_NO_ERROR;
1020251881Speter}
1021251881Speter
1022251881Speterstatic svn_error_t *
1023251881Spetermkdir_urls(const apr_array_header_t *urls,
1024251881Speter           svn_boolean_t make_parents,
1025251881Speter           const apr_hash_t *revprop_table,
1026251881Speter           svn_commit_callback2_t commit_callback,
1027251881Speter           void *commit_baton,
1028251881Speter           svn_client_ctx_t *ctx,
1029251881Speter           apr_pool_t *pool)
1030251881Speter{
1031251881Speter  svn_ra_session_t *ra_session = NULL;
1032251881Speter  const svn_delta_editor_t *editor;
1033251881Speter  void *edit_baton;
1034251881Speter  const char *log_msg;
1035251881Speter  apr_array_header_t *targets;
1036251881Speter  apr_hash_t *targets_hash;
1037251881Speter  apr_hash_t *commit_revprops;
1038251881Speter  svn_error_t *err;
1039251881Speter  const char *common;
1040251881Speter  int i;
1041251881Speter
1042251881Speter  /* Find any non-existent parent directories */
1043251881Speter  if (make_parents)
1044251881Speter    {
1045251881Speter      apr_array_header_t *all_urls = apr_array_make(pool, urls->nelts,
1046251881Speter                                                    sizeof(const char *));
1047251881Speter      const char *first_url = APR_ARRAY_IDX(urls, 0, const char *);
1048251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
1049251881Speter
1050251881Speter      SVN_ERR(svn_client_open_ra_session2(&ra_session, first_url, NULL,
1051251881Speter                                          ctx, pool, iterpool));
1052251881Speter
1053251881Speter      for (i = 0; i < urls->nelts; i++)
1054251881Speter        {
1055251881Speter          const char *url = APR_ARRAY_IDX(urls, i, const char *);
1056251881Speter
1057251881Speter          svn_pool_clear(iterpool);
1058251881Speter          SVN_ERR(add_url_parents(ra_session, url, all_urls, iterpool, pool));
1059251881Speter        }
1060251881Speter
1061251881Speter      svn_pool_destroy(iterpool);
1062251881Speter
1063251881Speter      urls = all_urls;
1064251881Speter    }
1065251881Speter
1066251881Speter  /* Condense our list of mkdir targets. */
1067251881Speter  SVN_ERR(svn_uri_condense_targets(&common, &targets, urls, FALSE,
1068251881Speter                                   pool, pool));
1069251881Speter
1070251881Speter  /*Remove duplicate targets introduced by make_parents with more targets. */
1071251881Speter  SVN_ERR(svn_hash_from_cstring_keys(&targets_hash, targets, pool));
1072251881Speter  SVN_ERR(svn_hash_keys(&targets, targets_hash, pool));
1073251881Speter
1074251881Speter  if (! targets->nelts)
1075251881Speter    {
1076251881Speter      const char *bname;
1077251881Speter      svn_uri_split(&common, &bname, common, pool);
1078251881Speter      APR_ARRAY_PUSH(targets, const char *) = bname;
1079251881Speter
1080251881Speter      if (*bname == '\0')
1081251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1082251881Speter                                 _("There is no valid URI above '%s'"),
1083251881Speter                                 common);
1084251881Speter    }
1085251881Speter  else
1086251881Speter    {
1087251881Speter      svn_boolean_t resplit = FALSE;
1088251881Speter
1089251881Speter      /* We can't "mkdir" the root of an editor drive, so if one of
1090251881Speter         our targets is the empty string, we need to back everything
1091251881Speter         up by a path component. */
1092251881Speter      for (i = 0; i < targets->nelts; i++)
1093251881Speter        {
1094251881Speter          const char *path = APR_ARRAY_IDX(targets, i, const char *);
1095251881Speter          if (! *path)
1096251881Speter            {
1097251881Speter              resplit = TRUE;
1098251881Speter              break;
1099251881Speter            }
1100251881Speter        }
1101251881Speter      if (resplit)
1102251881Speter        {
1103251881Speter          const char *bname;
1104251881Speter
1105251881Speter          svn_uri_split(&common, &bname, common, pool);
1106251881Speter
1107251881Speter          if (*bname == '\0')
1108251881Speter             return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1109251881Speter                                      _("There is no valid URI above '%s'"),
1110251881Speter                                      common);
1111251881Speter
1112251881Speter          for (i = 0; i < targets->nelts; i++)
1113251881Speter            {
1114251881Speter              const char *path = APR_ARRAY_IDX(targets, i, const char *);
1115251881Speter              path = svn_relpath_join(bname, path, pool);
1116251881Speter              APR_ARRAY_IDX(targets, i, const char *) = path;
1117251881Speter            }
1118251881Speter        }
1119251881Speter    }
1120251881Speter
1121299742Sdim  svn_sort__array(targets, svn_sort_compare_paths);
1122299742Sdim
1123251881Speter  /* ### This reparent may be problematic in limited-authz-to-common-parent
1124251881Speter     ### scenarios (compare issue #3242).  See also issue #3649. */
1125251881Speter  if (ra_session)
1126251881Speter    SVN_ERR(svn_ra_reparent(ra_session, common, pool));
1127251881Speter
1128251881Speter  /* Create new commit items and add them to the array. */
1129251881Speter  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1130251881Speter    {
1131251881Speter      svn_client_commit_item3_t *item;
1132251881Speter      const char *tmp_file;
1133251881Speter      apr_array_header_t *commit_items
1134251881Speter        = apr_array_make(pool, targets->nelts, sizeof(item));
1135251881Speter
1136251881Speter      for (i = 0; i < targets->nelts; i++)
1137251881Speter        {
1138251881Speter          const char *path = APR_ARRAY_IDX(targets, i, const char *);
1139251881Speter
1140251881Speter          item = svn_client_commit_item3_create(pool);
1141251881Speter          item->url = svn_path_url_add_component2(common, path, pool);
1142251881Speter          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1143251881Speter          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1144251881Speter        }
1145251881Speter
1146251881Speter      SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
1147251881Speter                                      ctx, pool));
1148251881Speter
1149251881Speter      if (! log_msg)
1150251881Speter        return SVN_NO_ERROR;
1151251881Speter    }
1152251881Speter  else
1153251881Speter    log_msg = "";
1154251881Speter
1155251881Speter  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1156251881Speter                                           log_msg, ctx, pool));
1157251881Speter
1158251881Speter  /* Open an RA session for the URL. Note that we don't have a local
1159251881Speter     directory, nor a place to put temp files. */
1160251881Speter  if (!ra_session)
1161251881Speter    SVN_ERR(svn_client_open_ra_session2(&ra_session, common, NULL, ctx,
1162251881Speter                                        pool, pool));
1163251881Speter  else
1164251881Speter    SVN_ERR(svn_ra_reparent(ra_session, common, pool));
1165251881Speter
1166251881Speter
1167251881Speter  /* Fetch RA commit editor */
1168251881Speter  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1169251881Speter                        svn_client__get_shim_callbacks(ctx->wc_ctx, NULL,
1170251881Speter                                                       pool)));
1171251881Speter  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1172251881Speter                                    commit_revprops,
1173251881Speter                                    commit_callback,
1174251881Speter                                    commit_baton,
1175251881Speter                                    NULL, TRUE, /* No lock tokens */
1176251881Speter                                    pool));
1177251881Speter
1178251881Speter  /* Call the path-based editor driver. */
1179299742Sdim  err = svn_error_trace(
1180299742Sdim        svn_delta_path_driver2(editor, edit_baton, targets, TRUE,
1181299742Sdim                               path_driver_cb_func, (void *)editor, pool));
1182251881Speter
1183251881Speter  if (err)
1184251881Speter    {
1185251881Speter      /* At least try to abort the edit (and fs txn) before throwing err. */
1186251881Speter      return svn_error_compose_create(
1187251881Speter                err,
1188299742Sdim                svn_error_trace(editor->abort_edit(edit_baton, pool)));
1189251881Speter    }
1190251881Speter
1191299742Sdim  if (ctx->notify_func2)
1192299742Sdim    {
1193299742Sdim      svn_wc_notify_t *notify;
1194299742Sdim      notify = svn_wc_create_notify_url(common,
1195299742Sdim                                        svn_wc_notify_commit_finalizing,
1196299742Sdim                                        pool);
1197299742Sdim      ctx->notify_func2(ctx->notify_baton2, notify, pool);
1198299742Sdim    }
1199299742Sdim
1200251881Speter  /* Close the edit. */
1201299742Sdim  return svn_error_trace(editor->close_edit(edit_baton, pool));
1202251881Speter}
1203251881Speter
1204251881Speter
1205251881Speter
1206251881Spetersvn_error_t *
1207299742Sdimsvn_client__make_local_parents(const char *local_abspath,
1208251881Speter                               svn_boolean_t make_parents,
1209251881Speter                               svn_client_ctx_t *ctx,
1210299742Sdim                               apr_pool_t *scratch_pool)
1211251881Speter{
1212251881Speter  svn_error_t *err;
1213251881Speter  svn_node_kind_t orig_kind;
1214299742Sdim  SVN_ERR(svn_io_check_path(local_abspath, &orig_kind, scratch_pool));
1215251881Speter  if (make_parents)
1216299742Sdim    SVN_ERR(svn_io_make_dir_recursively(local_abspath, scratch_pool));
1217251881Speter  else
1218299742Sdim    SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
1219251881Speter
1220299742Sdim  err = svn_client_add5(local_abspath, svn_depth_empty, FALSE, FALSE, FALSE,
1221299742Sdim                        make_parents, ctx, scratch_pool);
1222251881Speter
1223251881Speter  /* If we created a new directory, but couldn't add it to version
1224251881Speter     control, then delete it. */
1225251881Speter  if (err && (orig_kind == svn_node_none))
1226251881Speter    {
1227299742Sdim      err = svn_error_compose_create(err,
1228299742Sdim                                     svn_io_remove_dir2(local_abspath, FALSE,
1229299742Sdim                                                        NULL, NULL,
1230299742Sdim                                                        scratch_pool));
1231251881Speter    }
1232251881Speter
1233251881Speter  return svn_error_trace(err);
1234251881Speter}
1235251881Speter
1236251881Speter
1237251881Spetersvn_error_t *
1238251881Spetersvn_client_mkdir4(const apr_array_header_t *paths,
1239251881Speter                  svn_boolean_t make_parents,
1240251881Speter                  const apr_hash_t *revprop_table,
1241251881Speter                  svn_commit_callback2_t commit_callback,
1242251881Speter                  void *commit_baton,
1243251881Speter                  svn_client_ctx_t *ctx,
1244251881Speter                  apr_pool_t *pool)
1245251881Speter{
1246251881Speter  if (! paths->nelts)
1247251881Speter    return SVN_NO_ERROR;
1248251881Speter
1249251881Speter  SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
1250251881Speter
1251251881Speter  if (svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *)))
1252251881Speter    {
1253251881Speter      SVN_ERR(mkdir_urls(paths, make_parents, revprop_table, commit_callback,
1254251881Speter                         commit_baton, ctx, pool));
1255251881Speter    }
1256251881Speter  else
1257251881Speter    {
1258251881Speter      /* This is a regular "mkdir" + "svn add" */
1259299742Sdim      apr_pool_t *iterpool = svn_pool_create(pool);
1260251881Speter      int i;
1261251881Speter
1262251881Speter      for (i = 0; i < paths->nelts; i++)
1263251881Speter        {
1264251881Speter          const char *path = APR_ARRAY_IDX(paths, i, const char *);
1265251881Speter
1266299742Sdim          svn_pool_clear(iterpool);
1267251881Speter
1268251881Speter          /* See if the user wants us to stop. */
1269251881Speter          if (ctx->cancel_func)
1270251881Speter            SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1271251881Speter
1272299742Sdim          SVN_ERR(svn_dirent_get_absolute(&path, path, iterpool));
1273299742Sdim
1274251881Speter          SVN_ERR(svn_client__make_local_parents(path, make_parents, ctx,
1275299742Sdim                                                 iterpool));
1276251881Speter        }
1277299742Sdim      svn_pool_destroy(iterpool);
1278251881Speter    }
1279251881Speter
1280251881Speter  return SVN_NO_ERROR;
1281251881Speter}
1282