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