]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_wc/externals.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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 *url;
409   const char *repos_root_url;
410   const char *repos_uuid;
411
412   const char *record_ancestor_abspath;
413   const char *recorded_repos_relpath;
414   svn_revnum_t recorded_peg_revision;
415   svn_revnum_t recorded_revision;
416
417   /* Introducing a new file external */
418   svn_boolean_t added;
419
420   svn_wc_conflict_resolver_func2_t conflict_func;
421   void *conflict_baton;
422   svn_cancel_func_t cancel_func;
423   void *cancel_baton;
424   svn_wc_notify_func2_t notify_func;
425   void *notify_baton;
426
427   svn_revnum_t *target_revision;
428
429   /* What was there before the update */
430   svn_revnum_t original_revision;
431   const svn_checksum_t *original_checksum;
432
433   /* What we are installing now */
434   const char *new_pristine_abspath;
435   svn_checksum_t *new_sha1_checksum;
436   svn_checksum_t *new_md5_checksum;
437
438   /* List of incoming propchanges */
439   apr_array_header_t *propchanges;
440
441   /* Array of svn_prop_inherited_item_t * structures representing the
442      properties inherited by the base node at LOCAL_ABSPATH. */
443   apr_array_header_t *iprops;
444
445   /* The last change information */
446   svn_revnum_t changed_rev;
447   apr_time_t changed_date;
448   const char *changed_author;
449
450   svn_boolean_t had_props;
451
452   svn_boolean_t file_closed;
453 };
454
455 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
456 static svn_error_t *
457 set_target_revision(void *edit_baton,
458                      svn_revnum_t target_revision,
459                      apr_pool_t *pool)
460 {
461   struct edit_baton *eb = edit_baton;
462
463   *eb->target_revision = target_revision;
464
465   return SVN_NO_ERROR;
466 }
467
468 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
469 static svn_error_t *
470 open_root(void *edit_baton,
471           svn_revnum_t base_revision,
472           apr_pool_t *dir_pool,
473           void **root_baton)
474 {
475   *root_baton = edit_baton;
476   return SVN_NO_ERROR;
477 }
478
479 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
480 static svn_error_t *
481 add_file(const char *path,
482          void *parent_baton,
483          const char *copyfrom_path,
484          svn_revnum_t copyfrom_revision,
485          apr_pool_t *file_pool,
486          void **file_baton)
487 {
488   struct edit_baton *eb = parent_baton;
489   if (strcmp(path, eb->name))
490       return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
491                                _("This editor can only update '%s'"),
492                                svn_dirent_local_style(eb->local_abspath,
493                                                       file_pool));
494
495   *file_baton = eb;
496   eb->original_revision = SVN_INVALID_REVNUM;
497   eb->added = TRUE;
498
499   return SVN_NO_ERROR;
500 }
501
502 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
503 static svn_error_t *
504 open_file(const char *path,
505           void *parent_baton,
506           svn_revnum_t base_revision,
507           apr_pool_t *file_pool,
508           void **file_baton)
509 {
510   struct edit_baton *eb = parent_baton;
511   svn_node_kind_t kind;
512   if (strcmp(path, eb->name))
513       return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
514                                _("This editor can only update '%s'"),
515                                svn_dirent_local_style(eb->local_abspath,
516                                                       file_pool));
517
518   *file_baton = eb;
519   SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
520                                    NULL, NULL, NULL, &eb->changed_rev,
521                                    &eb->changed_date, &eb->changed_author,
522                                    NULL, &eb->original_checksum, NULL, NULL,
523                                    &eb->had_props, NULL, NULL,
524                                    eb->db, eb->local_abspath,
525                                    eb->pool, file_pool));
526
527   if (kind != svn_node_file)
528     return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
529                                _("Node '%s' is no existing file external"),
530                                svn_dirent_local_style(eb->local_abspath,
531                                                       file_pool));
532   return SVN_NO_ERROR;
533 }
534
535 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
536 static svn_error_t *
537 apply_textdelta(void *file_baton,
538                 const char *base_checksum_digest,
539                 apr_pool_t *pool,
540                 svn_txdelta_window_handler_t *handler,
541                 void **handler_baton)
542 {
543   struct edit_baton *eb = file_baton;
544   svn_stream_t *src_stream;
545   svn_stream_t *dest_stream;
546
547   if (eb->original_checksum)
548     {
549       if (base_checksum_digest)
550         {
551           svn_checksum_t *expected_checksum;
552           const svn_checksum_t *original_md5;
553
554           SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
555                                          base_checksum_digest, pool));
556
557           if (eb->original_checksum->kind != svn_checksum_md5)
558             SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
559                                                 eb->db, eb->wri_abspath,
560                                                 eb->original_checksum,
561                                                 pool, pool));
562           else
563             original_md5 = eb->original_checksum;
564
565           if (!svn_checksum_match(expected_checksum, original_md5))
566             return svn_error_trace(svn_checksum_mismatch_err(
567                                     expected_checksum,
568                                     original_md5,
569                                     pool,
570                                     _("Base checksum mismatch for '%s'"),
571                                     svn_dirent_local_style(eb->local_abspath,
572                                                            pool)));
573         }
574
575       SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
576                                        eb->wri_abspath, eb->original_checksum,
577                                        pool, pool));
578     }
579   else
580     src_stream = svn_stream_empty(pool);
581
582   SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath,
583                                      &eb->new_md5_checksum,
584                                      &eb->new_sha1_checksum,
585                                      eb->db, eb->wri_abspath,
586                                      eb->pool, pool));
587
588   svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
589                     handler, handler_baton);
590
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 change_file_prop(void *file_baton,
597                  const char *name,
598                  const svn_string_t *value,
599                  apr_pool_t *pool)
600 {
601   struct edit_baton *eb = file_baton;
602   svn_prop_t *propchange;
603
604   propchange = apr_array_push(eb->propchanges);
605   propchange->name = apr_pstrdup(eb->pool, name);
606   propchange->value = value ? svn_string_dup(value, eb->pool) : NULL;
607
608   return SVN_NO_ERROR;
609 }
610
611 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
612 static svn_error_t *
613 close_file(void *file_baton,
614            const char *expected_md5_digest,
615            apr_pool_t *pool)
616 {
617   struct edit_baton *eb = file_baton;
618   svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
619   svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
620   svn_boolean_t obstructed = FALSE;
621
622   eb->file_closed = TRUE; /* We bump the revision here */
623
624   /* Check the checksum, if provided */
625   if (expected_md5_digest)
626     {
627       svn_checksum_t *expected_md5_checksum;
628       const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
629
630       SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
631                                      expected_md5_digest, pool));
632
633       if (actual_md5_checksum == NULL)
634         {
635           actual_md5_checksum = eb->original_checksum;
636
637           if (actual_md5_checksum != NULL
638               && actual_md5_checksum->kind != svn_checksum_md5)
639             {
640               SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
641                                                   eb->db, eb->wri_abspath,
642                                                   actual_md5_checksum,
643                                                   pool, pool));
644             }
645         }
646
647       if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
648         return svn_checksum_mismatch_err(
649                         expected_md5_checksum,
650                         actual_md5_checksum, pool,
651                         _("Checksum mismatch for '%s'"),
652                         svn_dirent_local_style(eb->local_abspath, pool));
653     }
654
655   /* First move the file in the pristine store; this hands over the cleanup
656      behavior to the pristine store. */
657   if (eb->new_sha1_checksum)
658     {
659       SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath,
660                                           eb->new_sha1_checksum,
661                                           eb->new_md5_checksum, pool));
662
663       eb->new_pristine_abspath = NULL;
664     }
665
666   /* Merge the changes */
667   {
668     svn_skel_t *all_work_items = NULL;
669     svn_skel_t *conflict_skel = NULL;
670     svn_skel_t *work_item;
671     apr_hash_t *base_props = NULL;
672     apr_hash_t *actual_props = NULL;
673     apr_hash_t *new_pristine_props = NULL;
674     apr_hash_t *new_actual_props = NULL;
675     apr_hash_t *new_dav_props = NULL;
676     const svn_checksum_t *new_checksum = NULL;
677     const svn_checksum_t *original_checksum = NULL;
678
679     svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
680     const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url,
681                                                       eb->url, pool);
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                                     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                                     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                         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       || eb->iprops)
950     {
951       apr_hash_t *wcroot_iprops = NULL;
952
953       if (eb->iprops)
954         {
955           wcroot_iprops = apr_hash_make(pool);
956           svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
957         }
958
959       /* The node wasn't updated, so we just have to bump its revision */
960       SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
961                                                        eb->local_abspath,
962                                                        svn_depth_infinity,
963                                                        NULL, NULL, NULL,
964                                                        *eb->target_revision,
965                                                        apr_hash_make(pool),
966                                                        wcroot_iprops,
967                                                        eb->notify_func,
968                                                        eb->notify_baton,
969                                                        pool));
970     }
971
972   return SVN_NO_ERROR;
973 }
974
975 svn_error_t *
976 svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
977                                  void **edit_baton,
978                                  svn_revnum_t *target_revision,
979                                  svn_wc_context_t *wc_ctx,
980                                  const char *local_abspath,
981                                  const char *wri_abspath,
982                                  const char *url,
983                                  const char *repos_root_url,
984                                  const char *repos_uuid,
985                                  apr_array_header_t *iprops,
986                                  svn_boolean_t use_commit_times,
987                                  const char *diff3_cmd,
988                                  const apr_array_header_t *preserved_exts,
989                                  const char *record_ancestor_abspath,
990                                  const char *recorded_url,
991                                  const svn_opt_revision_t *recorded_peg_rev,
992                                  const svn_opt_revision_t *recorded_rev,
993                                  svn_wc_conflict_resolver_func2_t conflict_func,
994                                  void *conflict_baton,
995                                  svn_cancel_func_t cancel_func,
996                                  void *cancel_baton,
997                                  svn_wc_notify_func2_t notify_func,
998                                  void *notify_baton,
999                                  apr_pool_t *result_pool,
1000                                  apr_pool_t *scratch_pool)
1001 {
1002   svn_wc__db_t *db = wc_ctx->db;
1003   apr_pool_t *edit_pool = result_pool;
1004   struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1005   svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1006
1007   eb->pool = edit_pool;
1008   eb->db = db;
1009   eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1010   if (wri_abspath)
1011     eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1012   else
1013     eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1014   eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1015   eb->target_revision = target_revision;
1016
1017   eb->url = apr_pstrdup(edit_pool, url);
1018   eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1019   eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1020
1021   eb->iprops = iprops;
1022
1023   eb->use_commit_times = use_commit_times;
1024   eb->ext_patterns = preserved_exts;
1025   eb->diff3cmd = diff3_cmd;
1026
1027   eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1028   eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1029                                                      edit_pool);
1030
1031   eb->changed_rev = SVN_INVALID_REVNUM;
1032
1033   if (recorded_peg_rev->kind == svn_opt_revision_number)
1034     eb->recorded_peg_revision = recorded_peg_rev->value.number;
1035   else
1036     eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1037
1038   if (recorded_rev->kind == svn_opt_revision_number)
1039     eb->recorded_revision = recorded_rev->value.number;
1040   else
1041     eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1042
1043   eb->conflict_func = conflict_func;
1044   eb->conflict_baton = conflict_baton;
1045   eb->cancel_func = cancel_func;
1046   eb->cancel_baton = cancel_baton;
1047   eb->notify_func = notify_func;
1048   eb->notify_baton = notify_baton;
1049
1050   eb->propchanges  = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1051
1052   tree_editor->open_root = open_root;
1053   tree_editor->set_target_revision = set_target_revision;
1054   tree_editor->add_file = add_file;
1055   tree_editor->open_file = open_file;
1056   tree_editor->apply_textdelta = apply_textdelta;
1057   tree_editor->change_file_prop = change_file_prop;
1058   tree_editor->close_file = close_file;
1059   tree_editor->close_edit = close_edit;
1060
1061   return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1062                                            tree_editor, eb,
1063                                            editor, edit_baton,
1064                                            result_pool);
1065 }
1066
1067 svn_error_t *
1068 svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1069                             const char *local_abspath,
1070                             const svn_ra_reporter3_t *reporter,
1071                             void *report_baton,
1072                             svn_boolean_t restore_files,
1073                             svn_boolean_t use_commit_times,
1074                             svn_cancel_func_t cancel_func,
1075                             void *cancel_baton,
1076                             svn_wc_notify_func2_t notify_func,
1077                             void *notify_baton,
1078                             apr_pool_t *scratch_pool)
1079 {
1080   svn_wc__db_t *db = wc_ctx->db;
1081   svn_error_t *err;
1082   svn_node_kind_t kind;
1083   svn_wc__db_lock_t *lock;
1084   svn_revnum_t revision;
1085   const char *repos_root_url;
1086   const char *repos_relpath;
1087   svn_boolean_t update_root;
1088
1089   err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1090                                  &repos_relpath, &repos_root_url, NULL, NULL,
1091                                  NULL, NULL, NULL, NULL, NULL, &lock,
1092                                  NULL, NULL, &update_root,
1093                                  db, local_abspath,
1094                                  scratch_pool, scratch_pool);
1095
1096   if (err
1097       || kind == svn_node_dir
1098       || !update_root)
1099     {
1100       if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1101         return svn_error_trace(err);
1102
1103       svn_error_clear(err);
1104
1105       /* We don't know about this node, so all we have to do is tell
1106          the reporter that we don't know this node.
1107
1108          But first we have to start the report by sending some basic
1109          information for the root. */
1110
1111       SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1112                                  FALSE, NULL, scratch_pool));
1113       SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1114
1115       /* Finish the report, which causes the update editor to be
1116          driven. */
1117       SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1118
1119       return SVN_NO_ERROR;
1120     }
1121   else
1122     {
1123       if (restore_files)
1124         {
1125           svn_node_kind_t disk_kind;
1126           SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1127
1128           if (disk_kind == svn_node_none)
1129             {
1130               err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1131                                    scratch_pool);
1132
1133               if (err)
1134                 {
1135                   if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1136                     return svn_error_trace(err);
1137
1138                   svn_error_clear(err);
1139                 }
1140             }
1141         }
1142
1143       /* Report that we know the path */
1144       SVN_ERR(reporter->set_path(report_baton, "", revision,
1145                                  svn_depth_infinity, FALSE, NULL,
1146                                  scratch_pool));
1147
1148       /* For compatibility with the normal update editor report we report
1149          the target as switched.
1150
1151          ### We can probably report a parent url and unswitched later */
1152       SVN_ERR(reporter->link_path(report_baton, "",
1153                                   svn_path_url_add_component2(repos_root_url,
1154                                                               repos_relpath,
1155                                                               scratch_pool),
1156                                   revision,
1157                                   svn_depth_infinity,
1158                                   FALSE /* start_empty*/,
1159                                   lock ? lock->token : NULL,
1160                                   scratch_pool));
1161     }
1162
1163   return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1164 }
1165
1166 svn_error_t *
1167 svn_wc__read_external_info(svn_node_kind_t *external_kind,
1168                            const char **defining_abspath,
1169                            const char **defining_url,
1170                            svn_revnum_t *defining_operational_revision,
1171                            svn_revnum_t *defining_revision,
1172                            svn_wc_context_t *wc_ctx,
1173                            const char *wri_abspath,
1174                            const char *local_abspath,
1175                            svn_boolean_t ignore_enoent,
1176                            apr_pool_t *result_pool,
1177                            apr_pool_t *scratch_pool)
1178 {
1179   const char *repos_root_url;
1180   svn_wc__db_status_t status;
1181   svn_node_kind_t kind;
1182   svn_error_t *err;
1183
1184   err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1185                                  defining_url ? &repos_root_url : NULL, NULL,
1186                                  defining_url, defining_operational_revision,
1187                                  defining_revision,
1188                                  wc_ctx->db, local_abspath, wri_abspath,
1189                                  result_pool, scratch_pool);
1190
1191   if (err)
1192     {
1193       if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1194         return svn_error_trace(err);
1195
1196       svn_error_clear(err);
1197
1198       if (external_kind)
1199         *external_kind = svn_node_none;
1200
1201       if (defining_abspath)
1202         *defining_abspath = NULL;
1203
1204       if (defining_url)
1205         *defining_url = NULL;
1206
1207       if (defining_operational_revision)
1208         *defining_operational_revision = SVN_INVALID_REVNUM;
1209
1210       if (defining_revision)
1211         *defining_revision = SVN_INVALID_REVNUM;
1212
1213       return SVN_NO_ERROR;
1214     }
1215
1216   if (external_kind)
1217     {
1218       if (status != svn_wc__db_status_normal)
1219         *external_kind = svn_node_unknown;
1220       else
1221         switch(kind)
1222           {
1223             case svn_node_file:
1224             case svn_node_symlink:
1225               *external_kind = svn_node_file;
1226               break;
1227             case svn_node_dir:
1228               *external_kind = svn_node_dir;
1229               break;
1230             default:
1231               *external_kind = svn_node_none;
1232           }
1233     }
1234
1235   if (defining_url && *defining_url)
1236     *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1237                                                 result_pool);
1238
1239   return SVN_NO_ERROR;
1240 }
1241
1242 /* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1243  * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1244  * XINFO->REPOS_RELPATH.  All allocations are made in SCRATCH_POOL. */
1245 static svn_error_t *
1246 is_external_rolled_out(svn_boolean_t *is_rolled_out,
1247                        svn_wc_context_t *wc_ctx,
1248                        svn_wc__committable_external_info_t *xinfo,
1249                        apr_pool_t *scratch_pool)
1250 {
1251   const char *repos_relpath;
1252   const char *repos_root_url;
1253   svn_error_t *err;
1254
1255   *is_rolled_out = FALSE;
1256
1257   err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1258                                  &repos_root_url, NULL, NULL, NULL, NULL,
1259                                  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1260                                  wc_ctx->db, xinfo->local_abspath,
1261                                  scratch_pool, scratch_pool);
1262
1263   if (err)
1264     {
1265       if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1266         {
1267           svn_error_clear(err);
1268           return SVN_NO_ERROR;
1269         }
1270       SVN_ERR(err);
1271     }
1272
1273   *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1274                     strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1275   return SVN_NO_ERROR;
1276 }
1277
1278 svn_error_t *
1279 svn_wc__committable_externals_below(apr_array_header_t **externals,
1280                                     svn_wc_context_t *wc_ctx,
1281                                     const char *local_abspath,
1282                                     svn_depth_t depth,
1283                                     apr_pool_t *result_pool,
1284                                     apr_pool_t *scratch_pool)
1285 {
1286   apr_array_header_t *orig_externals;
1287   int i;
1288   apr_pool_t *iterpool;
1289
1290   /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1291   SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1292                                                  wc_ctx->db,
1293                                                  local_abspath,
1294                                                  depth != svn_depth_infinity,
1295                                                  result_pool, scratch_pool));
1296
1297   if (orig_externals == NULL)
1298     return SVN_NO_ERROR;
1299
1300   iterpool = svn_pool_create(scratch_pool);
1301
1302   for (i = 0; i < orig_externals->nelts; i++)
1303     {
1304       svn_boolean_t is_rolled_out;
1305
1306       svn_wc__committable_external_info_t *xinfo =
1307         APR_ARRAY_IDX(orig_externals, i,
1308                       svn_wc__committable_external_info_t *);
1309
1310       /* Discard dirs for svn_depth_files (s.a.). */
1311       if (depth == svn_depth_files
1312           && xinfo->kind == svn_node_dir)
1313         continue;
1314
1315       svn_pool_clear(iterpool);
1316
1317       /* Discard those externals that are not currently checked out. */
1318       SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1319                                      iterpool));
1320       if (! is_rolled_out)
1321         continue;
1322
1323       if (*externals == NULL)
1324         *externals = apr_array_make(
1325                                result_pool, 0,
1326                                sizeof(svn_wc__committable_external_info_t *));
1327
1328       APR_ARRAY_PUSH(*externals,
1329                      svn_wc__committable_external_info_t *) = xinfo;
1330
1331       if (depth != svn_depth_infinity)
1332         continue;
1333
1334       /* Are there any nested externals? */
1335       SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1336                                                   xinfo->local_abspath,
1337                                                   svn_depth_infinity,
1338                                                   result_pool, iterpool));
1339     }
1340
1341   return SVN_NO_ERROR;
1342 }
1343
1344 svn_error_t *
1345 svn_wc__externals_defined_below(apr_hash_t **externals,
1346                                 svn_wc_context_t *wc_ctx,
1347                                 const char *local_abspath,
1348                                 apr_pool_t *result_pool,
1349                                 apr_pool_t *scratch_pool)
1350 {
1351   return svn_error_trace(
1352             svn_wc__db_externals_defined_below(externals,
1353                                                wc_ctx->db, local_abspath,
1354                                                result_pool, scratch_pool));
1355 }
1356
1357 svn_error_t *
1358 svn_wc__external_register(svn_wc_context_t *wc_ctx,
1359                           const char *defining_abspath,
1360                           const char *local_abspath,
1361                           svn_node_kind_t kind,
1362                           const char *repos_root_url,
1363                           const char *repos_uuid,
1364                           const char *repos_relpath,
1365                           svn_revnum_t operational_revision,
1366                           svn_revnum_t revision,
1367                           apr_pool_t *scratch_pool)
1368 {
1369   SVN_ERR_ASSERT(kind == svn_node_dir);
1370   return svn_error_trace(
1371             svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1372                                         defining_abspath,
1373                                         repos_root_url,
1374                                         repos_uuid,
1375                                         defining_abspath,
1376                                         repos_relpath,
1377                                         operational_revision,
1378                                         revision,
1379                                         NULL,
1380                                         scratch_pool));
1381 }
1382
1383 svn_error_t *
1384 svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1385                         const char *wri_abspath,
1386                         const char *local_abspath,
1387                         svn_boolean_t declaration_only,
1388                         svn_cancel_func_t cancel_func,
1389                         void *cancel_baton,
1390                         apr_pool_t *scratch_pool)
1391 {
1392   svn_wc__db_status_t status;
1393   svn_node_kind_t kind;
1394
1395   SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1396                                    NULL, NULL,
1397                                    wc_ctx->db, local_abspath, wri_abspath,
1398                                    scratch_pool, scratch_pool));
1399
1400   SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1401                                      NULL, scratch_pool));
1402
1403   if (declaration_only)
1404     return SVN_NO_ERROR;
1405
1406   if (kind == svn_node_dir)
1407     SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1408                                                  TRUE, TRUE,
1409                                                  cancel_func, cancel_baton,
1410                                                  scratch_pool));
1411   else
1412     {
1413       SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1414                                      FALSE /* keep_as_working */,
1415                                      TRUE /* queue_deletes */,
1416                                      FALSE /* remove_locks */,
1417                                      SVN_INVALID_REVNUM,
1418                                      NULL, NULL, scratch_pool));
1419       SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1420                              cancel_func, cancel_baton,
1421                              scratch_pool));
1422     }
1423
1424   return SVN_NO_ERROR;
1425 }
1426
1427 svn_error_t *
1428 svn_wc__externals_gather_definitions(apr_hash_t **externals,
1429                                      apr_hash_t **depths,
1430                                      svn_wc_context_t *wc_ctx,
1431                                      const char *local_abspath,
1432                                      svn_depth_t depth,
1433                                      apr_pool_t *result_pool,
1434                                      apr_pool_t *scratch_pool)
1435 {
1436   if (depth == svn_depth_infinity
1437       || depth == svn_depth_unknown)
1438     {
1439       return svn_error_trace(
1440         svn_wc__db_externals_gather_definitions(externals, depths,
1441                                                 wc_ctx->db, local_abspath,
1442                                                 result_pool, scratch_pool));
1443     }
1444   else
1445     {
1446       const svn_string_t *value;
1447       svn_error_t *err;
1448       *externals = apr_hash_make(result_pool);
1449
1450       local_abspath = apr_pstrdup(result_pool, local_abspath);
1451
1452       err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1453                              SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1454
1455       if (err)
1456         {
1457           if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1458             return svn_error_trace(err);
1459
1460           svn_error_clear(err);
1461           value = NULL;
1462         }
1463
1464       if (value)
1465         svn_hash_sets(*externals, local_abspath, value->data);
1466
1467       if (value && depths)
1468         {
1469           svn_depth_t node_depth;
1470           *depths = apr_hash_make(result_pool);
1471
1472           SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1473                                        NULL, NULL, NULL, &node_depth, NULL,
1474                                        NULL, NULL, NULL, NULL, NULL, NULL,
1475                                        NULL, NULL, NULL, NULL, NULL, NULL,
1476                                        NULL, NULL, NULL, NULL,
1477                                        wc_ctx->db, local_abspath,
1478                                        scratch_pool, scratch_pool));
1479
1480           svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1481         }
1482
1483       return SVN_NO_ERROR;
1484     }
1485 }
1486
1487 svn_error_t *
1488 svn_wc__close_db(const char *external_abspath,
1489                  svn_wc_context_t *wc_ctx,
1490                  apr_pool_t *scratch_pool)
1491 {
1492   SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1493                                scratch_pool));
1494   return SVN_NO_ERROR;
1495 }
1496
1497 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
1498    If @a uri does not appear to be a valid URI, then @a scheme will
1499    not be updated.  */
1500 static svn_error_t *
1501 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1502 {
1503   apr_size_t i;
1504
1505   for (i = 0; uri[i] && uri[i] != ':'; ++i)
1506     if (uri[i] == '/')
1507       goto error;
1508
1509   if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1510     {
1511       *scheme = apr_pstrmemdup(pool, uri, i);
1512       return SVN_NO_ERROR;
1513     }
1514
1515 error:
1516   return svn_error_createf(SVN_ERR_BAD_URL, 0,
1517                            _("URL '%s' does not begin with a scheme"),
1518                            uri);
1519 }
1520
1521 svn_error_t *
1522 svn_wc__resolve_relative_external_url(const char **resolved_url,
1523                                       const svn_wc_external_item2_t *item,
1524                                       const char *repos_root_url,
1525                                       const char *parent_dir_url,
1526                                       apr_pool_t *result_pool,
1527                                       apr_pool_t *scratch_pool)
1528 {
1529   const char *url = item->url;
1530   apr_uri_t parent_dir_uri;
1531   apr_status_t status;
1532
1533   *resolved_url = item->url;
1534
1535   /* If the URL is already absolute, there is nothing to do. */
1536   if (svn_path_is_url(url))
1537     {
1538       /* "http://server/path" */
1539       *resolved_url = svn_uri_canonicalize(url, result_pool);
1540       return SVN_NO_ERROR;
1541     }
1542
1543   if (url[0] == '/')
1544     {
1545       /* "/path", "//path", and "///path" */
1546       int num_leading_slashes = 1;
1547       if (url[1] == '/')
1548         {
1549           num_leading_slashes++;
1550           if (url[2] == '/')
1551             num_leading_slashes++;
1552         }
1553
1554       /* "//schema-relative" and in some cases "///schema-relative".
1555          This last format is supported on file:// schema relative. */
1556       url = apr_pstrcat(scratch_pool,
1557                         apr_pstrndup(scratch_pool, url, num_leading_slashes),
1558                         svn_relpath_canonicalize(url + num_leading_slashes,
1559                                                  scratch_pool),
1560                         (char*)NULL);
1561     }
1562   else
1563     {
1564       /* "^/path" and "../path" */
1565       url = svn_relpath_canonicalize(url, scratch_pool);
1566     }
1567
1568   /* Parse the parent directory URL into its parts. */
1569   status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1570   if (status)
1571     return svn_error_createf(SVN_ERR_BAD_URL, 0,
1572                              _("Illegal parent directory URL '%s'"),
1573                              parent_dir_url);
1574
1575   /* If the parent directory URL is at the server root, then the URL
1576      may have no / after the hostname so apr_uri_parse() will leave
1577      the URL's path as NULL. */
1578   if (! parent_dir_uri.path)
1579     parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1580   parent_dir_uri.query = NULL;
1581   parent_dir_uri.fragment = NULL;
1582
1583   /* Handle URLs relative to the current directory or to the
1584      repository root.  The backpaths may only remove path elements,
1585      not the hostname.  This allows an external to refer to another
1586      repository in the same server relative to the location of this
1587      repository, say using SVNParentPath. */
1588   if ((0 == strncmp("../", url, 3)) ||
1589       (0 == strncmp("^/", url, 2)))
1590     {
1591       apr_array_header_t *base_components;
1592       apr_array_header_t *relative_components;
1593       int i;
1594
1595       /* Decompose either the parent directory's URL path or the
1596          repository root's URL path into components.  */
1597       if (0 == strncmp("../", url, 3))
1598         {
1599           base_components = svn_path_decompose(parent_dir_uri.path,
1600                                                scratch_pool);
1601           relative_components = svn_path_decompose(url, scratch_pool);
1602         }
1603       else
1604         {
1605           apr_uri_t repos_root_uri;
1606
1607           status = apr_uri_parse(scratch_pool, repos_root_url,
1608                                  &repos_root_uri);
1609           if (status)
1610             return svn_error_createf(SVN_ERR_BAD_URL, 0,
1611                                      _("Illegal repository root URL '%s'"),
1612                                      repos_root_url);
1613
1614           /* If the repository root URL is at the server root, then
1615              the URL may have no / after the hostname so
1616              apr_uri_parse() will leave the URL's path as NULL. */
1617           if (! repos_root_uri.path)
1618             repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1619
1620           base_components = svn_path_decompose(repos_root_uri.path,
1621                                                scratch_pool);
1622           relative_components = svn_path_decompose(url + 2, scratch_pool);
1623         }
1624
1625       for (i = 0; i < relative_components->nelts; ++i)
1626         {
1627           const char *component = APR_ARRAY_IDX(relative_components,
1628                                                 i,
1629                                                 const char *);
1630           if (0 == strcmp("..", component))
1631             {
1632               /* Constructing the final absolute URL together with
1633                  apr_uri_unparse() requires that the path be absolute,
1634                  so only pop a component if the component being popped
1635                  is not the component for the root directory. */
1636               if (base_components->nelts > 1)
1637                 apr_array_pop(base_components);
1638             }
1639           else
1640             APR_ARRAY_PUSH(base_components, const char *) = component;
1641         }
1642
1643       parent_dir_uri.path = (char *)svn_path_compose(base_components,
1644                                                      scratch_pool);
1645       *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1646                                                            &parent_dir_uri, 0),
1647                                        result_pool);
1648       return SVN_NO_ERROR;
1649     }
1650
1651   /* The remaining URLs are relative to either the scheme or server root
1652      and can only refer to locations inside that scope, so backpaths are
1653      not allowed. */
1654   if (svn_path_is_backpath_present(url))
1655     return svn_error_createf(SVN_ERR_BAD_URL, 0,
1656                              _("The external relative URL '%s' cannot have "
1657                                "backpaths, i.e. '..'"),
1658                              item->url);
1659
1660   /* Relative to the scheme: Build a new URL from the parts we know. */
1661   if (0 == strncmp("//", url, 2))
1662     {
1663       const char *scheme;
1664
1665       SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1666       *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1667                                                        ":", url, (char *)NULL),
1668                                            result_pool);
1669       return SVN_NO_ERROR;
1670     }
1671
1672   /* Relative to the server root: Just replace the path portion of the
1673      parent's URL. */
1674   if (url[0] == '/')
1675     {
1676       parent_dir_uri.path = (char *)url;
1677       *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1678                                                            &parent_dir_uri, 0),
1679                                            result_pool);
1680       return SVN_NO_ERROR;
1681     }
1682
1683   return svn_error_createf(SVN_ERR_BAD_URL, 0,
1684                            _("Unrecognized format for the relative external "
1685                              "URL '%s'"),
1686                            item->url);
1687 }