]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_wc/externals.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_wc / externals.c
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 \f
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 \f
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  */
78 static svn_error_t *
79 find_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
164 svn_error_t *
165 svn_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
386 svn_error_t *
387 svn_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
401 svn_error_t *
402 svn_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
449 struct 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_cancel_func_t cancel_func;
479   void *cancel_baton;
480   svn_wc_notify_func2_t notify_func;
481   void *notify_baton;
482
483   svn_revnum_t *target_revision;
484
485   /* What was there before the update */
486   svn_revnum_t original_revision;
487   const svn_checksum_t *original_checksum;
488
489   /* What we are installing now */
490   svn_wc__db_install_data_t *install_data;
491   svn_checksum_t *new_sha1_checksum;
492   svn_checksum_t *new_md5_checksum;
493
494   /* List of incoming propchanges */
495   apr_array_header_t *propchanges;
496
497   /* Array of svn_prop_inherited_item_t * structures representing the
498      properties inherited by the base node at LOCAL_ABSPATH. */
499   apr_array_header_t *iprops;
500
501   /* The last change information */
502   svn_revnum_t changed_rev;
503   apr_time_t changed_date;
504   const char *changed_author;
505
506   svn_boolean_t had_props;
507
508   svn_boolean_t file_closed;
509 };
510
511 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
512 static svn_error_t *
513 set_target_revision(void *edit_baton,
514                      svn_revnum_t target_revision,
515                      apr_pool_t *pool)
516 {
517   struct edit_baton *eb = edit_baton;
518
519   *eb->target_revision = target_revision;
520
521   return SVN_NO_ERROR;
522 }
523
524 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
525 static svn_error_t *
526 open_root(void *edit_baton,
527           svn_revnum_t base_revision,
528           apr_pool_t *dir_pool,
529           void **root_baton)
530 {
531   *root_baton = edit_baton;
532   return SVN_NO_ERROR;
533 }
534
535 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
536 static svn_error_t *
537 add_file(const char *path,
538          void *parent_baton,
539          const char *copyfrom_path,
540          svn_revnum_t copyfrom_revision,
541          apr_pool_t *file_pool,
542          void **file_baton)
543 {
544   struct edit_baton *eb = parent_baton;
545   if (strcmp(path, eb->name))
546       return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
547                                _("This editor can only update '%s'"),
548                                svn_dirent_local_style(eb->local_abspath,
549                                                       file_pool));
550
551   *file_baton = eb;
552   eb->original_revision = SVN_INVALID_REVNUM;
553   eb->added = TRUE;
554
555   return SVN_NO_ERROR;
556 }
557
558 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
559 static svn_error_t *
560 open_file(const char *path,
561           void *parent_baton,
562           svn_revnum_t base_revision,
563           apr_pool_t *file_pool,
564           void **file_baton)
565 {
566   struct edit_baton *eb = parent_baton;
567   svn_node_kind_t kind;
568   if (strcmp(path, eb->name))
569       return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
570                                _("This editor can only update '%s'"),
571                                svn_dirent_local_style(eb->local_abspath,
572                                                       file_pool));
573
574   *file_baton = eb;
575   SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
576                                    &eb->old_repos_relpath, NULL, NULL,
577                                    &eb->changed_rev,
578                                    &eb->changed_date, &eb->changed_author,
579                                    NULL, &eb->original_checksum, NULL, NULL,
580                                    &eb->had_props, NULL, NULL,
581                                    eb->db, eb->local_abspath,
582                                    eb->pool, file_pool));
583
584   if (kind != svn_node_file)
585     return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
586                                _("Node '%s' is no existing file external"),
587                                svn_dirent_local_style(eb->local_abspath,
588                                                       file_pool));
589   return SVN_NO_ERROR;
590 }
591
592 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
593 static svn_error_t *
594 apply_textdelta(void *file_baton,
595                 const char *base_checksum_digest,
596                 apr_pool_t *pool,
597                 svn_txdelta_window_handler_t *handler,
598                 void **handler_baton)
599 {
600   struct edit_baton *eb = file_baton;
601   svn_stream_t *src_stream;
602   svn_stream_t *dest_stream;
603
604   if (eb->original_checksum)
605     {
606       if (base_checksum_digest)
607         {
608           svn_checksum_t *expected_checksum;
609           const svn_checksum_t *original_md5;
610
611           SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
612                                          base_checksum_digest, pool));
613
614           if (eb->original_checksum->kind != svn_checksum_md5)
615             SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
616                                                 eb->db, eb->wri_abspath,
617                                                 eb->original_checksum,
618                                                 pool, pool));
619           else
620             original_md5 = eb->original_checksum;
621
622           if (!svn_checksum_match(expected_checksum, original_md5))
623             return svn_error_trace(svn_checksum_mismatch_err(
624                                     expected_checksum,
625                                     original_md5,
626                                     pool,
627                                     _("Base checksum mismatch for '%s'"),
628                                     svn_dirent_local_style(eb->local_abspath,
629                                                            pool)));
630         }
631
632       SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
633                                        eb->wri_abspath, eb->original_checksum,
634                                        pool, pool));
635     }
636   else
637     src_stream = svn_stream_empty(pool);
638
639   SVN_ERR(svn_wc__db_pristine_prepare_install(&dest_stream,
640                                               &eb->install_data,
641                                               &eb->new_sha1_checksum,
642                                               &eb->new_md5_checksum,
643                                               eb->db, eb->wri_abspath,
644                                               eb->pool, pool));
645
646   svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
647                     handler, handler_baton);
648
649   return SVN_NO_ERROR;
650 }
651
652 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
653 static svn_error_t *
654 change_file_prop(void *file_baton,
655                  const char *name,
656                  const svn_string_t *value,
657                  apr_pool_t *pool)
658 {
659   struct edit_baton *eb = file_baton;
660   svn_prop_t *propchange;
661
662   propchange = apr_array_push(eb->propchanges);
663   propchange->name = apr_pstrdup(eb->pool, name);
664   propchange->value = svn_string_dup(value, eb->pool);
665
666   return SVN_NO_ERROR;
667 }
668
669 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
670 static svn_error_t *
671 close_file(void *file_baton,
672            const char *expected_md5_digest,
673            apr_pool_t *pool)
674 {
675   struct edit_baton *eb = file_baton;
676   svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
677   svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
678   svn_boolean_t obstructed = FALSE;
679
680   eb->file_closed = TRUE; /* We bump the revision here */
681
682   /* Check the checksum, if provided */
683   if (expected_md5_digest)
684     {
685       svn_checksum_t *expected_md5_checksum;
686       const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
687
688       SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
689                                      expected_md5_digest, pool));
690
691       if (actual_md5_checksum == NULL)
692         {
693           actual_md5_checksum = eb->original_checksum;
694
695           if (actual_md5_checksum != NULL
696               && actual_md5_checksum->kind != svn_checksum_md5)
697             {
698               SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
699                                                   eb->db, eb->wri_abspath,
700                                                   actual_md5_checksum,
701                                                   pool, pool));
702             }
703         }
704
705       if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
706         return svn_checksum_mismatch_err(
707                         expected_md5_checksum,
708                         actual_md5_checksum, pool,
709                         _("Checksum mismatch for '%s'"),
710                         svn_dirent_local_style(eb->local_abspath, pool));
711     }
712
713   /* First move the file in the pristine store; this hands over the cleanup
714      behavior to the pristine store. */
715   if (eb->new_sha1_checksum)
716     {
717       SVN_ERR(svn_wc__db_pristine_install(eb->install_data,
718                                           eb->new_sha1_checksum,
719                                           eb->new_md5_checksum, pool));
720
721       eb->install_data = NULL;
722     }
723
724   /* Merge the changes */
725   {
726     svn_skel_t *all_work_items = NULL;
727     svn_skel_t *conflict_skel = NULL;
728     svn_skel_t *work_item;
729     apr_hash_t *base_props = NULL;
730     apr_hash_t *actual_props = NULL;
731     apr_hash_t *new_pristine_props = NULL;
732     apr_hash_t *new_actual_props = NULL;
733     apr_hash_t *new_dav_props = NULL;
734     const svn_checksum_t *new_checksum = NULL;
735     const svn_checksum_t *original_checksum = NULL;
736
737     svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
738
739     if (! added)
740       {
741         new_checksum = eb->original_checksum;
742
743         if (eb->had_props)
744           SVN_ERR(svn_wc__db_base_get_props(
745                     &base_props, eb->db, eb->local_abspath, pool, pool));
746
747         SVN_ERR(svn_wc__db_read_props(
748                   &actual_props, eb->db, eb->local_abspath, pool, pool));
749       }
750
751     if (!base_props)
752       base_props = apr_hash_make(pool);
753
754     if (!actual_props)
755       actual_props = apr_hash_make(pool);
756
757     if (eb->new_sha1_checksum)
758       new_checksum = eb->new_sha1_checksum;
759
760     /* Merge the properties */
761     {
762       apr_array_header_t *entry_prop_changes;
763       apr_array_header_t *dav_prop_changes;
764       apr_array_header_t *regular_prop_changes;
765       int i;
766
767       SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
768                                    &dav_prop_changes, &regular_prop_changes,
769                                    pool));
770
771       /* Read the entry-prop changes to update the last-changed info. */
772       for (i = 0; i < entry_prop_changes->nelts; i++)
773         {
774           const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
775                                                   svn_prop_t);
776
777           if (! prop->value)
778             continue; /* authz or something */
779
780           if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
781             eb->changed_author = apr_pstrdup(pool, prop->value->data);
782           else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
783             {
784               apr_int64_t rev;
785               SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
786               eb->changed_rev = (svn_revnum_t)rev;
787             }
788           else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
789             SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
790                                           pool));
791         }
792
793       /* Store the DAV-prop (aka WC-prop) changes.  (This treats a list
794        * of changes as a list of new props, but we only use this when
795        * adding a new file and it's equivalent in that case.) */
796       if (dav_prop_changes->nelts > 0)
797         new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
798
799       /* Merge the regular prop changes. */
800       if (regular_prop_changes->nelts > 0)
801         {
802           new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
803                                                pool);
804           SVN_ERR(svn_wc__merge_props(&conflict_skel,
805                                       &prop_state,
806                                       &new_actual_props,
807                                       eb->db, eb->local_abspath,
808                                       NULL /* server_baseprops*/,
809                                       base_props,
810                                       actual_props,
811                                       regular_prop_changes,
812                                       pool, pool));
813         }
814       else
815         {
816           new_pristine_props = base_props;
817           new_actual_props = actual_props;
818         }
819     }
820
821     /* Merge the text */
822     if (eb->new_sha1_checksum)
823       {
824         svn_node_kind_t disk_kind;
825         svn_boolean_t install_pristine = FALSE;
826
827         SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
828
829         if (disk_kind == svn_node_none)
830           {
831             /* Just install the file */
832             install_pristine = TRUE;
833             content_state = svn_wc_notify_state_changed;
834           }
835         else if (disk_kind != svn_node_file
836                  || (eb->added && disk_kind == svn_node_file))
837           {
838             /* The node is obstructed; we just change the DB */
839             obstructed = TRUE;
840             content_state = svn_wc_notify_state_unchanged;
841           }
842         else
843           {
844             svn_boolean_t is_mod;
845             SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
846                                                      eb->db, eb->local_abspath,
847                                                      FALSE, pool));
848
849             if (!is_mod)
850               {
851                 install_pristine = TRUE;
852                 content_state = svn_wc_notify_state_changed;
853               }
854             else
855               {
856                 svn_boolean_t found_text_conflict;
857
858                 /* Ok, we have to do some work to merge a local change */
859                 SVN_ERR(svn_wc__perform_file_merge(&work_item,
860                                                    &conflict_skel,
861                                                    &found_text_conflict,
862                                                    eb->db,
863                                                    eb->local_abspath,
864                                                    eb->wri_abspath,
865                                                    new_checksum,
866                                                    original_checksum,
867                                                    actual_props,
868                                                    eb->ext_patterns,
869                                                    eb->original_revision,
870                                                    *eb->target_revision,
871                                                    eb->propchanges,
872                                                    eb->diff3cmd,
873                                                    eb->cancel_func,
874                                                    eb->cancel_baton,
875                                                    pool, pool));
876
877                 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
878                                                   pool);
879
880                 if (found_text_conflict)
881                   content_state = svn_wc_notify_state_conflicted;
882                 else
883                   content_state = svn_wc_notify_state_merged;
884               }
885           }
886         if (install_pristine)
887           {
888             SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
889                                             eb->local_abspath,
890                                             NULL,
891                                             eb->use_commit_times, TRUE,
892                                             pool, pool));
893
894             all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
895           }
896       }
897     else
898       {
899         content_state = svn_wc_notify_state_unchanged;
900         /* ### Retranslate on magic property changes, etc. */
901       }
902
903     /* Generate a conflict description, if needed */
904     if (conflict_skel)
905       {
906         SVN_ERR(svn_wc__conflict_skel_set_op_switch(
907                             conflict_skel,
908                             svn_wc_conflict_version_create2(
909                                     eb->repos_root_url,
910                                     eb->repos_uuid,
911                                     eb->old_repos_relpath,
912                                     eb->original_revision,
913                                     svn_node_file,
914                                     pool),
915                             svn_wc_conflict_version_create2(
916                                     eb->repos_root_url,
917                                     eb->repos_uuid,
918                                     eb->new_repos_relpath,
919                                     *eb->target_revision,
920                                     svn_node_file,
921                                     pool),
922                             pool, pool));
923         SVN_ERR(svn_wc__conflict_create_markers(&work_item,
924                                                 eb->db, eb->local_abspath,
925                                                 conflict_skel,
926                                                 pool, pool));
927         all_work_items = svn_wc__wq_merge(all_work_items, work_item,
928                                           pool);
929       }
930
931     /* Install the file in the DB */
932     SVN_ERR(svn_wc__db_external_add_file(
933                         eb->db,
934                         eb->local_abspath,
935                         eb->wri_abspath,
936                         eb->new_repos_relpath,
937                         eb->repos_root_url,
938                         eb->repos_uuid,
939                         *eb->target_revision,
940                         new_pristine_props,
941                         eb->iprops,
942                         eb->changed_rev,
943                         eb->changed_date,
944                         eb->changed_author,
945                         new_checksum,
946                         new_dav_props,
947                         eb->record_ancestor_abspath,
948                         eb->recorded_repos_relpath,
949                         eb->recorded_peg_revision,
950                         eb->recorded_revision,
951                         TRUE, new_actual_props,
952                         FALSE /* keep_recorded_info */,
953                         conflict_skel,
954                         all_work_items,
955                         pool));
956
957     /* close_edit may also update iprops for switched files, catching
958        those for which close_file is never called (e.g. an update of a
959        file external with no changes).  So as a minor optimization we
960        clear the iprops so as not to set them again in close_edit. */
961     eb->iprops = NULL;
962
963     /* Run the work queue to complete the installation */
964     SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
965                            eb->cancel_func, eb->cancel_baton, pool));
966   }
967
968   /* Notify */
969   if (eb->notify_func)
970     {
971       svn_wc_notify_action_t action;
972       svn_wc_notify_t *notify;
973
974       if (!eb->added)
975         action = obstructed ? svn_wc_notify_update_shadowed_update
976                             : svn_wc_notify_update_update;
977       else
978         action = obstructed ? svn_wc_notify_update_shadowed_add
979                             : svn_wc_notify_update_add;
980
981       notify = svn_wc_create_notify(eb->local_abspath, action, pool);
982       notify->kind = svn_node_file;
983
984       notify->revision = *eb->target_revision;
985       notify->prop_state = prop_state;
986       notify->content_state = content_state;
987
988       notify->old_revision = eb->original_revision;
989
990       eb->notify_func(eb->notify_baton, notify, pool);
991     }
992
993   return SVN_NO_ERROR;
994 }
995
996 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
997 static svn_error_t *
998 close_edit(void *edit_baton,
999            apr_pool_t *pool)
1000 {
1001   struct edit_baton *eb = edit_baton;
1002
1003   if (!eb->file_closed)
1004     {
1005       apr_hash_t *wcroot_iprops = NULL;
1006       /* The file wasn't updated, but its url or revision might have...
1007          e.g. switch between branches for relative externals.
1008
1009          Just bump the information as that is just as expensive as
1010          investigating when we should and shouldn't update it...
1011          and avoid hard to debug edge cases */
1012
1013       if (eb->iprops)
1014         {
1015           wcroot_iprops = apr_hash_make(pool);
1016           svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
1017         }
1018
1019       SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
1020                                                        eb->local_abspath,
1021                                                        svn_depth_infinity,
1022                                                        eb->new_repos_relpath,
1023                                                        eb->repos_root_url,
1024                                                        eb->repos_uuid,
1025                                                        *eb->target_revision,
1026                                                        apr_hash_make(pool)
1027                                                        /* exclude_relpaths */,
1028                                                        wcroot_iprops,
1029                                                        TRUE /* empty update */,
1030                                                        eb->notify_func,
1031                                                        eb->notify_baton,
1032                                                        pool));
1033     }
1034
1035   return SVN_NO_ERROR;
1036 }
1037
1038 svn_error_t *
1039 svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
1040                                  void **edit_baton,
1041                                  svn_revnum_t *target_revision,
1042                                  svn_wc_context_t *wc_ctx,
1043                                  const char *local_abspath,
1044                                  const char *wri_abspath,
1045                                  const char *url,
1046                                  const char *repos_root_url,
1047                                  const char *repos_uuid,
1048                                  apr_array_header_t *iprops,
1049                                  svn_boolean_t use_commit_times,
1050                                  const char *diff3_cmd,
1051                                  const apr_array_header_t *preserved_exts,
1052                                  const char *record_ancestor_abspath,
1053                                  const char *recorded_url,
1054                                  const svn_opt_revision_t *recorded_peg_rev,
1055                                  const svn_opt_revision_t *recorded_rev,
1056                                  svn_cancel_func_t cancel_func,
1057                                  void *cancel_baton,
1058                                  svn_wc_notify_func2_t notify_func,
1059                                  void *notify_baton,
1060                                  apr_pool_t *result_pool,
1061                                  apr_pool_t *scratch_pool)
1062 {
1063   svn_wc__db_t *db = wc_ctx->db;
1064   apr_pool_t *edit_pool = result_pool;
1065   struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1066   svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1067
1068   eb->pool = edit_pool;
1069   eb->db = db;
1070   eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1071   if (wri_abspath)
1072     eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1073   else
1074     eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1075   eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1076   eb->target_revision = target_revision;
1077
1078   eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1079   eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1080   eb->new_repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, url, edit_pool);
1081   eb->old_repos_relpath = eb->new_repos_relpath;
1082
1083   eb->original_revision = SVN_INVALID_REVNUM;
1084
1085   eb->iprops = iprops;
1086
1087   eb->use_commit_times = use_commit_times;
1088   eb->ext_patterns = preserved_exts;
1089   eb->diff3cmd = diff3_cmd;
1090
1091   eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1092   eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1093                                                      edit_pool);
1094
1095   eb->changed_rev = SVN_INVALID_REVNUM;
1096
1097   if (recorded_peg_rev->kind == svn_opt_revision_number)
1098     eb->recorded_peg_revision = recorded_peg_rev->value.number;
1099   else
1100     eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1101
1102   if (recorded_rev->kind == svn_opt_revision_number)
1103     eb->recorded_revision = recorded_rev->value.number;
1104   else
1105     eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1106
1107   eb->cancel_func = cancel_func;
1108   eb->cancel_baton = cancel_baton;
1109   eb->notify_func = notify_func;
1110   eb->notify_baton = notify_baton;
1111
1112   eb->propchanges  = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1113
1114   tree_editor->open_root = open_root;
1115   tree_editor->set_target_revision = set_target_revision;
1116   tree_editor->add_file = add_file;
1117   tree_editor->open_file = open_file;
1118   tree_editor->apply_textdelta = apply_textdelta;
1119   tree_editor->change_file_prop = change_file_prop;
1120   tree_editor->close_file = close_file;
1121   tree_editor->close_edit = close_edit;
1122
1123   return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1124                                            tree_editor, eb,
1125                                            editor, edit_baton,
1126                                            result_pool);
1127 }
1128
1129 svn_error_t *
1130 svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1131                             const char *local_abspath,
1132                             const svn_ra_reporter3_t *reporter,
1133                             void *report_baton,
1134                             svn_boolean_t restore_files,
1135                             svn_boolean_t use_commit_times,
1136                             svn_cancel_func_t cancel_func,
1137                             void *cancel_baton,
1138                             svn_wc_notify_func2_t notify_func,
1139                             void *notify_baton,
1140                             apr_pool_t *scratch_pool)
1141 {
1142   svn_wc__db_t *db = wc_ctx->db;
1143   svn_error_t *err;
1144   svn_node_kind_t kind;
1145   svn_wc__db_lock_t *lock;
1146   svn_revnum_t revision;
1147   const char *repos_root_url;
1148   const char *repos_relpath;
1149   svn_boolean_t update_root;
1150
1151   err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1152                                  &repos_relpath, &repos_root_url, NULL, NULL,
1153                                  NULL, NULL, NULL, NULL, NULL, &lock,
1154                                  NULL, NULL, &update_root,
1155                                  db, local_abspath,
1156                                  scratch_pool, scratch_pool);
1157
1158   if (err
1159       || kind == svn_node_dir
1160       || !update_root)
1161     {
1162       if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1163         return svn_error_trace(err);
1164
1165       svn_error_clear(err);
1166
1167       /* We don't know about this node, so all we have to do is tell
1168          the reporter that we don't know this node.
1169
1170          But first we have to start the report by sending some basic
1171          information for the root. */
1172
1173       SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1174                                  FALSE, NULL, scratch_pool));
1175       SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1176
1177       /* Finish the report, which causes the update editor to be
1178          driven. */
1179       SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1180
1181       return SVN_NO_ERROR;
1182     }
1183   else
1184     {
1185       if (restore_files)
1186         {
1187           svn_node_kind_t disk_kind;
1188           SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1189
1190           if (disk_kind == svn_node_none)
1191             {
1192               err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1193                                    scratch_pool);
1194
1195               if (err)
1196                 {
1197                   if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1198                     return svn_error_trace(err);
1199
1200                   svn_error_clear(err);
1201                 }
1202             }
1203         }
1204
1205       /* Report that we know the path */
1206       SVN_ERR(reporter->set_path(report_baton, "", revision,
1207                                  svn_depth_infinity, FALSE, NULL,
1208                                  scratch_pool));
1209
1210       /* For compatibility with the normal update editor report we report
1211          the target as switched.
1212
1213          ### We can probably report a parent url and unswitched later */
1214       SVN_ERR(reporter->link_path(report_baton, "",
1215                                   svn_path_url_add_component2(repos_root_url,
1216                                                               repos_relpath,
1217                                                               scratch_pool),
1218                                   revision,
1219                                   svn_depth_infinity,
1220                                   FALSE /* start_empty*/,
1221                                   lock ? lock->token : NULL,
1222                                   scratch_pool));
1223     }
1224
1225   return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1226 }
1227
1228 svn_error_t *
1229 svn_wc__read_external_info(svn_node_kind_t *external_kind,
1230                            const char **defining_abspath,
1231                            const char **defining_url,
1232                            svn_revnum_t *defining_operational_revision,
1233                            svn_revnum_t *defining_revision,
1234                            svn_wc_context_t *wc_ctx,
1235                            const char *wri_abspath,
1236                            const char *local_abspath,
1237                            svn_boolean_t ignore_enoent,
1238                            apr_pool_t *result_pool,
1239                            apr_pool_t *scratch_pool)
1240 {
1241   const char *repos_root_url;
1242   svn_wc__db_status_t status;
1243   svn_node_kind_t kind;
1244   svn_error_t *err;
1245
1246   err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1247                                  defining_url ? &repos_root_url : NULL, NULL,
1248                                  defining_url, defining_operational_revision,
1249                                  defining_revision,
1250                                  wc_ctx->db, local_abspath, wri_abspath,
1251                                  result_pool, scratch_pool);
1252
1253   if (err)
1254     {
1255       if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1256         return svn_error_trace(err);
1257
1258       svn_error_clear(err);
1259
1260       if (external_kind)
1261         *external_kind = svn_node_none;
1262
1263       if (defining_abspath)
1264         *defining_abspath = NULL;
1265
1266       if (defining_url)
1267         *defining_url = NULL;
1268
1269       if (defining_operational_revision)
1270         *defining_operational_revision = SVN_INVALID_REVNUM;
1271
1272       if (defining_revision)
1273         *defining_revision = SVN_INVALID_REVNUM;
1274
1275       return SVN_NO_ERROR;
1276     }
1277
1278   if (external_kind)
1279     {
1280       if (status != svn_wc__db_status_normal)
1281         *external_kind = svn_node_unknown;
1282       else
1283         switch(kind)
1284           {
1285             case svn_node_file:
1286             case svn_node_symlink:
1287               *external_kind = svn_node_file;
1288               break;
1289             case svn_node_dir:
1290               *external_kind = svn_node_dir;
1291               break;
1292             default:
1293               *external_kind = svn_node_none;
1294           }
1295     }
1296
1297   if (defining_url && *defining_url)
1298     *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1299                                                 result_pool);
1300
1301   return SVN_NO_ERROR;
1302 }
1303
1304 /* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1305  * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1306  * XINFO->REPOS_RELPATH.  All allocations are made in SCRATCH_POOL. */
1307 static svn_error_t *
1308 is_external_rolled_out(svn_boolean_t *is_rolled_out,
1309                        svn_wc_context_t *wc_ctx,
1310                        svn_wc__committable_external_info_t *xinfo,
1311                        apr_pool_t *scratch_pool)
1312 {
1313   const char *repos_relpath;
1314   const char *repos_root_url;
1315   svn_error_t *err;
1316
1317   *is_rolled_out = FALSE;
1318
1319   err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1320                                  &repos_root_url, NULL, NULL, NULL, NULL,
1321                                  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1322                                  wc_ctx->db, xinfo->local_abspath,
1323                                  scratch_pool, scratch_pool);
1324
1325   if (err)
1326     {
1327       if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1328         {
1329           svn_error_clear(err);
1330           return SVN_NO_ERROR;
1331         }
1332       SVN_ERR(err);
1333     }
1334
1335   *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1336                     strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1337   return SVN_NO_ERROR;
1338 }
1339
1340 svn_error_t *
1341 svn_wc__committable_externals_below(apr_array_header_t **externals,
1342                                     svn_wc_context_t *wc_ctx,
1343                                     const char *local_abspath,
1344                                     svn_depth_t depth,
1345                                     apr_pool_t *result_pool,
1346                                     apr_pool_t *scratch_pool)
1347 {
1348   apr_array_header_t *orig_externals;
1349   int i;
1350   apr_pool_t *iterpool;
1351
1352   /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1353   SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1354                                                  wc_ctx->db,
1355                                                  local_abspath,
1356                                                  depth != svn_depth_infinity,
1357                                                  result_pool, scratch_pool));
1358
1359   if (orig_externals == NULL)
1360     return SVN_NO_ERROR;
1361
1362   iterpool = svn_pool_create(scratch_pool);
1363
1364   for (i = 0; i < orig_externals->nelts; i++)
1365     {
1366       svn_boolean_t is_rolled_out;
1367
1368       svn_wc__committable_external_info_t *xinfo =
1369         APR_ARRAY_IDX(orig_externals, i,
1370                       svn_wc__committable_external_info_t *);
1371
1372       /* Discard dirs for svn_depth_files (s.a.). */
1373       if (depth == svn_depth_files
1374           && xinfo->kind == svn_node_dir)
1375         continue;
1376
1377       svn_pool_clear(iterpool);
1378
1379       /* Discard those externals that are not currently checked out. */
1380       SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1381                                      iterpool));
1382       if (! is_rolled_out)
1383         continue;
1384
1385       if (*externals == NULL)
1386         *externals = apr_array_make(
1387                                result_pool, 0,
1388                                sizeof(svn_wc__committable_external_info_t *));
1389
1390       APR_ARRAY_PUSH(*externals,
1391                      svn_wc__committable_external_info_t *) = xinfo;
1392
1393       if (depth != svn_depth_infinity)
1394         continue;
1395
1396       /* Are there any nested externals? */
1397       SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1398                                                   xinfo->local_abspath,
1399                                                   svn_depth_infinity,
1400                                                   result_pool, iterpool));
1401     }
1402
1403   return SVN_NO_ERROR;
1404 }
1405
1406 svn_error_t *
1407 svn_wc__externals_defined_below(apr_hash_t **externals,
1408                                 svn_wc_context_t *wc_ctx,
1409                                 const char *local_abspath,
1410                                 apr_pool_t *result_pool,
1411                                 apr_pool_t *scratch_pool)
1412 {
1413   return svn_error_trace(
1414             svn_wc__db_externals_defined_below(externals,
1415                                                wc_ctx->db, local_abspath,
1416                                                result_pool, scratch_pool));
1417 }
1418
1419 svn_error_t *
1420 svn_wc__external_register(svn_wc_context_t *wc_ctx,
1421                           const char *defining_abspath,
1422                           const char *local_abspath,
1423                           svn_node_kind_t kind,
1424                           const char *repos_root_url,
1425                           const char *repos_uuid,
1426                           const char *repos_relpath,
1427                           svn_revnum_t operational_revision,
1428                           svn_revnum_t revision,
1429                           apr_pool_t *scratch_pool)
1430 {
1431   SVN_ERR_ASSERT(kind == svn_node_dir);
1432   return svn_error_trace(
1433             svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1434                                         defining_abspath,
1435                                         repos_root_url,
1436                                         repos_uuid,
1437                                         defining_abspath,
1438                                         repos_relpath,
1439                                         operational_revision,
1440                                         revision,
1441                                         NULL,
1442                                         scratch_pool));
1443 }
1444
1445 svn_error_t *
1446 svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1447                         const char *wri_abspath,
1448                         const char *local_abspath,
1449                         svn_boolean_t declaration_only,
1450                         svn_cancel_func_t cancel_func,
1451                         void *cancel_baton,
1452                         apr_pool_t *scratch_pool)
1453 {
1454   svn_wc__db_status_t status;
1455   svn_node_kind_t kind;
1456
1457   SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1458                                    NULL, NULL,
1459                                    wc_ctx->db, local_abspath, wri_abspath,
1460                                    scratch_pool, scratch_pool));
1461
1462   SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1463                                      NULL, scratch_pool));
1464
1465   if (declaration_only)
1466     return SVN_NO_ERROR;
1467
1468   if (kind == svn_node_dir)
1469     SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1470                                                  TRUE, TRUE,
1471                                                  cancel_func, cancel_baton,
1472                                                  scratch_pool));
1473   else
1474     {
1475       SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1476                                      FALSE, TRUE, FALSE,
1477                                      0,
1478                                      NULL, NULL, scratch_pool));
1479       SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1480                              cancel_func, cancel_baton,
1481                              scratch_pool));
1482     }
1483
1484   return SVN_NO_ERROR;
1485 }
1486
1487 svn_error_t *
1488 svn_wc__externals_gather_definitions(apr_hash_t **externals,
1489                                      apr_hash_t **depths,
1490                                      svn_wc_context_t *wc_ctx,
1491                                      const char *local_abspath,
1492                                      svn_depth_t depth,
1493                                      apr_pool_t *result_pool,
1494                                      apr_pool_t *scratch_pool)
1495 {
1496   if (depth == svn_depth_infinity
1497       || depth == svn_depth_unknown)
1498     {
1499       return svn_error_trace(
1500         svn_wc__db_externals_gather_definitions(externals, depths,
1501                                                 wc_ctx->db, local_abspath,
1502                                                 result_pool, scratch_pool));
1503     }
1504   else
1505     {
1506       const svn_string_t *value;
1507       svn_error_t *err;
1508       *externals = apr_hash_make(result_pool);
1509
1510       local_abspath = apr_pstrdup(result_pool, local_abspath);
1511
1512       err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1513                              SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1514
1515       if (err)
1516         {
1517           if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1518             return svn_error_trace(err);
1519
1520           svn_error_clear(err);
1521           value = NULL;
1522         }
1523
1524       if (value)
1525         svn_hash_sets(*externals, local_abspath, value->data);
1526
1527       if (value && depths)
1528         {
1529           svn_depth_t node_depth;
1530           *depths = apr_hash_make(result_pool);
1531
1532           SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1533                                        NULL, NULL, NULL, &node_depth, NULL,
1534                                        NULL, NULL, NULL, NULL, NULL, NULL,
1535                                        NULL, NULL, NULL, NULL, NULL, NULL,
1536                                        NULL, NULL, NULL, NULL,
1537                                        wc_ctx->db, local_abspath,
1538                                        scratch_pool, scratch_pool));
1539
1540           svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1541         }
1542
1543       return SVN_NO_ERROR;
1544     }
1545 }
1546
1547 svn_error_t *
1548 svn_wc__close_db(const char *external_abspath,
1549                  svn_wc_context_t *wc_ctx,
1550                  apr_pool_t *scratch_pool)
1551 {
1552   SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1553                                scratch_pool));
1554   return SVN_NO_ERROR;
1555 }
1556
1557 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
1558    If @a uri does not appear to be a valid URI, then @a scheme will
1559    not be updated.  */
1560 static svn_error_t *
1561 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1562 {
1563   apr_size_t i;
1564
1565   for (i = 0; uri[i] && uri[i] != ':'; ++i)
1566     if (uri[i] == '/')
1567       goto error;
1568
1569   if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1570     {
1571       *scheme = apr_pstrmemdup(pool, uri, i);
1572       return SVN_NO_ERROR;
1573     }
1574
1575 error:
1576   return svn_error_createf(SVN_ERR_BAD_URL, 0,
1577                            _("URL '%s' does not begin with a scheme"),
1578                            uri);
1579 }
1580
1581 svn_error_t *
1582 svn_wc__resolve_relative_external_url(const char **resolved_url,
1583                                       const svn_wc_external_item2_t *item,
1584                                       const char *repos_root_url,
1585                                       const char *parent_dir_url,
1586                                       apr_pool_t *result_pool,
1587                                       apr_pool_t *scratch_pool)
1588 {
1589   const char *url = item->url;
1590   apr_uri_t parent_dir_uri;
1591   apr_status_t status;
1592
1593   *resolved_url = item->url;
1594
1595   /* If the URL is already absolute, there is nothing to do. */
1596   if (svn_path_is_url(url))
1597     {
1598       /* "http://server/path" */
1599       *resolved_url = svn_uri_canonicalize(url, result_pool);
1600       return SVN_NO_ERROR;
1601     }
1602
1603   if (url[0] == '/')
1604     {
1605       /* "/path", "//path", and "///path" */
1606       int num_leading_slashes = 1;
1607       if (url[1] == '/')
1608         {
1609           num_leading_slashes++;
1610           if (url[2] == '/')
1611             num_leading_slashes++;
1612         }
1613
1614       /* "//schema-relative" and in some cases "///schema-relative".
1615          This last format is supported on file:// schema relative. */
1616       url = apr_pstrcat(scratch_pool,
1617                         apr_pstrndup(scratch_pool, url, num_leading_slashes),
1618                         svn_relpath_canonicalize(url + num_leading_slashes,
1619                                                  scratch_pool),
1620                         SVN_VA_NULL);
1621     }
1622   else
1623     {
1624       /* "^/path" and "../path" */
1625       url = svn_relpath_canonicalize(url, scratch_pool);
1626     }
1627
1628   /* Parse the parent directory URL into its parts. */
1629   status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1630   if (status)
1631     return svn_error_createf(SVN_ERR_BAD_URL, 0,
1632                              _("Illegal parent directory URL '%s'"),
1633                              parent_dir_url);
1634
1635   /* If the parent directory URL is at the server root, then the URL
1636      may have no / after the hostname so apr_uri_parse() will leave
1637      the URL's path as NULL. */
1638   if (! parent_dir_uri.path)
1639     parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1640   parent_dir_uri.query = NULL;
1641   parent_dir_uri.fragment = NULL;
1642
1643   /* Handle URLs relative to the current directory or to the
1644      repository root.  The backpaths may only remove path elements,
1645      not the hostname.  This allows an external to refer to another
1646      repository in the same server relative to the location of this
1647      repository, say using SVNParentPath. */
1648   if ((0 == strncmp("../", url, 3)) ||
1649       (0 == strncmp("^/", url, 2)))
1650     {
1651       apr_array_header_t *base_components;
1652       apr_array_header_t *relative_components;
1653       int i;
1654
1655       /* Decompose either the parent directory's URL path or the
1656          repository root's URL path into components.  */
1657       if (0 == strncmp("../", url, 3))
1658         {
1659           base_components = svn_path_decompose(parent_dir_uri.path,
1660                                                scratch_pool);
1661           relative_components = svn_path_decompose(url, scratch_pool);
1662         }
1663       else
1664         {
1665           apr_uri_t repos_root_uri;
1666
1667           status = apr_uri_parse(scratch_pool, repos_root_url,
1668                                  &repos_root_uri);
1669           if (status)
1670             return svn_error_createf(SVN_ERR_BAD_URL, 0,
1671                                      _("Illegal repository root URL '%s'"),
1672                                      repos_root_url);
1673
1674           /* If the repository root URL is at the server root, then
1675              the URL may have no / after the hostname so
1676              apr_uri_parse() will leave the URL's path as NULL. */
1677           if (! repos_root_uri.path)
1678             repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1679
1680           base_components = svn_path_decompose(repos_root_uri.path,
1681                                                scratch_pool);
1682           relative_components = svn_path_decompose(url + 2, scratch_pool);
1683         }
1684
1685       for (i = 0; i < relative_components->nelts; ++i)
1686         {
1687           const char *component = APR_ARRAY_IDX(relative_components,
1688                                                 i,
1689                                                 const char *);
1690           if (0 == strcmp("..", component))
1691             {
1692               /* Constructing the final absolute URL together with
1693                  apr_uri_unparse() requires that the path be absolute,
1694                  so only pop a component if the component being popped
1695                  is not the component for the root directory. */
1696               if (base_components->nelts > 1)
1697                 apr_array_pop(base_components);
1698             }
1699           else
1700             APR_ARRAY_PUSH(base_components, const char *) = component;
1701         }
1702
1703       parent_dir_uri.path = (char *)svn_path_compose(base_components,
1704                                                      scratch_pool);
1705       *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1706                                                            &parent_dir_uri, 0),
1707                                        result_pool);
1708       return SVN_NO_ERROR;
1709     }
1710
1711   /* The remaining URLs are relative to either the scheme or server root
1712      and can only refer to locations inside that scope, so backpaths are
1713      not allowed. */
1714   if (svn_path_is_backpath_present(url))
1715     return svn_error_createf(SVN_ERR_BAD_URL, 0,
1716                              _("The external relative URL '%s' cannot have "
1717                                "backpaths, i.e. '..'"),
1718                              item->url);
1719
1720   /* Relative to the scheme: Build a new URL from the parts we know. */
1721   if (0 == strncmp("//", url, 2))
1722     {
1723       const char *scheme;
1724
1725       SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1726       *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1727                                                        ":", url, SVN_VA_NULL),
1728                                            result_pool);
1729       return SVN_NO_ERROR;
1730     }
1731
1732   /* Relative to the server root: Just replace the path portion of the
1733      parent's URL. */
1734   if (url[0] == '/')
1735     {
1736       parent_dir_uri.path = (char *)url;
1737       *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1738                                                            &parent_dir_uri, 0),
1739                                            result_pool);
1740       return SVN_NO_ERROR;
1741     }
1742
1743   return svn_error_createf(SVN_ERR_BAD_URL, 0,
1744                            _("Unrecognized format for the relative external "
1745                              "URL '%s'"),
1746                            item->url);
1747 }