]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_wc/externals.c
Upgrade Unbound to 1.7.1.
[FreeBSD/FreeBSD.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_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 */
514 static svn_error_t *
515 set_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 */
527 static svn_error_t *
528 open_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 */
538 static svn_error_t *
539 add_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 */
561 static svn_error_t *
562 open_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 */
595 static svn_error_t *
596 apply_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 */
655 static svn_error_t *
656 change_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 */
672 static svn_error_t *
673 close_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 */
1011 static svn_error_t *
1012 close_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
1052 svn_error_t *
1053 svn_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
1147 svn_error_t *
1148 svn_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
1246 svn_error_t *
1247 svn_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. */
1325 static svn_error_t *
1326 is_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
1358 svn_error_t *
1359 svn_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
1424 svn_error_t *
1425 svn_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
1437 svn_error_t *
1438 svn_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
1463 svn_error_t *
1464 svn_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
1505 svn_error_t *
1506 svn_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
1565 svn_error_t *
1566 svn_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.  */
1578 static svn_error_t *
1579 uri_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
1593 error:
1594   return svn_error_createf(SVN_ERR_BAD_URL, 0,
1595                            _("URL '%s' does not begin with a scheme"),
1596                            uri);
1597 }
1598
1599 svn_error_t *
1600 svn_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 }