externals.c revision 362181
1/*
2 * externals.c :  routines dealing with (file) externals in the working copy
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#include <stdlib.h>
27#include <string.h>
28
29#include <apr_pools.h>
30#include <apr_hash.h>
31#include <apr_tables.h>
32#include <apr_general.h>
33#include <apr_uri.h>
34
35#include "svn_dirent_uri.h"
36#include "svn_path.h"
37#include "svn_error.h"
38#include "svn_hash.h"
39#include "svn_io.h"
40#include "svn_pools.h"
41#include "svn_props.h"
42#include "svn_string.h"
43#include "svn_time.h"
44#include "svn_types.h"
45#include "svn_wc.h"
46
47#include "private/svn_skel.h"
48#include "private/svn_subr_private.h"
49
50#include "wc.h"
51#include "adm_files.h"
52#include "props.h"
53#include "translate.h"
54#include "workqueue.h"
55#include "conflicts.h"
56
57#include "svn_private_config.h"
58
59/** Externals **/
60
61/*
62 * Look for either
63 *
64 *   -r N
65 *   -rN
66 *
67 * in the LINE_PARTS array and update the revision field in ITEM with
68 * the revision if the revision is found.  Set REV_IDX to the index in
69 * LINE_PARTS where the revision specification starts.  Remove from
70 * LINE_PARTS the element(s) that specify the revision.
71 * Set REV_STR to the element that specifies the revision.
72 * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
73 * string.
74 *
75 * If this function returns successfully, then LINE_PARTS will have
76 * only two elements in it.
77 */
78static svn_error_t *
79find_and_remove_externals_revision(int *rev_idx,
80                                   const char **rev_str,
81                                   const char **line_parts,
82                                   int num_line_parts,
83                                   svn_wc_external_item2_t *item,
84                                   const char *parent_directory_display,
85                                   const char *line,
86                                   apr_pool_t *pool)
87{
88  int i;
89
90  for (i = 0; i < 2; ++i)
91    {
92      const char *token = line_parts[i];
93
94      if (token[0] == '-' && token[1] == 'r')
95        {
96          svn_opt_revision_t end_revision = { svn_opt_revision_unspecified };
97          const char *digits_ptr;
98          int shift_count;
99          int j;
100
101          *rev_idx = i;
102
103          if (token[2] == '\0')
104            {
105              /* There must be a total of four elements in the line if
106                 -r N is used. */
107              if (num_line_parts != 4)
108                goto parse_error;
109
110              shift_count = 2;
111              digits_ptr = line_parts[i+1];
112            }
113          else
114            {
115              /* There must be a total of three elements in the line
116                 if -rN is used. */
117              if (num_line_parts != 3)
118                goto parse_error;
119
120              shift_count = 1;
121              digits_ptr = token+2;
122            }
123
124          if (svn_opt_parse_revision(&item->revision,
125                                     &end_revision,
126                                     digits_ptr, pool) != 0)
127            goto parse_error;
128          /* We want a single revision, not a range. */
129          if (end_revision.kind != svn_opt_revision_unspecified)
130            goto parse_error;
131          /* Allow only numbers and dates, not keywords. */
132          if (item->revision.kind != svn_opt_revision_number
133              && item->revision.kind != svn_opt_revision_date)
134            goto parse_error;
135
136          /* Shift any line elements past the revision specification
137             down over the revision specification. */
138          for (j = i; j < num_line_parts-shift_count; ++j)
139            line_parts[j] = line_parts[j+shift_count];
140          line_parts[num_line_parts-shift_count] = NULL;
141
142          *rev_str = apr_psprintf(pool, "-r%s", digits_ptr);
143
144          /* Found the revision, so leave the function immediately, do
145           * not continue looking for additional revisions. */
146          return SVN_NO_ERROR;
147        }
148    }
149
150  /* No revision was found, so there must be exactly two items in the
151     line array. */
152  if (num_line_parts == 2)
153    return SVN_NO_ERROR;
154
155 parse_error:
156  return svn_error_createf
157    (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
158     _("Error parsing %s property on '%s': '%s'"),
159     SVN_PROP_EXTERNALS,
160     parent_directory_display,
161     line);
162}
163
164svn_error_t *
165svn_wc__parse_externals_description(apr_array_header_t **externals_p,
166                                    apr_array_header_t **parser_infos_p,
167                                    const char *defining_directory,
168                                    const char *desc,
169                                    svn_boolean_t canonicalize_url,
170                                    apr_pool_t *pool)
171{
172  int i;
173  apr_array_header_t *externals = NULL;
174  apr_array_header_t *parser_infos = NULL;
175  apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
176  const char *defining_directory_display = svn_path_is_url(defining_directory) ?
177    defining_directory : svn_dirent_local_style(defining_directory, pool);
178
179  /* If an error occurs halfway through parsing, *externals_p should stay
180   * untouched. So, store the list in a local var first. */
181  if (externals_p)
182    externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
183
184  if (parser_infos_p)
185    parser_infos =
186      apr_array_make(pool, 1, sizeof(svn_wc__externals_parser_info_t *));
187
188  for (i = 0; i < lines->nelts; i++)
189    {
190      const char *line = APR_ARRAY_IDX(lines, i, const char *);
191      apr_status_t status;
192      char **line_parts;
193      int num_line_parts;
194      svn_wc_external_item2_t *item;
195      const char *token0;
196      const char *token1;
197      svn_boolean_t token0_is_url;
198      svn_boolean_t token1_is_url;
199      svn_wc__externals_parser_info_t *info = NULL;
200
201      /* Index into line_parts where the revision specification
202         started. */
203      int rev_idx = -1;
204      const char *rev_str = NULL;
205
206      if ((! line) || (line[0] == '#'))
207        continue;
208
209      /* else proceed */
210
211      status = apr_tokenize_to_argv(line, &line_parts, pool);
212      if (status)
213        return svn_error_wrap_apr(status,
214                                  _("Can't split line into components: '%s'"),
215                                  line);
216      /* Count the number of tokens. */
217      for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
218        ;
219
220      SVN_ERR(svn_wc_external_item2_create(&item, pool));
221      item->revision.kind = svn_opt_revision_unspecified;
222      item->peg_revision.kind = svn_opt_revision_unspecified;
223
224      if (parser_infos)
225        info = apr_pcalloc(pool, sizeof(*info));
226
227      /*
228       * There are six different formats of externals:
229       *
230       * 1) DIR URL
231       * 2) DIR -r N URL
232       * 3) DIR -rN  URL
233       * 4) URL DIR
234       * 5) -r N URL DIR
235       * 6) -rN URL DIR
236       *
237       * The last three allow peg revisions in the URL.
238       *
239       * With relative URLs and no '-rN' or '-r N', there is no way to
240       * distinguish between 'DIR URL' and 'URL DIR' when URL is a
241       * relative URL like /svn/repos/trunk, so this case is taken as
242       * case 4).
243       */
244      if (num_line_parts < 2 || num_line_parts > 4)
245        return svn_error_createf
246          (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
247           _("Error parsing %s property on '%s': '%s'"),
248           SVN_PROP_EXTERNALS,
249           defining_directory_display,
250           line);
251
252      /* To make it easy to check for the forms, find and remove -r N
253         or -rN from the line item array.  If it is found, rev_idx
254         contains the index into line_parts where '-r' was found and
255         set item->revision to the parsed revision. */
256      /* ### ugh. stupid cast. */
257      SVN_ERR(find_and_remove_externals_revision(&rev_idx,
258                                                 &rev_str,
259                                                 (const char **)line_parts,
260                                                 num_line_parts, item,
261                                                 defining_directory_display,
262                                                 line, pool));
263
264      token0 = line_parts[0];
265      token1 = line_parts[1];
266
267      token0_is_url = svn_path_is_url(token0);
268      token1_is_url = svn_path_is_url(token1);
269
270      if (token0_is_url && token1_is_url)
271        return svn_error_createf
272          (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
273           _("Invalid %s property on '%s': "
274             "cannot use two absolute URLs ('%s' and '%s') in an external; "
275             "one must be a path where an absolute or relative URL is "
276             "checked out to"),
277           SVN_PROP_EXTERNALS, defining_directory_display, token0, token1);
278
279      if (0 == rev_idx && token1_is_url)
280        return svn_error_createf
281          (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
282           _("Invalid %s property on '%s': "
283             "cannot use a URL '%s' as the target directory for an external "
284             "definition"),
285           SVN_PROP_EXTERNALS, defining_directory_display, token1);
286
287      if (1 == rev_idx && token0_is_url)
288        return svn_error_createf
289          (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
290           _("Invalid %s property on '%s': "
291             "cannot use a URL '%s' as the target directory for an external "
292             "definition"),
293           SVN_PROP_EXTERNALS, defining_directory_display, token0);
294
295      /* The appearance of -r N or -rN forces the type of external.
296         If -r is at the beginning of the line or the first token is
297         an absolute URL or if the second token is not an absolute
298         URL, then the URL supports peg revisions. */
299      if (0 == rev_idx ||
300          (-1 == rev_idx && (token0_is_url || ! token1_is_url)))
301        {
302          /* The URL is passed to svn_opt_parse_path in
303             uncanonicalized form so that the scheme relative URL
304             //hostname/foo is not collapsed to a server root relative
305             URL /hostname/foo. */
306          SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
307                                     token0, pool));
308          item->target_dir = token1;
309
310          if (info)
311            {
312              info->format = svn_wc__external_description_format_2;
313
314              if (rev_str)
315                info->rev_str = apr_pstrdup(pool, rev_str);
316
317              if (item->peg_revision.kind != svn_opt_revision_unspecified)
318                info->peg_rev_str = strrchr(token0, '@');
319            }
320        }
321      else
322        {
323          item->target_dir = token0;
324          item->url = token1;
325          item->peg_revision = item->revision;
326
327          if (info)
328            {
329              info->format = svn_wc__external_description_format_1;
330
331              if (rev_str)
332                {
333                  info->rev_str = apr_pstrdup(pool, rev_str);
334                  info->peg_rev_str = info->rev_str;
335                }
336            }
337        }
338
339      SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
340                                        &item->revision, TRUE, FALSE,
341                                        pool));
342
343      item->target_dir = svn_dirent_internal_style(item->target_dir, pool);
344
345      if (item->target_dir[0] == '\0'
346          || svn_dirent_is_absolute(item->target_dir)
347          || svn_path_is_backpath_present(item->target_dir)
348          || !svn_dirent_skip_ancestor("dummy",
349                                       svn_dirent_join("dummy",
350                                                       item->target_dir,
351                                                       pool)))
352        return svn_error_createf
353          (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
354           _("Invalid %s property on '%s': "
355             "target '%s' is an absolute path or involves '..'"),
356           SVN_PROP_EXTERNALS,
357           defining_directory_display,
358           item->target_dir);
359
360      if (canonicalize_url)
361        {
362          /* Uh... this is stupid.  But it's consistent with what our
363             code did before we split up the relpath/dirent/uri APIs.
364             Still, given this, it's no wonder that our own libraries
365             don't ask this function to canonicalize the results.  */
366          if (svn_path_is_url(item->url))
367            item->url = svn_uri_canonicalize(item->url, pool);
368          else
369            item->url = svn_dirent_canonicalize(item->url, pool);
370        }
371
372      if (externals)
373        APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item;
374      if (parser_infos)
375        APR_ARRAY_PUSH(parser_infos, svn_wc__externals_parser_info_t *) = info;
376    }
377
378  if (externals_p)
379    *externals_p = externals;
380  if (parser_infos_p)
381    *parser_infos_p = parser_infos;
382
383  return SVN_NO_ERROR;
384}
385
386svn_error_t *
387svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
388                                    const char *defining_directory,
389                                    const char *desc,
390                                    svn_boolean_t canonicalize_url,
391                                    apr_pool_t *pool)
392{
393  return svn_error_trace(svn_wc__parse_externals_description(externals_p,
394                                                             NULL,
395                                                             defining_directory,
396                                                             desc,
397                                                             canonicalize_url,
398                                                             pool));
399}
400
401svn_error_t *
402svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
403                                   apr_array_header_t *externals,
404                                   apr_pool_t *pool,
405                                   apr_pool_t *scratch_pool)
406{
407  int i;
408  unsigned int len;
409  unsigned int len2;
410  const char *target;
411  apr_hash_t *targets = apr_hash_make(scratch_pool);
412  apr_hash_t *targets2 = NULL;
413  *duplicate_targets = NULL;
414
415  for (i = 0; i < externals->nelts; i++)
416    {
417      target = APR_ARRAY_IDX(externals, i,
418                                         svn_wc_external_item2_t*)->target_dir;
419      len = apr_hash_count(targets);
420      svn_hash_sets(targets, target, "");
421      if (len == apr_hash_count(targets))
422        {
423          /* Hashtable length is unchanged. This must be a duplicate. */
424
425          /* Collapse multiple duplicates of the same target by using a second
426           * hash layer. */
427          if (! targets2)
428            targets2 = apr_hash_make(scratch_pool);
429          len2 = apr_hash_count(targets2);
430          svn_hash_sets(targets2, target, "");
431          if (len2 < apr_hash_count(targets2))
432            {
433              /* The second hash list just got bigger, i.e. this target has
434               * not been counted as duplicate before. */
435              if (! *duplicate_targets)
436                {
437                  *duplicate_targets = apr_array_make(
438                                    pool, 1, sizeof(svn_wc_external_item2_t*));
439                }
440              APR_ARRAY_PUSH((*duplicate_targets), const char *) = target;
441            }
442          /* Else, this same target has already been recorded as a duplicate,
443           * don't count it again. */
444        }
445    }
446  return SVN_NO_ERROR;
447}
448
449struct edit_baton
450{
451  apr_pool_t *pool;
452  svn_wc__db_t *db;
453
454  /* We explicitly use wri_abspath and local_abspath here, because we
455     might want to install file externals in an obstructing working copy */
456  const char *wri_abspath;     /* The working defining the file external */
457  const char *local_abspath;   /* The file external itself */
458  const char *name;            /* The basename of the file external itself */
459
460  /* Information from the caller */
461  svn_boolean_t use_commit_times;
462  const apr_array_header_t *ext_patterns;
463  const char *diff3cmd;
464
465  const char *repos_root_url;
466  const char *repos_uuid;
467  const char *old_repos_relpath;
468  const char *new_repos_relpath;
469
470  const char *record_ancestor_abspath;
471  const char *recorded_repos_relpath;
472  svn_revnum_t recorded_peg_revision;
473  svn_revnum_t recorded_revision;
474
475  /* Introducing a new file external */
476  svn_boolean_t added;
477
478  svn_wc_conflict_resolver_func2_t conflict_func;
479  void *conflict_baton;
480  svn_cancel_func_t cancel_func;
481  void *cancel_baton;
482  svn_wc_notify_func2_t notify_func;
483  void *notify_baton;
484
485  svn_revnum_t *target_revision;
486
487  /* What was there before the update */
488  svn_revnum_t original_revision;
489  const svn_checksum_t *original_checksum;
490
491  /* What we are installing now */
492  svn_wc__db_install_data_t *install_data;
493  svn_checksum_t *new_sha1_checksum;
494  svn_checksum_t *new_md5_checksum;
495
496  /* List of incoming propchanges */
497  apr_array_header_t *propchanges;
498
499  /* Array of svn_prop_inherited_item_t * structures representing the
500     properties inherited by the base node at LOCAL_ABSPATH. */
501  apr_array_header_t *iprops;
502
503  /* The last change information */
504  svn_revnum_t changed_rev;
505  apr_time_t changed_date;
506  const char *changed_author;
507
508  svn_boolean_t had_props;
509
510  svn_boolean_t file_closed;
511};
512
513/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
514static svn_error_t *
515set_target_revision(void *edit_baton,
516                     svn_revnum_t target_revision,
517                     apr_pool_t *pool)
518{
519  struct edit_baton *eb = edit_baton;
520
521  *eb->target_revision = target_revision;
522
523  return SVN_NO_ERROR;
524}
525
526/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
527static svn_error_t *
528open_root(void *edit_baton,
529          svn_revnum_t base_revision,
530          apr_pool_t *dir_pool,
531          void **root_baton)
532{
533  *root_baton = edit_baton;
534  return SVN_NO_ERROR;
535}
536
537/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
538static svn_error_t *
539add_file(const char *path,
540         void *parent_baton,
541         const char *copyfrom_path,
542         svn_revnum_t copyfrom_revision,
543         apr_pool_t *file_pool,
544         void **file_baton)
545{
546  struct edit_baton *eb = parent_baton;
547  if (strcmp(path, eb->name))
548      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
549                               _("This editor can only update '%s'"),
550                               svn_dirent_local_style(eb->local_abspath,
551                                                      file_pool));
552
553  *file_baton = eb;
554  eb->original_revision = SVN_INVALID_REVNUM;
555  eb->added = TRUE;
556
557  return SVN_NO_ERROR;
558}
559
560/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
561static svn_error_t *
562open_file(const char *path,
563          void *parent_baton,
564          svn_revnum_t base_revision,
565          apr_pool_t *file_pool,
566          void **file_baton)
567{
568  struct edit_baton *eb = parent_baton;
569  svn_node_kind_t kind;
570  if (strcmp(path, eb->name))
571      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
572                               _("This editor can only update '%s'"),
573                               svn_dirent_local_style(eb->local_abspath,
574                                                      file_pool));
575
576  *file_baton = eb;
577  SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
578                                   &eb->old_repos_relpath, NULL, NULL,
579                                   &eb->changed_rev,
580                                   &eb->changed_date, &eb->changed_author,
581                                   NULL, &eb->original_checksum, NULL, NULL,
582                                   &eb->had_props, NULL, NULL,
583                                   eb->db, eb->local_abspath,
584                                   eb->pool, file_pool));
585
586  if (kind != svn_node_file)
587    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
588                               _("Node '%s' is no existing file external"),
589                               svn_dirent_local_style(eb->local_abspath,
590                                                      file_pool));
591  return SVN_NO_ERROR;
592}
593
594/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
595static svn_error_t *
596apply_textdelta(void *file_baton,
597                const char *base_checksum_digest,
598                apr_pool_t *pool,
599                svn_txdelta_window_handler_t *handler,
600                void **handler_baton)
601{
602  struct edit_baton *eb = file_baton;
603  svn_stream_t *src_stream;
604  svn_stream_t *dest_stream;
605
606  if (eb->original_checksum)
607    {
608      if (base_checksum_digest)
609        {
610          svn_checksum_t *expected_checksum;
611          const svn_checksum_t *original_md5;
612
613          SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
614                                         base_checksum_digest, pool));
615
616          if (eb->original_checksum->kind != svn_checksum_md5)
617            SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
618                                                eb->db, eb->wri_abspath,
619                                                eb->original_checksum,
620                                                pool, pool));
621          else
622            original_md5 = eb->original_checksum;
623
624          if (!svn_checksum_match(expected_checksum, original_md5))
625            return svn_error_trace(svn_checksum_mismatch_err(
626                                    expected_checksum,
627                                    original_md5,
628                                    pool,
629                                    _("Base checksum mismatch for '%s'"),
630                                    svn_dirent_local_style(eb->local_abspath,
631                                                           pool)));
632        }
633
634      SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
635                                       eb->wri_abspath, eb->original_checksum,
636                                       pool, pool));
637    }
638  else
639    src_stream = svn_stream_empty(pool);
640
641  SVN_ERR(svn_wc__db_pristine_prepare_install(&dest_stream,
642                                              &eb->install_data,
643                                              &eb->new_sha1_checksum,
644                                              &eb->new_md5_checksum,
645                                              eb->db, eb->wri_abspath,
646                                              eb->pool, pool));
647
648  svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
649                    handler, handler_baton);
650
651  return SVN_NO_ERROR;
652}
653
654/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
655static svn_error_t *
656change_file_prop(void *file_baton,
657                 const char *name,
658                 const svn_string_t *value,
659                 apr_pool_t *pool)
660{
661  struct edit_baton *eb = file_baton;
662  svn_prop_t *propchange;
663
664  propchange = apr_array_push(eb->propchanges);
665  propchange->name = apr_pstrdup(eb->pool, name);
666  propchange->value = svn_string_dup(value, eb->pool);
667
668  return SVN_NO_ERROR;
669}
670
671/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
672static svn_error_t *
673close_file(void *file_baton,
674           const char *expected_md5_digest,
675           apr_pool_t *pool)
676{
677  struct edit_baton *eb = file_baton;
678  svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
679  svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
680  svn_boolean_t obstructed = FALSE;
681
682  eb->file_closed = TRUE; /* We bump the revision here */
683
684  /* Check the checksum, if provided */
685  if (expected_md5_digest)
686    {
687      svn_checksum_t *expected_md5_checksum;
688      const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
689
690      SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
691                                     expected_md5_digest, pool));
692
693      if (actual_md5_checksum == NULL)
694        {
695          actual_md5_checksum = eb->original_checksum;
696
697          if (actual_md5_checksum != NULL
698              && actual_md5_checksum->kind != svn_checksum_md5)
699            {
700              SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
701                                                  eb->db, eb->wri_abspath,
702                                                  actual_md5_checksum,
703                                                  pool, pool));
704            }
705        }
706
707      if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
708        return svn_checksum_mismatch_err(
709                        expected_md5_checksum,
710                        actual_md5_checksum, pool,
711                        _("Checksum mismatch for '%s'"),
712                        svn_dirent_local_style(eb->local_abspath, pool));
713    }
714
715  /* First move the file in the pristine store; this hands over the cleanup
716     behavior to the pristine store. */
717  if (eb->new_sha1_checksum)
718    {
719      SVN_ERR(svn_wc__db_pristine_install(eb->install_data,
720                                          eb->new_sha1_checksum,
721                                          eb->new_md5_checksum, pool));
722
723      eb->install_data = NULL;
724    }
725
726  /* Merge the changes */
727  {
728    svn_skel_t *all_work_items = NULL;
729    svn_skel_t *conflict_skel = NULL;
730    svn_skel_t *work_item;
731    apr_hash_t *base_props = NULL;
732    apr_hash_t *actual_props = NULL;
733    apr_hash_t *new_pristine_props = NULL;
734    apr_hash_t *new_actual_props = NULL;
735    apr_hash_t *new_dav_props = NULL;
736    const svn_checksum_t *new_checksum = NULL;
737    const svn_checksum_t *original_checksum = NULL;
738
739    svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
740
741    if (! added)
742      {
743        new_checksum = eb->original_checksum;
744
745        if (eb->had_props)
746          SVN_ERR(svn_wc__db_base_get_props(
747                    &base_props, eb->db, eb->local_abspath, pool, pool));
748
749        SVN_ERR(svn_wc__db_read_props(
750                  &actual_props, eb->db, eb->local_abspath, pool, pool));
751      }
752
753    if (!base_props)
754      base_props = apr_hash_make(pool);
755
756    if (!actual_props)
757      actual_props = apr_hash_make(pool);
758
759    if (eb->new_sha1_checksum)
760      new_checksum = eb->new_sha1_checksum;
761
762    /* Merge the properties */
763    {
764      apr_array_header_t *entry_prop_changes;
765      apr_array_header_t *dav_prop_changes;
766      apr_array_header_t *regular_prop_changes;
767      int i;
768
769      SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
770                                   &dav_prop_changes, &regular_prop_changes,
771                                   pool));
772
773      /* Read the entry-prop changes to update the last-changed info. */
774      for (i = 0; i < entry_prop_changes->nelts; i++)
775        {
776          const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
777                                                  svn_prop_t);
778
779          if (! prop->value)
780            continue; /* authz or something */
781
782          if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
783            eb->changed_author = apr_pstrdup(pool, prop->value->data);
784          else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
785            {
786              apr_int64_t rev;
787              SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
788              eb->changed_rev = (svn_revnum_t)rev;
789            }
790          else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
791            SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
792                                          pool));
793        }
794
795      /* Store the DAV-prop (aka WC-prop) changes.  (This treats a list
796       * of changes as a list of new props, but we only use this when
797       * adding a new file and it's equivalent in that case.) */
798      if (dav_prop_changes->nelts > 0)
799        new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
800
801      /* Merge the regular prop changes. */
802      if (regular_prop_changes->nelts > 0)
803        {
804          new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
805                                               pool);
806          SVN_ERR(svn_wc__merge_props(&conflict_skel,
807                                      &prop_state,
808                                      &new_actual_props,
809                                      eb->db, eb->local_abspath,
810                                      NULL /* server_baseprops*/,
811                                      base_props,
812                                      actual_props,
813                                      regular_prop_changes,
814                                      pool, pool));
815        }
816      else
817        {
818          new_pristine_props = base_props;
819          new_actual_props = actual_props;
820        }
821    }
822
823    /* Merge the text */
824    if (eb->new_sha1_checksum)
825      {
826        svn_node_kind_t disk_kind;
827        svn_boolean_t install_pristine = FALSE;
828
829        SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
830
831        if (disk_kind == svn_node_none)
832          {
833            /* Just install the file */
834            install_pristine = TRUE;
835            content_state = svn_wc_notify_state_changed;
836          }
837        else if (disk_kind != svn_node_file
838                 || (eb->added && disk_kind == svn_node_file))
839          {
840            /* The node is obstructed; we just change the DB */
841            obstructed = TRUE;
842            content_state = svn_wc_notify_state_unchanged;
843          }
844        else
845          {
846            svn_boolean_t is_mod;
847            SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
848                                                     eb->db, eb->local_abspath,
849                                                     FALSE, pool));
850
851            if (!is_mod)
852              {
853                install_pristine = TRUE;
854                content_state = svn_wc_notify_state_changed;
855              }
856            else
857              {
858                svn_boolean_t found_text_conflict;
859
860                /* Ok, we have to do some work to merge a local change */
861                SVN_ERR(svn_wc__perform_file_merge(&work_item,
862                                                   &conflict_skel,
863                                                   &found_text_conflict,
864                                                   eb->db,
865                                                   eb->local_abspath,
866                                                   eb->wri_abspath,
867                                                   new_checksum,
868                                                   original_checksum,
869                                                   actual_props,
870                                                   eb->ext_patterns,
871                                                   eb->original_revision,
872                                                   *eb->target_revision,
873                                                   eb->propchanges,
874                                                   eb->diff3cmd,
875                                                   eb->cancel_func,
876                                                   eb->cancel_baton,
877                                                   pool, pool));
878
879                all_work_items = svn_wc__wq_merge(all_work_items, work_item,
880                                                  pool);
881
882                if (found_text_conflict)
883                  content_state = svn_wc_notify_state_conflicted;
884                else
885                  content_state = svn_wc_notify_state_merged;
886              }
887          }
888        if (install_pristine)
889          {
890            SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
891                                            eb->local_abspath,
892                                            NULL,
893                                            eb->use_commit_times, TRUE,
894                                            pool, pool));
895
896            all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
897          }
898      }
899    else
900      {
901        content_state = svn_wc_notify_state_unchanged;
902        /* ### Retranslate on magic property changes, etc. */
903      }
904
905    /* Generate a conflict description, if needed */
906    if (conflict_skel)
907      {
908        SVN_ERR(svn_wc__conflict_skel_set_op_switch(
909                            conflict_skel,
910                            svn_wc_conflict_version_create2(
911                                    eb->repos_root_url,
912                                    eb->repos_uuid,
913                                    eb->old_repos_relpath,
914                                    eb->original_revision,
915                                    svn_node_file,
916                                    pool),
917                            svn_wc_conflict_version_create2(
918                                    eb->repos_root_url,
919                                    eb->repos_uuid,
920                                    eb->new_repos_relpath,
921                                    *eb->target_revision,
922                                    svn_node_file,
923                                    pool),
924                            pool, pool));
925        SVN_ERR(svn_wc__conflict_create_markers(&work_item,
926                                                eb->db, eb->local_abspath,
927                                                conflict_skel,
928                                                pool, pool));
929        all_work_items = svn_wc__wq_merge(all_work_items, work_item,
930                                          pool);
931      }
932
933    /* Install the file in the DB */
934    SVN_ERR(svn_wc__db_external_add_file(
935                        eb->db,
936                        eb->local_abspath,
937                        eb->wri_abspath,
938                        eb->new_repos_relpath,
939                        eb->repos_root_url,
940                        eb->repos_uuid,
941                        *eb->target_revision,
942                        new_pristine_props,
943                        eb->iprops,
944                        eb->changed_rev,
945                        eb->changed_date,
946                        eb->changed_author,
947                        new_checksum,
948                        new_dav_props,
949                        eb->record_ancestor_abspath,
950                        eb->recorded_repos_relpath,
951                        eb->recorded_peg_revision,
952                        eb->recorded_revision,
953                        TRUE, new_actual_props,
954                        FALSE /* keep_recorded_info */,
955                        conflict_skel,
956                        all_work_items,
957                        pool));
958
959    /* close_edit may also update iprops for switched files, catching
960       those for which close_file is never called (e.g. an update of a
961       file external with no changes).  So as a minor optimization we
962       clear the iprops so as not to set them again in close_edit. */
963    eb->iprops = NULL;
964
965    /* Run the work queue to complete the installation */
966    SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
967                           eb->cancel_func, eb->cancel_baton, pool));
968
969    if (conflict_skel && eb->conflict_func)
970      SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db,
971                                               eb->local_abspath,
972                                               svn_node_file,
973                                               conflict_skel,
974                                               NULL /* merge_options */,
975                                               eb->conflict_func,
976                                               eb->conflict_baton,
977                                               eb->cancel_func,
978                                               eb->cancel_baton,
979                                               pool));
980  }
981
982  /* Notify */
983  if (eb->notify_func)
984    {
985      svn_wc_notify_action_t action;
986      svn_wc_notify_t *notify;
987
988      if (!eb->added)
989        action = obstructed ? svn_wc_notify_update_shadowed_update
990                            : svn_wc_notify_update_update;
991      else
992        action = obstructed ? svn_wc_notify_update_shadowed_add
993                            : svn_wc_notify_update_add;
994
995      notify = svn_wc_create_notify(eb->local_abspath, action, pool);
996      notify->kind = svn_node_file;
997
998      notify->revision = *eb->target_revision;
999      notify->prop_state = prop_state;
1000      notify->content_state = content_state;
1001
1002      notify->old_revision = eb->original_revision;
1003
1004      eb->notify_func(eb->notify_baton, notify, pool);
1005    }
1006
1007  return SVN_NO_ERROR;
1008}
1009
1010/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
1011static svn_error_t *
1012close_edit(void *edit_baton,
1013           apr_pool_t *pool)
1014{
1015  struct edit_baton *eb = edit_baton;
1016
1017  if (!eb->file_closed)
1018    {
1019      apr_hash_t *wcroot_iprops = NULL;
1020      /* The file wasn't updated, but its url or revision might have...
1021         e.g. switch between branches for relative externals.
1022
1023         Just bump the information as that is just as expensive as
1024         investigating when we should and shouldn't update it...
1025         and avoid hard to debug edge cases */
1026
1027      if (eb->iprops)
1028        {
1029          wcroot_iprops = apr_hash_make(pool);
1030          svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
1031        }
1032
1033      SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
1034                                                       eb->local_abspath,
1035                                                       svn_depth_infinity,
1036                                                       eb->new_repos_relpath,
1037                                                       eb->repos_root_url,
1038                                                       eb->repos_uuid,
1039                                                       *eb->target_revision,
1040                                                       apr_hash_make(pool)
1041                                                       /* exclude_relpaths */,
1042                                                       wcroot_iprops,
1043                                                       TRUE /* empty update */,
1044                                                       eb->notify_func,
1045                                                       eb->notify_baton,
1046                                                       pool));
1047    }
1048
1049  return SVN_NO_ERROR;
1050}
1051
1052svn_error_t *
1053svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
1054                                 void **edit_baton,
1055                                 svn_revnum_t *target_revision,
1056                                 svn_wc_context_t *wc_ctx,
1057                                 const char *local_abspath,
1058                                 const char *wri_abspath,
1059                                 const char *url,
1060                                 const char *repos_root_url,
1061                                 const char *repos_uuid,
1062                                 apr_array_header_t *iprops,
1063                                 svn_boolean_t use_commit_times,
1064                                 const char *diff3_cmd,
1065                                 const apr_array_header_t *preserved_exts,
1066                                 const char *record_ancestor_abspath,
1067                                 const char *recorded_url,
1068                                 const svn_opt_revision_t *recorded_peg_rev,
1069                                 const svn_opt_revision_t *recorded_rev,
1070                                 svn_wc_conflict_resolver_func2_t conflict_func,
1071                                 void *conflict_baton,
1072                                 svn_cancel_func_t cancel_func,
1073                                 void *cancel_baton,
1074                                 svn_wc_notify_func2_t notify_func,
1075                                 void *notify_baton,
1076                                 apr_pool_t *result_pool,
1077                                 apr_pool_t *scratch_pool)
1078{
1079  svn_wc__db_t *db = wc_ctx->db;
1080  apr_pool_t *edit_pool = result_pool;
1081  struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1082  svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1083
1084  eb->pool = edit_pool;
1085  eb->db = db;
1086  eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1087  if (wri_abspath)
1088    eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1089  else
1090    eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1091  eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1092  eb->target_revision = target_revision;
1093
1094  eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1095  eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1096  eb->new_repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, url, edit_pool);
1097  eb->old_repos_relpath = eb->new_repos_relpath;
1098
1099  eb->original_revision = SVN_INVALID_REVNUM;
1100
1101  eb->iprops = iprops;
1102
1103  eb->use_commit_times = use_commit_times;
1104  eb->ext_patterns = preserved_exts;
1105  eb->diff3cmd = diff3_cmd;
1106
1107  eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1108  eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1109                                                     edit_pool);
1110
1111  eb->changed_rev = SVN_INVALID_REVNUM;
1112
1113  if (recorded_peg_rev->kind == svn_opt_revision_number)
1114    eb->recorded_peg_revision = recorded_peg_rev->value.number;
1115  else
1116    eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1117
1118  if (recorded_rev->kind == svn_opt_revision_number)
1119    eb->recorded_revision = recorded_rev->value.number;
1120  else
1121    eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1122
1123  eb->conflict_func = conflict_func;
1124  eb->conflict_baton = conflict_baton;
1125  eb->cancel_func = cancel_func;
1126  eb->cancel_baton = cancel_baton;
1127  eb->notify_func = notify_func;
1128  eb->notify_baton = notify_baton;
1129
1130  eb->propchanges  = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1131
1132  tree_editor->open_root = open_root;
1133  tree_editor->set_target_revision = set_target_revision;
1134  tree_editor->add_file = add_file;
1135  tree_editor->open_file = open_file;
1136  tree_editor->apply_textdelta = apply_textdelta;
1137  tree_editor->change_file_prop = change_file_prop;
1138  tree_editor->close_file = close_file;
1139  tree_editor->close_edit = close_edit;
1140
1141  return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1142                                           tree_editor, eb,
1143                                           editor, edit_baton,
1144                                           result_pool);
1145}
1146
1147svn_error_t *
1148svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1149                            const char *local_abspath,
1150                            const svn_ra_reporter3_t *reporter,
1151                            void *report_baton,
1152                            svn_boolean_t restore_files,
1153                            svn_boolean_t use_commit_times,
1154                            svn_cancel_func_t cancel_func,
1155                            void *cancel_baton,
1156                            svn_wc_notify_func2_t notify_func,
1157                            void *notify_baton,
1158                            apr_pool_t *scratch_pool)
1159{
1160  svn_wc__db_t *db = wc_ctx->db;
1161  svn_error_t *err;
1162  svn_node_kind_t kind;
1163  svn_wc__db_lock_t *lock;
1164  svn_revnum_t revision;
1165  const char *repos_root_url;
1166  const char *repos_relpath;
1167  svn_boolean_t update_root;
1168
1169  err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1170                                 &repos_relpath, &repos_root_url, NULL, NULL,
1171                                 NULL, NULL, NULL, NULL, NULL, &lock,
1172                                 NULL, NULL, &update_root,
1173                                 db, local_abspath,
1174                                 scratch_pool, scratch_pool);
1175
1176  if (err
1177      || kind == svn_node_dir
1178      || !update_root)
1179    {
1180      if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1181        return svn_error_trace(err);
1182
1183      svn_error_clear(err);
1184
1185      /* We don't know about this node, so all we have to do is tell
1186         the reporter that we don't know this node.
1187
1188         But first we have to start the report by sending some basic
1189         information for the root. */
1190
1191      SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1192                                 FALSE, NULL, scratch_pool));
1193      SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1194
1195      /* Finish the report, which causes the update editor to be
1196         driven. */
1197      SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1198
1199      return SVN_NO_ERROR;
1200    }
1201  else
1202    {
1203      if (restore_files)
1204        {
1205          svn_node_kind_t disk_kind;
1206          SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1207
1208          if (disk_kind == svn_node_none)
1209            {
1210              err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1211                                   scratch_pool);
1212
1213              if (err)
1214                {
1215                  if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1216                    return svn_error_trace(err);
1217
1218                  svn_error_clear(err);
1219                }
1220            }
1221        }
1222
1223      /* Report that we know the path */
1224      SVN_ERR(reporter->set_path(report_baton, "", revision,
1225                                 svn_depth_infinity, FALSE, NULL,
1226                                 scratch_pool));
1227
1228      /* For compatibility with the normal update editor report we report
1229         the target as switched.
1230
1231         ### We can probably report a parent url and unswitched later */
1232      SVN_ERR(reporter->link_path(report_baton, "",
1233                                  svn_path_url_add_component2(repos_root_url,
1234                                                              repos_relpath,
1235                                                              scratch_pool),
1236                                  revision,
1237                                  svn_depth_infinity,
1238                                  FALSE /* start_empty*/,
1239                                  lock ? lock->token : NULL,
1240                                  scratch_pool));
1241    }
1242
1243  return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1244}
1245
1246svn_error_t *
1247svn_wc__read_external_info(svn_node_kind_t *external_kind,
1248                           const char **defining_abspath,
1249                           const char **defining_url,
1250                           svn_revnum_t *defining_operational_revision,
1251                           svn_revnum_t *defining_revision,
1252                           svn_wc_context_t *wc_ctx,
1253                           const char *wri_abspath,
1254                           const char *local_abspath,
1255                           svn_boolean_t ignore_enoent,
1256                           apr_pool_t *result_pool,
1257                           apr_pool_t *scratch_pool)
1258{
1259  const char *repos_root_url;
1260  svn_wc__db_status_t status;
1261  svn_node_kind_t kind;
1262  svn_error_t *err;
1263
1264  err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1265                                 defining_url ? &repos_root_url : NULL, NULL,
1266                                 defining_url, defining_operational_revision,
1267                                 defining_revision,
1268                                 wc_ctx->db, local_abspath, wri_abspath,
1269                                 result_pool, scratch_pool);
1270
1271  if (err)
1272    {
1273      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1274        return svn_error_trace(err);
1275
1276      svn_error_clear(err);
1277
1278      if (external_kind)
1279        *external_kind = svn_node_none;
1280
1281      if (defining_abspath)
1282        *defining_abspath = NULL;
1283
1284      if (defining_url)
1285        *defining_url = NULL;
1286
1287      if (defining_operational_revision)
1288        *defining_operational_revision = SVN_INVALID_REVNUM;
1289
1290      if (defining_revision)
1291        *defining_revision = SVN_INVALID_REVNUM;
1292
1293      return SVN_NO_ERROR;
1294    }
1295
1296  if (external_kind)
1297    {
1298      if (status != svn_wc__db_status_normal)
1299        *external_kind = svn_node_unknown;
1300      else
1301        switch(kind)
1302          {
1303            case svn_node_file:
1304            case svn_node_symlink:
1305              *external_kind = svn_node_file;
1306              break;
1307            case svn_node_dir:
1308              *external_kind = svn_node_dir;
1309              break;
1310            default:
1311              *external_kind = svn_node_none;
1312          }
1313    }
1314
1315  if (defining_url && *defining_url)
1316    *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1317                                                result_pool);
1318
1319  return SVN_NO_ERROR;
1320}
1321
1322/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1323 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1324 * XINFO->REPOS_RELPATH.  All allocations are made in SCRATCH_POOL. */
1325static svn_error_t *
1326is_external_rolled_out(svn_boolean_t *is_rolled_out,
1327                       svn_wc_context_t *wc_ctx,
1328                       svn_wc__committable_external_info_t *xinfo,
1329                       apr_pool_t *scratch_pool)
1330{
1331  const char *repos_relpath;
1332  const char *repos_root_url;
1333  svn_error_t *err;
1334
1335  *is_rolled_out = FALSE;
1336
1337  err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1338                                 &repos_root_url, NULL, NULL, NULL, NULL,
1339                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1340                                 wc_ctx->db, xinfo->local_abspath,
1341                                 scratch_pool, scratch_pool);
1342
1343  if (err)
1344    {
1345      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1346        {
1347          svn_error_clear(err);
1348          return SVN_NO_ERROR;
1349        }
1350      SVN_ERR(err);
1351    }
1352
1353  *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1354                    strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1355  return SVN_NO_ERROR;
1356}
1357
1358svn_error_t *
1359svn_wc__committable_externals_below(apr_array_header_t **externals,
1360                                    svn_wc_context_t *wc_ctx,
1361                                    const char *local_abspath,
1362                                    svn_depth_t depth,
1363                                    apr_pool_t *result_pool,
1364                                    apr_pool_t *scratch_pool)
1365{
1366  apr_array_header_t *orig_externals;
1367  int i;
1368  apr_pool_t *iterpool;
1369
1370  /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1371  SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1372                                                 wc_ctx->db,
1373                                                 local_abspath,
1374                                                 depth != svn_depth_infinity,
1375                                                 result_pool, scratch_pool));
1376
1377  if (orig_externals == NULL)
1378    return SVN_NO_ERROR;
1379
1380  iterpool = svn_pool_create(scratch_pool);
1381
1382  for (i = 0; i < orig_externals->nelts; i++)
1383    {
1384      svn_boolean_t is_rolled_out;
1385
1386      svn_wc__committable_external_info_t *xinfo =
1387        APR_ARRAY_IDX(orig_externals, i,
1388                      svn_wc__committable_external_info_t *);
1389
1390      /* Discard dirs for svn_depth_files (s.a.). */
1391      if (depth == svn_depth_files
1392          && xinfo->kind == svn_node_dir)
1393        continue;
1394
1395      svn_pool_clear(iterpool);
1396
1397      /* Discard those externals that are not currently checked out. */
1398      SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1399                                     iterpool));
1400      if (! is_rolled_out)
1401        continue;
1402
1403      if (*externals == NULL)
1404        *externals = apr_array_make(
1405                               result_pool, 0,
1406                               sizeof(svn_wc__committable_external_info_t *));
1407
1408      APR_ARRAY_PUSH(*externals,
1409                     svn_wc__committable_external_info_t *) = xinfo;
1410
1411      if (depth != svn_depth_infinity)
1412        continue;
1413
1414      /* Are there any nested externals? */
1415      SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1416                                                  xinfo->local_abspath,
1417                                                  svn_depth_infinity,
1418                                                  result_pool, iterpool));
1419    }
1420
1421  return SVN_NO_ERROR;
1422}
1423
1424svn_error_t *
1425svn_wc__externals_defined_below(apr_hash_t **externals,
1426                                svn_wc_context_t *wc_ctx,
1427                                const char *local_abspath,
1428                                apr_pool_t *result_pool,
1429                                apr_pool_t *scratch_pool)
1430{
1431  return svn_error_trace(
1432            svn_wc__db_externals_defined_below(externals,
1433                                               wc_ctx->db, local_abspath,
1434                                               result_pool, scratch_pool));
1435}
1436
1437svn_error_t *
1438svn_wc__external_register(svn_wc_context_t *wc_ctx,
1439                          const char *defining_abspath,
1440                          const char *local_abspath,
1441                          svn_node_kind_t kind,
1442                          const char *repos_root_url,
1443                          const char *repos_uuid,
1444                          const char *repos_relpath,
1445                          svn_revnum_t operational_revision,
1446                          svn_revnum_t revision,
1447                          apr_pool_t *scratch_pool)
1448{
1449  SVN_ERR_ASSERT(kind == svn_node_dir);
1450  return svn_error_trace(
1451            svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1452                                        defining_abspath,
1453                                        repos_root_url,
1454                                        repos_uuid,
1455                                        defining_abspath,
1456                                        repos_relpath,
1457                                        operational_revision,
1458                                        revision,
1459                                        NULL,
1460                                        scratch_pool));
1461}
1462
1463svn_error_t *
1464svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1465                        const char *wri_abspath,
1466                        const char *local_abspath,
1467                        svn_boolean_t declaration_only,
1468                        svn_cancel_func_t cancel_func,
1469                        void *cancel_baton,
1470                        apr_pool_t *scratch_pool)
1471{
1472  svn_wc__db_status_t status;
1473  svn_node_kind_t kind;
1474
1475  SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1476                                   NULL, NULL,
1477                                   wc_ctx->db, local_abspath, wri_abspath,
1478                                   scratch_pool, scratch_pool));
1479
1480  SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1481                                     NULL, scratch_pool));
1482
1483  if (declaration_only)
1484    return SVN_NO_ERROR;
1485
1486  if (kind == svn_node_dir)
1487    SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1488                                                 TRUE, TRUE,
1489                                                 cancel_func, cancel_baton,
1490                                                 scratch_pool));
1491  else
1492    {
1493      SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1494                                     FALSE, TRUE, FALSE,
1495                                     0,
1496                                     NULL, NULL, scratch_pool));
1497      SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1498                             cancel_func, cancel_baton,
1499                             scratch_pool));
1500    }
1501
1502  return SVN_NO_ERROR;
1503}
1504
1505svn_error_t *
1506svn_wc__externals_gather_definitions(apr_hash_t **externals,
1507                                     apr_hash_t **depths,
1508                                     svn_wc_context_t *wc_ctx,
1509                                     const char *local_abspath,
1510                                     svn_depth_t depth,
1511                                     apr_pool_t *result_pool,
1512                                     apr_pool_t *scratch_pool)
1513{
1514  if (depth == svn_depth_infinity
1515      || depth == svn_depth_unknown)
1516    {
1517      return svn_error_trace(
1518        svn_wc__db_externals_gather_definitions(externals, depths,
1519                                                wc_ctx->db, local_abspath,
1520                                                result_pool, scratch_pool));
1521    }
1522  else
1523    {
1524      const svn_string_t *value;
1525      svn_error_t *err;
1526      *externals = apr_hash_make(result_pool);
1527
1528      local_abspath = apr_pstrdup(result_pool, local_abspath);
1529
1530      err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1531                             SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1532
1533      if (err)
1534        {
1535          if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1536            return svn_error_trace(err);
1537
1538          svn_error_clear(err);
1539          value = NULL;
1540        }
1541
1542      if (value)
1543        svn_hash_sets(*externals, local_abspath, value->data);
1544
1545      if (value && depths)
1546        {
1547          svn_depth_t node_depth;
1548          *depths = apr_hash_make(result_pool);
1549
1550          SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1551                                       NULL, NULL, NULL, &node_depth, NULL,
1552                                       NULL, NULL, NULL, NULL, NULL, NULL,
1553                                       NULL, NULL, NULL, NULL, NULL, NULL,
1554                                       NULL, NULL, NULL, NULL,
1555                                       wc_ctx->db, local_abspath,
1556                                       scratch_pool, scratch_pool));
1557
1558          svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1559        }
1560
1561      return SVN_NO_ERROR;
1562    }
1563}
1564
1565svn_error_t *
1566svn_wc__close_db(const char *external_abspath,
1567                 svn_wc_context_t *wc_ctx,
1568                 apr_pool_t *scratch_pool)
1569{
1570  SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1571                               scratch_pool));
1572  return SVN_NO_ERROR;
1573}
1574
1575/* Return the scheme of @a uri in @a scheme allocated from @a pool.
1576   If @a uri does not appear to be a valid URI, then @a scheme will
1577   not be updated.  */
1578static svn_error_t *
1579uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1580{
1581  apr_size_t i;
1582
1583  for (i = 0; uri[i] && uri[i] != ':'; ++i)
1584    if (uri[i] == '/')
1585      goto error;
1586
1587  if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1588    {
1589      *scheme = apr_pstrmemdup(pool, uri, i);
1590      return SVN_NO_ERROR;
1591    }
1592
1593error:
1594  return svn_error_createf(SVN_ERR_BAD_URL, 0,
1595                           _("URL '%s' does not begin with a scheme"),
1596                           uri);
1597}
1598
1599svn_error_t *
1600svn_wc__resolve_relative_external_url(const char **resolved_url,
1601                                      const svn_wc_external_item2_t *item,
1602                                      const char *repos_root_url,
1603                                      const char *parent_dir_url,
1604                                      apr_pool_t *result_pool,
1605                                      apr_pool_t *scratch_pool)
1606{
1607  const char *url = item->url;
1608  apr_uri_t parent_dir_uri;
1609  apr_status_t status;
1610
1611  *resolved_url = item->url;
1612
1613  /* If the URL is already absolute, there is nothing to do. */
1614  if (svn_path_is_url(url))
1615    {
1616      /* "http://server/path" */
1617      *resolved_url = svn_uri_canonicalize(url, result_pool);
1618      return SVN_NO_ERROR;
1619    }
1620
1621  if (url[0] == '/')
1622    {
1623      /* "/path", "//path", and "///path" */
1624      int num_leading_slashes = 1;
1625      if (url[1] == '/')
1626        {
1627          num_leading_slashes++;
1628          if (url[2] == '/')
1629            num_leading_slashes++;
1630        }
1631
1632      /* "//schema-relative" and in some cases "///schema-relative".
1633         This last format is supported on file:// schema relative. */
1634      url = apr_pstrcat(scratch_pool,
1635                        apr_pstrndup(scratch_pool, url, num_leading_slashes),
1636                        svn_relpath_canonicalize(url + num_leading_slashes,
1637                                                 scratch_pool),
1638                        SVN_VA_NULL);
1639    }
1640  else
1641    {
1642      /* "^/path" and "../path" */
1643      url = svn_relpath_canonicalize(url, scratch_pool);
1644    }
1645
1646  /* Parse the parent directory URL into its parts. */
1647  status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1648  if (status)
1649    return svn_error_createf(SVN_ERR_BAD_URL, 0,
1650                             _("Illegal parent directory URL '%s'"),
1651                             parent_dir_url);
1652
1653  /* If the parent directory URL is at the server root, then the URL
1654     may have no / after the hostname so apr_uri_parse() will leave
1655     the URL's path as NULL. */
1656  if (! parent_dir_uri.path)
1657    parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1658  parent_dir_uri.query = NULL;
1659  parent_dir_uri.fragment = NULL;
1660
1661  /* Handle URLs relative to the current directory or to the
1662     repository root.  The backpaths may only remove path elements,
1663     not the hostname.  This allows an external to refer to another
1664     repository in the same server relative to the location of this
1665     repository, say using SVNParentPath. */
1666  if ((0 == strncmp("../", url, 3)) ||
1667      (0 == strncmp("^/", url, 2)))
1668    {
1669      apr_array_header_t *base_components;
1670      apr_array_header_t *relative_components;
1671      int i;
1672
1673      /* Decompose either the parent directory's URL path or the
1674         repository root's URL path into components.  */
1675      if (0 == strncmp("../", url, 3))
1676        {
1677          base_components = svn_path_decompose(parent_dir_uri.path,
1678                                               scratch_pool);
1679          relative_components = svn_path_decompose(url, scratch_pool);
1680        }
1681      else
1682        {
1683          apr_uri_t repos_root_uri;
1684
1685          status = apr_uri_parse(scratch_pool, repos_root_url,
1686                                 &repos_root_uri);
1687          if (status)
1688            return svn_error_createf(SVN_ERR_BAD_URL, 0,
1689                                     _("Illegal repository root URL '%s'"),
1690                                     repos_root_url);
1691
1692          /* If the repository root URL is at the server root, then
1693             the URL may have no / after the hostname so
1694             apr_uri_parse() will leave the URL's path as NULL. */
1695          if (! repos_root_uri.path)
1696            repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1697
1698          base_components = svn_path_decompose(repos_root_uri.path,
1699                                               scratch_pool);
1700          relative_components = svn_path_decompose(url + 2, scratch_pool);
1701        }
1702
1703      for (i = 0; i < relative_components->nelts; ++i)
1704        {
1705          const char *component = APR_ARRAY_IDX(relative_components,
1706                                                i,
1707                                                const char *);
1708          if (0 == strcmp("..", component))
1709            {
1710              /* Constructing the final absolute URL together with
1711                 apr_uri_unparse() requires that the path be absolute,
1712                 so only pop a component if the component being popped
1713                 is not the component for the root directory. */
1714              if (base_components->nelts > 1)
1715                apr_array_pop(base_components);
1716            }
1717          else
1718            APR_ARRAY_PUSH(base_components, const char *) = component;
1719        }
1720
1721      parent_dir_uri.path = (char *)svn_path_compose(base_components,
1722                                                     scratch_pool);
1723      *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1724                                                           &parent_dir_uri, 0),
1725                                       result_pool);
1726      return SVN_NO_ERROR;
1727    }
1728
1729  /* The remaining URLs are relative to either the scheme or server root
1730     and can only refer to locations inside that scope, so backpaths are
1731     not allowed. */
1732  if (svn_path_is_backpath_present(url))
1733    return svn_error_createf(SVN_ERR_BAD_URL, 0,
1734                             _("The external relative URL '%s' cannot have "
1735                               "backpaths, i.e. '..'"),
1736                             item->url);
1737
1738  /* Relative to the scheme: Build a new URL from the parts we know. */
1739  if (0 == strncmp("//", url, 2))
1740    {
1741      const char *scheme;
1742
1743      SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1744      *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1745                                                       ":", url, SVN_VA_NULL),
1746                                           result_pool);
1747      return SVN_NO_ERROR;
1748    }
1749
1750  /* Relative to the server root: Just replace the path portion of the
1751     parent's URL. */
1752  if (url[0] == '/')
1753    {
1754      parent_dir_uri.path = (char *)url;
1755      *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1756                                                           &parent_dir_uri, 0),
1757                                           result_pool);
1758      return SVN_NO_ERROR;
1759    }
1760
1761  return svn_error_createf(SVN_ERR_BAD_URL, 0,
1762                           _("Unrecognized format for the relative external "
1763                             "URL '%s'"),
1764                           item->url);
1765}
1766