2 * externals.c : routines dealing with (file) externals in the working copy
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
29 #include <apr_pools.h>
31 #include <apr_tables.h>
32 #include <apr_general.h>
35 #include "svn_dirent_uri.h"
37 #include "svn_error.h"
40 #include "svn_pools.h"
41 #include "svn_props.h"
42 #include "svn_string.h"
44 #include "svn_types.h"
47 #include "private/svn_skel.h"
48 #include "private/svn_subr_private.h"
51 #include "adm_files.h"
53 #include "translate.h"
54 #include "workqueue.h"
55 #include "conflicts.h"
57 #include "svn_private_config.h"
67 * in the LINE_PARTS array and update the revision field in ITEM with
68 * the revision if the revision is found. Set REV_IDX to the index in
69 * LINE_PARTS where the revision specification starts. Remove from
70 * LINE_PARTS the element(s) that specify the revision.
71 * Set REV_STR to the element that specifies the revision.
72 * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
75 * If this function returns successfully, then LINE_PARTS will have
76 * only two elements in it.
79 find_and_remove_externals_revision(int *rev_idx,
81 const char **line_parts,
83 svn_wc_external_item2_t *item,
84 const char *parent_directory_display,
90 for (i = 0; i < 2; ++i)
92 const char *token = line_parts[i];
94 if (token[0] == '-' && token[1] == 'r')
96 svn_opt_revision_t end_revision = { svn_opt_revision_unspecified };
97 const char *digits_ptr;
103 if (token[2] == '\0')
105 /* There must be a total of four elements in the line if
107 if (num_line_parts != 4)
111 digits_ptr = line_parts[i+1];
115 /* There must be a total of three elements in the line
117 if (num_line_parts != 3)
121 digits_ptr = token+2;
124 if (svn_opt_parse_revision(&item->revision,
126 digits_ptr, pool) != 0)
128 /* We want a single revision, not a range. */
129 if (end_revision.kind != svn_opt_revision_unspecified)
131 /* Allow only numbers and dates, not keywords. */
132 if (item->revision.kind != svn_opt_revision_number
133 && item->revision.kind != svn_opt_revision_date)
136 /* Shift any line elements past the revision specification
137 down over the revision specification. */
138 for (j = i; j < num_line_parts-shift_count; ++j)
139 line_parts[j] = line_parts[j+shift_count];
140 line_parts[num_line_parts-shift_count] = NULL;
142 *rev_str = apr_psprintf(pool, "-r%s", digits_ptr);
144 /* Found the revision, so leave the function immediately, do
145 * not continue looking for additional revisions. */
150 /* No revision was found, so there must be exactly two items in the
152 if (num_line_parts == 2)
156 return svn_error_createf
157 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
158 _("Error parsing %s property on '%s': '%s'"),
160 parent_directory_display,
165 svn_wc__parse_externals_description(apr_array_header_t **externals_p,
166 apr_array_header_t **parser_infos_p,
167 const char *defining_directory,
169 svn_boolean_t canonicalize_url,
173 apr_array_header_t *externals = NULL;
174 apr_array_header_t *parser_infos = NULL;
175 apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
176 const char *defining_directory_display = svn_path_is_url(defining_directory) ?
177 defining_directory : svn_dirent_local_style(defining_directory, pool);
179 /* If an error occurs halfway through parsing, *externals_p should stay
180 * untouched. So, store the list in a local var first. */
182 externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
186 apr_array_make(pool, 1, sizeof(svn_wc__externals_parser_info_t *));
188 for (i = 0; i < lines->nelts; i++)
190 const char *line = APR_ARRAY_IDX(lines, i, const char *);
194 svn_wc_external_item2_t *item;
197 svn_boolean_t token0_is_url;
198 svn_boolean_t token1_is_url;
199 svn_wc__externals_parser_info_t *info = NULL;
201 /* Index into line_parts where the revision specification
204 const char *rev_str = NULL;
206 if ((! line) || (line[0] == '#'))
211 status = apr_tokenize_to_argv(line, &line_parts, pool);
213 return svn_error_wrap_apr(status,
214 _("Can't split line into components: '%s'"),
216 /* Count the number of tokens. */
217 for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
220 SVN_ERR(svn_wc_external_item2_create(&item, pool));
221 item->revision.kind = svn_opt_revision_unspecified;
222 item->peg_revision.kind = svn_opt_revision_unspecified;
225 info = apr_pcalloc(pool, sizeof(*info));
228 * There are six different formats of externals:
237 * The last three allow peg revisions in the URL.
239 * With relative URLs and no '-rN' or '-r N', there is no way to
240 * distinguish between 'DIR URL' and 'URL DIR' when URL is a
241 * relative URL like /svn/repos/trunk, so this case is taken as
244 if (num_line_parts < 2 || num_line_parts > 4)
245 return svn_error_createf
246 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
247 _("Error parsing %s property on '%s': '%s'"),
249 defining_directory_display,
252 /* To make it easy to check for the forms, find and remove -r N
253 or -rN from the line item array. If it is found, rev_idx
254 contains the index into line_parts where '-r' was found and
255 set item->revision to the parsed revision. */
256 /* ### ugh. stupid cast. */
257 SVN_ERR(find_and_remove_externals_revision(&rev_idx,
259 (const char **)line_parts,
260 num_line_parts, item,
261 defining_directory_display,
264 token0 = line_parts[0];
265 token1 = line_parts[1];
267 token0_is_url = svn_path_is_url(token0);
268 token1_is_url = svn_path_is_url(token1);
270 if (token0_is_url && token1_is_url)
271 return svn_error_createf
272 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
273 _("Invalid %s property on '%s': "
274 "cannot use two absolute URLs ('%s' and '%s') in an external; "
275 "one must be a path where an absolute or relative URL is "
277 SVN_PROP_EXTERNALS, defining_directory_display, token0, token1);
279 if (0 == rev_idx && token1_is_url)
280 return svn_error_createf
281 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
282 _("Invalid %s property on '%s': "
283 "cannot use a URL '%s' as the target directory for an external "
285 SVN_PROP_EXTERNALS, defining_directory_display, token1);
287 if (1 == rev_idx && token0_is_url)
288 return svn_error_createf
289 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
290 _("Invalid %s property on '%s': "
291 "cannot use a URL '%s' as the target directory for an external "
293 SVN_PROP_EXTERNALS, defining_directory_display, token0);
295 /* The appearance of -r N or -rN forces the type of external.
296 If -r is at the beginning of the line or the first token is
297 an absolute URL or if the second token is not an absolute
298 URL, then the URL supports peg revisions. */
300 (-1 == rev_idx && (token0_is_url || ! token1_is_url)))
302 /* The URL is passed to svn_opt_parse_path in
303 uncanonicalized form so that the scheme relative URL
304 //hostname/foo is not collapsed to a server root relative
305 URL /hostname/foo. */
306 SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
308 item->target_dir = token1;
312 info->format = svn_wc__external_description_format_2;
315 info->rev_str = apr_pstrdup(pool, rev_str);
317 if (item->peg_revision.kind != svn_opt_revision_unspecified)
318 info->peg_rev_str = strrchr(token0, '@');
323 item->target_dir = token0;
325 item->peg_revision = item->revision;
329 info->format = svn_wc__external_description_format_1;
333 info->rev_str = apr_pstrdup(pool, rev_str);
334 info->peg_rev_str = info->rev_str;
339 SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
340 &item->revision, TRUE, FALSE,
343 item->target_dir = svn_dirent_internal_style(item->target_dir, pool);
345 if (item->target_dir[0] == '\0'
346 || svn_dirent_is_absolute(item->target_dir)
347 || svn_path_is_backpath_present(item->target_dir)
348 || !svn_dirent_skip_ancestor("dummy",
349 svn_dirent_join("dummy",
352 return svn_error_createf
353 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
354 _("Invalid %s property on '%s': "
355 "target '%s' is an absolute path or involves '..'"),
357 defining_directory_display,
360 if (canonicalize_url)
362 /* Uh... this is stupid. But it's consistent with what our
363 code did before we split up the relpath/dirent/uri APIs.
364 Still, given this, it's no wonder that our own libraries
365 don't ask this function to canonicalize the results. */
366 if (svn_path_is_url(item->url))
367 item->url = svn_uri_canonicalize(item->url, pool);
369 item->url = svn_dirent_canonicalize(item->url, pool);
373 APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item;
375 APR_ARRAY_PUSH(parser_infos, svn_wc__externals_parser_info_t *) = info;
379 *externals_p = externals;
381 *parser_infos_p = parser_infos;
387 svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
388 const char *defining_directory,
390 svn_boolean_t canonicalize_url,
393 return svn_error_trace(svn_wc__parse_externals_description(externals_p,
402 svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
403 apr_array_header_t *externals,
405 apr_pool_t *scratch_pool)
411 apr_hash_t *targets = apr_hash_make(scratch_pool);
412 apr_hash_t *targets2 = NULL;
413 *duplicate_targets = NULL;
415 for (i = 0; i < externals->nelts; i++)
417 target = APR_ARRAY_IDX(externals, i,
418 svn_wc_external_item2_t*)->target_dir;
419 len = apr_hash_count(targets);
420 svn_hash_sets(targets, target, "");
421 if (len == apr_hash_count(targets))
423 /* Hashtable length is unchanged. This must be a duplicate. */
425 /* Collapse multiple duplicates of the same target by using a second
428 targets2 = apr_hash_make(scratch_pool);
429 len2 = apr_hash_count(targets2);
430 svn_hash_sets(targets2, target, "");
431 if (len2 < apr_hash_count(targets2))
433 /* The second hash list just got bigger, i.e. this target has
434 * not been counted as duplicate before. */
435 if (! *duplicate_targets)
437 *duplicate_targets = apr_array_make(
438 pool, 1, sizeof(svn_wc_external_item2_t*));
440 APR_ARRAY_PUSH((*duplicate_targets), const char *) = target;
442 /* Else, this same target has already been recorded as a duplicate,
443 * don't count it again. */
454 /* We explicitly use wri_abspath and local_abspath here, because we
455 might want to install file externals in an obstructing working copy */
456 const char *wri_abspath; /* The working defining the file external */
457 const char *local_abspath; /* The file external itself */
458 const char *name; /* The basename of the file external itself */
460 /* Information from the caller */
461 svn_boolean_t use_commit_times;
462 const apr_array_header_t *ext_patterns;
463 const char *diff3cmd;
465 const char *repos_root_url;
466 const char *repos_uuid;
467 const char *old_repos_relpath;
468 const char *new_repos_relpath;
470 const char *record_ancestor_abspath;
471 const char *recorded_repos_relpath;
472 svn_revnum_t recorded_peg_revision;
473 svn_revnum_t recorded_revision;
475 /* Introducing a new file external */
478 svn_wc_conflict_resolver_func2_t conflict_func;
479 void *conflict_baton;
480 svn_cancel_func_t cancel_func;
482 svn_wc_notify_func2_t notify_func;
485 svn_revnum_t *target_revision;
487 /* What was there before the update */
488 svn_revnum_t original_revision;
489 const svn_checksum_t *original_checksum;
491 /* What we are installing now */
492 svn_wc__db_install_data_t *install_data;
493 svn_checksum_t *new_sha1_checksum;
494 svn_checksum_t *new_md5_checksum;
496 /* List of incoming propchanges */
497 apr_array_header_t *propchanges;
499 /* Array of svn_prop_inherited_item_t * structures representing the
500 properties inherited by the base node at LOCAL_ABSPATH. */
501 apr_array_header_t *iprops;
503 /* The last change information */
504 svn_revnum_t changed_rev;
505 apr_time_t changed_date;
506 const char *changed_author;
508 svn_boolean_t had_props;
510 svn_boolean_t file_closed;
513 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
515 set_target_revision(void *edit_baton,
516 svn_revnum_t target_revision,
519 struct edit_baton *eb = edit_baton;
521 *eb->target_revision = target_revision;
526 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
528 open_root(void *edit_baton,
529 svn_revnum_t base_revision,
530 apr_pool_t *dir_pool,
533 *root_baton = edit_baton;
537 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
539 add_file(const char *path,
541 const char *copyfrom_path,
542 svn_revnum_t copyfrom_revision,
543 apr_pool_t *file_pool,
546 struct edit_baton *eb = parent_baton;
547 if (strcmp(path, eb->name))
548 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
549 _("This editor can only update '%s'"),
550 svn_dirent_local_style(eb->local_abspath,
554 eb->original_revision = SVN_INVALID_REVNUM;
560 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
562 open_file(const char *path,
564 svn_revnum_t base_revision,
565 apr_pool_t *file_pool,
568 struct edit_baton *eb = parent_baton;
569 svn_node_kind_t kind;
570 if (strcmp(path, eb->name))
571 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
572 _("This editor can only update '%s'"),
573 svn_dirent_local_style(eb->local_abspath,
577 SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
578 &eb->old_repos_relpath, NULL, NULL,
580 &eb->changed_date, &eb->changed_author,
581 NULL, &eb->original_checksum, NULL, NULL,
582 &eb->had_props, NULL, NULL,
583 eb->db, eb->local_abspath,
584 eb->pool, file_pool));
586 if (kind != svn_node_file)
587 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
588 _("Node '%s' is no existing file external"),
589 svn_dirent_local_style(eb->local_abspath,
594 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
596 apply_textdelta(void *file_baton,
597 const char *base_checksum_digest,
599 svn_txdelta_window_handler_t *handler,
600 void **handler_baton)
602 struct edit_baton *eb = file_baton;
603 svn_stream_t *src_stream;
604 svn_stream_t *dest_stream;
606 if (eb->original_checksum)
608 if (base_checksum_digest)
610 svn_checksum_t *expected_checksum;
611 const svn_checksum_t *original_md5;
613 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
614 base_checksum_digest, pool));
616 if (eb->original_checksum->kind != svn_checksum_md5)
617 SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
618 eb->db, eb->wri_abspath,
619 eb->original_checksum,
622 original_md5 = eb->original_checksum;
624 if (!svn_checksum_match(expected_checksum, original_md5))
625 return svn_error_trace(svn_checksum_mismatch_err(
629 _("Base checksum mismatch for '%s'"),
630 svn_dirent_local_style(eb->local_abspath,
634 SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
635 eb->wri_abspath, eb->original_checksum,
639 src_stream = svn_stream_empty(pool);
641 SVN_ERR(svn_wc__db_pristine_prepare_install(&dest_stream,
643 &eb->new_sha1_checksum,
644 &eb->new_md5_checksum,
645 eb->db, eb->wri_abspath,
648 svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
649 handler, handler_baton);
654 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
656 change_file_prop(void *file_baton,
658 const svn_string_t *value,
661 struct edit_baton *eb = file_baton;
662 svn_prop_t *propchange;
664 propchange = apr_array_push(eb->propchanges);
665 propchange->name = apr_pstrdup(eb->pool, name);
666 propchange->value = svn_string_dup(value, eb->pool);
671 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
673 close_file(void *file_baton,
674 const char *expected_md5_digest,
677 struct edit_baton *eb = file_baton;
678 svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
679 svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
680 svn_boolean_t obstructed = FALSE;
682 eb->file_closed = TRUE; /* We bump the revision here */
684 /* Check the checksum, if provided */
685 if (expected_md5_digest)
687 svn_checksum_t *expected_md5_checksum;
688 const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
690 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
691 expected_md5_digest, pool));
693 if (actual_md5_checksum == NULL)
695 actual_md5_checksum = eb->original_checksum;
697 if (actual_md5_checksum != NULL
698 && actual_md5_checksum->kind != svn_checksum_md5)
700 SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
701 eb->db, eb->wri_abspath,
707 if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
708 return svn_checksum_mismatch_err(
709 expected_md5_checksum,
710 actual_md5_checksum, pool,
711 _("Checksum mismatch for '%s'"),
712 svn_dirent_local_style(eb->local_abspath, pool));
715 /* First move the file in the pristine store; this hands over the cleanup
716 behavior to the pristine store. */
717 if (eb->new_sha1_checksum)
719 SVN_ERR(svn_wc__db_pristine_install(eb->install_data,
720 eb->new_sha1_checksum,
721 eb->new_md5_checksum, pool));
723 eb->install_data = NULL;
726 /* Merge the changes */
728 svn_skel_t *all_work_items = NULL;
729 svn_skel_t *conflict_skel = NULL;
730 svn_skel_t *work_item;
731 apr_hash_t *base_props = NULL;
732 apr_hash_t *actual_props = NULL;
733 apr_hash_t *new_pristine_props = NULL;
734 apr_hash_t *new_actual_props = NULL;
735 apr_hash_t *new_dav_props = NULL;
736 const svn_checksum_t *new_checksum = NULL;
737 const svn_checksum_t *original_checksum = NULL;
739 svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
743 new_checksum = eb->original_checksum;
746 SVN_ERR(svn_wc__db_base_get_props(
747 &base_props, eb->db, eb->local_abspath, pool, pool));
749 SVN_ERR(svn_wc__db_read_props(
750 &actual_props, eb->db, eb->local_abspath, pool, pool));
754 base_props = apr_hash_make(pool);
757 actual_props = apr_hash_make(pool);
759 if (eb->new_sha1_checksum)
760 new_checksum = eb->new_sha1_checksum;
762 /* Merge the properties */
764 apr_array_header_t *entry_prop_changes;
765 apr_array_header_t *dav_prop_changes;
766 apr_array_header_t *regular_prop_changes;
769 SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
770 &dav_prop_changes, ®ular_prop_changes,
773 /* Read the entry-prop changes to update the last-changed info. */
774 for (i = 0; i < entry_prop_changes->nelts; i++)
776 const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
780 continue; /* authz or something */
782 if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
783 eb->changed_author = apr_pstrdup(pool, prop->value->data);
784 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
787 SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
788 eb->changed_rev = (svn_revnum_t)rev;
790 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
791 SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
795 /* Store the DAV-prop (aka WC-prop) changes. (This treats a list
796 * of changes as a list of new props, but we only use this when
797 * adding a new file and it's equivalent in that case.) */
798 if (dav_prop_changes->nelts > 0)
799 new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
801 /* Merge the regular prop changes. */
802 if (regular_prop_changes->nelts > 0)
804 new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
806 SVN_ERR(svn_wc__merge_props(&conflict_skel,
809 eb->db, eb->local_abspath,
810 NULL /* server_baseprops*/,
813 regular_prop_changes,
818 new_pristine_props = base_props;
819 new_actual_props = actual_props;
824 if (eb->new_sha1_checksum)
826 svn_node_kind_t disk_kind;
827 svn_boolean_t install_pristine = FALSE;
829 SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
831 if (disk_kind == svn_node_none)
833 /* Just install the file */
834 install_pristine = TRUE;
835 content_state = svn_wc_notify_state_changed;
837 else if (disk_kind != svn_node_file
838 || (eb->added && disk_kind == svn_node_file))
840 /* The node is obstructed; we just change the DB */
842 content_state = svn_wc_notify_state_unchanged;
846 svn_boolean_t is_mod;
847 SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
848 eb->db, eb->local_abspath,
853 install_pristine = TRUE;
854 content_state = svn_wc_notify_state_changed;
858 svn_boolean_t found_text_conflict;
860 /* Ok, we have to do some work to merge a local change */
861 SVN_ERR(svn_wc__perform_file_merge(&work_item,
863 &found_text_conflict,
871 eb->original_revision,
872 *eb->target_revision,
879 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
882 if (found_text_conflict)
883 content_state = svn_wc_notify_state_conflicted;
885 content_state = svn_wc_notify_state_merged;
888 if (install_pristine)
890 SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
893 eb->use_commit_times, TRUE,
896 all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
901 content_state = svn_wc_notify_state_unchanged;
902 /* ### Retranslate on magic property changes, etc. */
905 /* Generate a conflict description, if needed */
908 SVN_ERR(svn_wc__conflict_skel_set_op_switch(
910 svn_wc_conflict_version_create2(
913 eb->old_repos_relpath,
914 eb->original_revision,
917 svn_wc_conflict_version_create2(
920 eb->new_repos_relpath,
921 *eb->target_revision,
925 SVN_ERR(svn_wc__conflict_create_markers(&work_item,
926 eb->db, eb->local_abspath,
929 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
933 /* Install the file in the DB */
934 SVN_ERR(svn_wc__db_external_add_file(
938 eb->new_repos_relpath,
941 *eb->target_revision,
949 eb->record_ancestor_abspath,
950 eb->recorded_repos_relpath,
951 eb->recorded_peg_revision,
952 eb->recorded_revision,
953 TRUE, new_actual_props,
954 FALSE /* keep_recorded_info */,
959 /* close_edit may also update iprops for switched files, catching
960 those for which close_file is never called (e.g. an update of a
961 file external with no changes). So as a minor optimization we
962 clear the iprops so as not to set them again in close_edit. */
965 /* Run the work queue to complete the installation */
966 SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
967 eb->cancel_func, eb->cancel_baton, pool));
969 if (conflict_skel && eb->conflict_func)
970 SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db,
974 NULL /* merge_options */,
985 svn_wc_notify_action_t action;
986 svn_wc_notify_t *notify;
989 action = obstructed ? svn_wc_notify_update_shadowed_update
990 : svn_wc_notify_update_update;
992 action = obstructed ? svn_wc_notify_update_shadowed_add
993 : svn_wc_notify_update_add;
995 notify = svn_wc_create_notify(eb->local_abspath, action, pool);
996 notify->kind = svn_node_file;
998 notify->revision = *eb->target_revision;
999 notify->prop_state = prop_state;
1000 notify->content_state = content_state;
1002 notify->old_revision = eb->original_revision;
1004 eb->notify_func(eb->notify_baton, notify, pool);
1007 return SVN_NO_ERROR;
1010 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
1011 static svn_error_t *
1012 close_edit(void *edit_baton,
1015 struct edit_baton *eb = edit_baton;
1017 if (!eb->file_closed)
1019 apr_hash_t *wcroot_iprops = NULL;
1020 /* The file wasn't updated, but its url or revision might have...
1021 e.g. switch between branches for relative externals.
1023 Just bump the information as that is just as expensive as
1024 investigating when we should and shouldn't update it...
1025 and avoid hard to debug edge cases */
1029 wcroot_iprops = apr_hash_make(pool);
1030 svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
1033 SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
1036 eb->new_repos_relpath,
1039 *eb->target_revision,
1041 /* exclude_relpaths */,
1043 TRUE /* empty update */,
1049 return SVN_NO_ERROR;
1053 svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
1055 svn_revnum_t *target_revision,
1056 svn_wc_context_t *wc_ctx,
1057 const char *local_abspath,
1058 const char *wri_abspath,
1060 const char *repos_root_url,
1061 const char *repos_uuid,
1062 apr_array_header_t *iprops,
1063 svn_boolean_t use_commit_times,
1064 const char *diff3_cmd,
1065 const apr_array_header_t *preserved_exts,
1066 const char *record_ancestor_abspath,
1067 const char *recorded_url,
1068 const svn_opt_revision_t *recorded_peg_rev,
1069 const svn_opt_revision_t *recorded_rev,
1070 svn_wc_conflict_resolver_func2_t conflict_func,
1071 void *conflict_baton,
1072 svn_cancel_func_t cancel_func,
1074 svn_wc_notify_func2_t notify_func,
1076 apr_pool_t *result_pool,
1077 apr_pool_t *scratch_pool)
1079 svn_wc__db_t *db = wc_ctx->db;
1080 apr_pool_t *edit_pool = result_pool;
1081 struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1082 svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1084 eb->pool = edit_pool;
1086 eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1088 eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1090 eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1091 eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1092 eb->target_revision = target_revision;
1094 eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1095 eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1096 eb->new_repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, url, edit_pool);
1097 eb->old_repos_relpath = eb->new_repos_relpath;
1099 eb->original_revision = SVN_INVALID_REVNUM;
1101 eb->iprops = iprops;
1103 eb->use_commit_times = use_commit_times;
1104 eb->ext_patterns = preserved_exts;
1105 eb->diff3cmd = diff3_cmd;
1107 eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1108 eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1111 eb->changed_rev = SVN_INVALID_REVNUM;
1113 if (recorded_peg_rev->kind == svn_opt_revision_number)
1114 eb->recorded_peg_revision = recorded_peg_rev->value.number;
1116 eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1118 if (recorded_rev->kind == svn_opt_revision_number)
1119 eb->recorded_revision = recorded_rev->value.number;
1121 eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1123 eb->conflict_func = conflict_func;
1124 eb->conflict_baton = conflict_baton;
1125 eb->cancel_func = cancel_func;
1126 eb->cancel_baton = cancel_baton;
1127 eb->notify_func = notify_func;
1128 eb->notify_baton = notify_baton;
1130 eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1132 tree_editor->open_root = open_root;
1133 tree_editor->set_target_revision = set_target_revision;
1134 tree_editor->add_file = add_file;
1135 tree_editor->open_file = open_file;
1136 tree_editor->apply_textdelta = apply_textdelta;
1137 tree_editor->change_file_prop = change_file_prop;
1138 tree_editor->close_file = close_file;
1139 tree_editor->close_edit = close_edit;
1141 return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1148 svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1149 const char *local_abspath,
1150 const svn_ra_reporter3_t *reporter,
1152 svn_boolean_t restore_files,
1153 svn_boolean_t use_commit_times,
1154 svn_cancel_func_t cancel_func,
1156 svn_wc_notify_func2_t notify_func,
1158 apr_pool_t *scratch_pool)
1160 svn_wc__db_t *db = wc_ctx->db;
1162 svn_node_kind_t kind;
1163 svn_wc__db_lock_t *lock;
1164 svn_revnum_t revision;
1165 const char *repos_root_url;
1166 const char *repos_relpath;
1167 svn_boolean_t update_root;
1169 err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1170 &repos_relpath, &repos_root_url, NULL, NULL,
1171 NULL, NULL, NULL, NULL, NULL, &lock,
1172 NULL, NULL, &update_root,
1174 scratch_pool, scratch_pool);
1177 || kind == svn_node_dir
1180 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1181 return svn_error_trace(err);
1183 svn_error_clear(err);
1185 /* We don't know about this node, so all we have to do is tell
1186 the reporter that we don't know this node.
1188 But first we have to start the report by sending some basic
1189 information for the root. */
1191 SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1192 FALSE, NULL, scratch_pool));
1193 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1195 /* Finish the report, which causes the update editor to be
1197 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1199 return SVN_NO_ERROR;
1205 svn_node_kind_t disk_kind;
1206 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1208 if (disk_kind == svn_node_none)
1210 err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1215 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1216 return svn_error_trace(err);
1218 svn_error_clear(err);
1223 /* Report that we know the path */
1224 SVN_ERR(reporter->set_path(report_baton, "", revision,
1225 svn_depth_infinity, FALSE, NULL,
1228 /* For compatibility with the normal update editor report we report
1229 the target as switched.
1231 ### We can probably report a parent url and unswitched later */
1232 SVN_ERR(reporter->link_path(report_baton, "",
1233 svn_path_url_add_component2(repos_root_url,
1238 FALSE /* start_empty*/,
1239 lock ? lock->token : NULL,
1243 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1247 svn_wc__read_external_info(svn_node_kind_t *external_kind,
1248 const char **defining_abspath,
1249 const char **defining_url,
1250 svn_revnum_t *defining_operational_revision,
1251 svn_revnum_t *defining_revision,
1252 svn_wc_context_t *wc_ctx,
1253 const char *wri_abspath,
1254 const char *local_abspath,
1255 svn_boolean_t ignore_enoent,
1256 apr_pool_t *result_pool,
1257 apr_pool_t *scratch_pool)
1259 const char *repos_root_url;
1260 svn_wc__db_status_t status;
1261 svn_node_kind_t kind;
1264 err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1265 defining_url ? &repos_root_url : NULL, NULL,
1266 defining_url, defining_operational_revision,
1268 wc_ctx->db, local_abspath, wri_abspath,
1269 result_pool, scratch_pool);
1273 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1274 return svn_error_trace(err);
1276 svn_error_clear(err);
1279 *external_kind = svn_node_none;
1281 if (defining_abspath)
1282 *defining_abspath = NULL;
1285 *defining_url = NULL;
1287 if (defining_operational_revision)
1288 *defining_operational_revision = SVN_INVALID_REVNUM;
1290 if (defining_revision)
1291 *defining_revision = SVN_INVALID_REVNUM;
1293 return SVN_NO_ERROR;
1298 if (status != svn_wc__db_status_normal)
1299 *external_kind = svn_node_unknown;
1304 case svn_node_symlink:
1305 *external_kind = svn_node_file;
1308 *external_kind = svn_node_dir;
1311 *external_kind = svn_node_none;
1315 if (defining_url && *defining_url)
1316 *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1319 return SVN_NO_ERROR;
1322 /* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1323 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1324 * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
1325 static svn_error_t *
1326 is_external_rolled_out(svn_boolean_t *is_rolled_out,
1327 svn_wc_context_t *wc_ctx,
1328 svn_wc__committable_external_info_t *xinfo,
1329 apr_pool_t *scratch_pool)
1331 const char *repos_relpath;
1332 const char *repos_root_url;
1335 *is_rolled_out = FALSE;
1337 err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1338 &repos_root_url, NULL, NULL, NULL, NULL,
1339 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1340 wc_ctx->db, xinfo->local_abspath,
1341 scratch_pool, scratch_pool);
1345 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1347 svn_error_clear(err);
1348 return SVN_NO_ERROR;
1353 *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1354 strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1355 return SVN_NO_ERROR;
1359 svn_wc__committable_externals_below(apr_array_header_t **externals,
1360 svn_wc_context_t *wc_ctx,
1361 const char *local_abspath,
1363 apr_pool_t *result_pool,
1364 apr_pool_t *scratch_pool)
1366 apr_array_header_t *orig_externals;
1368 apr_pool_t *iterpool;
1370 /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1371 SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1374 depth != svn_depth_infinity,
1375 result_pool, scratch_pool));
1377 if (orig_externals == NULL)
1378 return SVN_NO_ERROR;
1380 iterpool = svn_pool_create(scratch_pool);
1382 for (i = 0; i < orig_externals->nelts; i++)
1384 svn_boolean_t is_rolled_out;
1386 svn_wc__committable_external_info_t *xinfo =
1387 APR_ARRAY_IDX(orig_externals, i,
1388 svn_wc__committable_external_info_t *);
1390 /* Discard dirs for svn_depth_files (s.a.). */
1391 if (depth == svn_depth_files
1392 && xinfo->kind == svn_node_dir)
1395 svn_pool_clear(iterpool);
1397 /* Discard those externals that are not currently checked out. */
1398 SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1400 if (! is_rolled_out)
1403 if (*externals == NULL)
1404 *externals = apr_array_make(
1406 sizeof(svn_wc__committable_external_info_t *));
1408 APR_ARRAY_PUSH(*externals,
1409 svn_wc__committable_external_info_t *) = xinfo;
1411 if (depth != svn_depth_infinity)
1414 /* Are there any nested externals? */
1415 SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1416 xinfo->local_abspath,
1418 result_pool, iterpool));
1421 return SVN_NO_ERROR;
1425 svn_wc__externals_defined_below(apr_hash_t **externals,
1426 svn_wc_context_t *wc_ctx,
1427 const char *local_abspath,
1428 apr_pool_t *result_pool,
1429 apr_pool_t *scratch_pool)
1431 return svn_error_trace(
1432 svn_wc__db_externals_defined_below(externals,
1433 wc_ctx->db, local_abspath,
1434 result_pool, scratch_pool));
1438 svn_wc__external_register(svn_wc_context_t *wc_ctx,
1439 const char *defining_abspath,
1440 const char *local_abspath,
1441 svn_node_kind_t kind,
1442 const char *repos_root_url,
1443 const char *repos_uuid,
1444 const char *repos_relpath,
1445 svn_revnum_t operational_revision,
1446 svn_revnum_t revision,
1447 apr_pool_t *scratch_pool)
1449 SVN_ERR_ASSERT(kind == svn_node_dir);
1450 return svn_error_trace(
1451 svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1457 operational_revision,
1464 svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1465 const char *wri_abspath,
1466 const char *local_abspath,
1467 svn_boolean_t declaration_only,
1468 svn_cancel_func_t cancel_func,
1470 apr_pool_t *scratch_pool)
1472 svn_wc__db_status_t status;
1473 svn_node_kind_t kind;
1475 SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1477 wc_ctx->db, local_abspath, wri_abspath,
1478 scratch_pool, scratch_pool));
1480 SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1481 NULL, scratch_pool));
1483 if (declaration_only)
1484 return SVN_NO_ERROR;
1486 if (kind == svn_node_dir)
1487 SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1489 cancel_func, cancel_baton,
1493 SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1496 NULL, NULL, scratch_pool));
1497 SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1498 cancel_func, cancel_baton,
1502 return SVN_NO_ERROR;
1506 svn_wc__externals_gather_definitions(apr_hash_t **externals,
1507 apr_hash_t **depths,
1508 svn_wc_context_t *wc_ctx,
1509 const char *local_abspath,
1511 apr_pool_t *result_pool,
1512 apr_pool_t *scratch_pool)
1514 if (depth == svn_depth_infinity
1515 || depth == svn_depth_unknown)
1517 return svn_error_trace(
1518 svn_wc__db_externals_gather_definitions(externals, depths,
1519 wc_ctx->db, local_abspath,
1520 result_pool, scratch_pool));
1524 const svn_string_t *value;
1526 *externals = apr_hash_make(result_pool);
1528 local_abspath = apr_pstrdup(result_pool, local_abspath);
1530 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1531 SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1535 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1536 return svn_error_trace(err);
1538 svn_error_clear(err);
1543 svn_hash_sets(*externals, local_abspath, value->data);
1545 if (value && depths)
1547 svn_depth_t node_depth;
1548 *depths = apr_hash_make(result_pool);
1550 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1551 NULL, NULL, NULL, &node_depth, NULL,
1552 NULL, NULL, NULL, NULL, NULL, NULL,
1553 NULL, NULL, NULL, NULL, NULL, NULL,
1554 NULL, NULL, NULL, NULL,
1555 wc_ctx->db, local_abspath,
1556 scratch_pool, scratch_pool));
1558 svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1561 return SVN_NO_ERROR;
1566 svn_wc__close_db(const char *external_abspath,
1567 svn_wc_context_t *wc_ctx,
1568 apr_pool_t *scratch_pool)
1570 SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1572 return SVN_NO_ERROR;
1575 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
1576 If @a uri does not appear to be a valid URI, then @a scheme will
1578 static svn_error_t *
1579 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1583 for (i = 0; uri[i] && uri[i] != ':'; ++i)
1587 if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1589 *scheme = apr_pstrmemdup(pool, uri, i);
1590 return SVN_NO_ERROR;
1594 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1595 _("URL '%s' does not begin with a scheme"),
1600 svn_wc__resolve_relative_external_url(const char **resolved_url,
1601 const svn_wc_external_item2_t *item,
1602 const char *repos_root_url,
1603 const char *parent_dir_url,
1604 apr_pool_t *result_pool,
1605 apr_pool_t *scratch_pool)
1607 const char *url = item->url;
1608 apr_uri_t parent_dir_uri;
1609 apr_status_t status;
1611 *resolved_url = item->url;
1613 /* If the URL is already absolute, there is nothing to do. */
1614 if (svn_path_is_url(url))
1616 /* "http://server/path" */
1617 *resolved_url = svn_uri_canonicalize(url, result_pool);
1618 return SVN_NO_ERROR;
1623 /* "/path", "//path", and "///path" */
1624 int num_leading_slashes = 1;
1627 num_leading_slashes++;
1629 num_leading_slashes++;
1632 /* "//schema-relative" and in some cases "///schema-relative".
1633 This last format is supported on file:// schema relative. */
1634 url = apr_pstrcat(scratch_pool,
1635 apr_pstrndup(scratch_pool, url, num_leading_slashes),
1636 svn_relpath_canonicalize(url + num_leading_slashes,
1642 /* "^/path" and "../path" */
1643 url = svn_relpath_canonicalize(url, scratch_pool);
1646 /* Parse the parent directory URL into its parts. */
1647 status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1649 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1650 _("Illegal parent directory URL '%s'"),
1653 /* If the parent directory URL is at the server root, then the URL
1654 may have no / after the hostname so apr_uri_parse() will leave
1655 the URL's path as NULL. */
1656 if (! parent_dir_uri.path)
1657 parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1658 parent_dir_uri.query = NULL;
1659 parent_dir_uri.fragment = NULL;
1661 /* Handle URLs relative to the current directory or to the
1662 repository root. The backpaths may only remove path elements,
1663 not the hostname. This allows an external to refer to another
1664 repository in the same server relative to the location of this
1665 repository, say using SVNParentPath. */
1666 if ((0 == strncmp("../", url, 3)) ||
1667 (0 == strncmp("^/", url, 2)))
1669 apr_array_header_t *base_components;
1670 apr_array_header_t *relative_components;
1673 /* Decompose either the parent directory's URL path or the
1674 repository root's URL path into components. */
1675 if (0 == strncmp("../", url, 3))
1677 base_components = svn_path_decompose(parent_dir_uri.path,
1679 relative_components = svn_path_decompose(url, scratch_pool);
1683 apr_uri_t repos_root_uri;
1685 status = apr_uri_parse(scratch_pool, repos_root_url,
1688 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1689 _("Illegal repository root URL '%s'"),
1692 /* If the repository root URL is at the server root, then
1693 the URL may have no / after the hostname so
1694 apr_uri_parse() will leave the URL's path as NULL. */
1695 if (! repos_root_uri.path)
1696 repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1698 base_components = svn_path_decompose(repos_root_uri.path,
1700 relative_components = svn_path_decompose(url + 2, scratch_pool);
1703 for (i = 0; i < relative_components->nelts; ++i)
1705 const char *component = APR_ARRAY_IDX(relative_components,
1708 if (0 == strcmp("..", component))
1710 /* Constructing the final absolute URL together with
1711 apr_uri_unparse() requires that the path be absolute,
1712 so only pop a component if the component being popped
1713 is not the component for the root directory. */
1714 if (base_components->nelts > 1)
1715 apr_array_pop(base_components);
1718 APR_ARRAY_PUSH(base_components, const char *) = component;
1721 parent_dir_uri.path = (char *)svn_path_compose(base_components,
1723 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1724 &parent_dir_uri, 0),
1726 return SVN_NO_ERROR;
1729 /* The remaining URLs are relative to either the scheme or server root
1730 and can only refer to locations inside that scope, so backpaths are
1732 if (svn_path_is_backpath_present(url))
1733 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1734 _("The external relative URL '%s' cannot have "
1735 "backpaths, i.e. '..'"),
1738 /* Relative to the scheme: Build a new URL from the parts we know. */
1739 if (0 == strncmp("//", url, 2))
1743 SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1744 *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1745 ":", url, SVN_VA_NULL),
1747 return SVN_NO_ERROR;
1750 /* Relative to the server root: Just replace the path portion of the
1754 parent_dir_uri.path = (char *)url;
1755 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1756 &parent_dir_uri, 0),
1758 return SVN_NO_ERROR;
1761 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1762 _("Unrecognized format for the relative external "