diff options
Diffstat (limited to 'subversion/libsvn_wc/externals.c')
-rw-r--r-- | subversion/libsvn_wc/externals.c | 1686 |
1 files changed, 1686 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/externals.c b/subversion/libsvn_wc/externals.c new file mode 100644 index 0000000..514148fe --- /dev/null +++ b/subversion/libsvn_wc/externals.c @@ -0,0 +1,1686 @@ +/* + * externals.c : routines dealing with (file) externals in the working copy + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <string.h> + +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_tables.h> +#include <apr_general.h> +#include <apr_uri.h> + +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_hash.h" +#include "svn_io.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_string.h" +#include "svn_time.h" +#include "svn_types.h" +#include "svn_wc.h" + +#include "private/svn_skel.h" +#include "private/svn_subr_private.h" + +#include "wc.h" +#include "adm_files.h" +#include "props.h" +#include "translate.h" +#include "workqueue.h" +#include "conflicts.h" + +#include "svn_private_config.h" + +/** Externals **/ + +/* + * Look for either + * + * -r N + * -rN + * + * in the LINE_PARTS array and update the revision field in ITEM with + * the revision if the revision is found. Set REV_IDX to the index in + * LINE_PARTS where the revision specification starts. Remove from + * LINE_PARTS the element(s) that specify the revision. + * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error + * string. + * + * If this function returns successfully, then LINE_PARTS will have + * only two elements in it. + */ +static svn_error_t * +find_and_remove_externals_revision(int *rev_idx, + const char **line_parts, + int num_line_parts, + svn_wc_external_item2_t *item, + const char *parent_directory_display, + const char *line, + apr_pool_t *pool) +{ + int i; + + for (i = 0; i < 2; ++i) + { + const char *token = line_parts[i]; + + if (token[0] == '-' && token[1] == 'r') + { + svn_opt_revision_t end_revision = { svn_opt_revision_unspecified }; + const char *digits_ptr; + int shift_count; + int j; + + *rev_idx = i; + + if (token[2] == '\0') + { + /* There must be a total of four elements in the line if + -r N is used. */ + if (num_line_parts != 4) + goto parse_error; + + shift_count = 2; + digits_ptr = line_parts[i+1]; + } + else + { + /* There must be a total of three elements in the line + if -rN is used. */ + if (num_line_parts != 3) + goto parse_error; + + shift_count = 1; + digits_ptr = token+2; + } + + if (svn_opt_parse_revision(&item->revision, + &end_revision, + digits_ptr, pool) != 0) + goto parse_error; + /* We want a single revision, not a range. */ + if (end_revision.kind != svn_opt_revision_unspecified) + goto parse_error; + /* Allow only numbers and dates, not keywords. */ + if (item->revision.kind != svn_opt_revision_number + && item->revision.kind != svn_opt_revision_date) + goto parse_error; + + /* Shift any line elements past the revision specification + down over the revision specification. */ + for (j = i; j < num_line_parts-shift_count; ++j) + line_parts[j] = line_parts[j+shift_count]; + line_parts[num_line_parts-shift_count] = NULL; + + /* Found the revision, so leave the function immediately, do + * not continue looking for additional revisions. */ + return SVN_NO_ERROR; + } + } + + /* No revision was found, so there must be exactly two items in the + line array. */ + if (num_line_parts == 2) + return SVN_NO_ERROR; + + parse_error: + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Error parsing %s property on '%s': '%s'"), + SVN_PROP_EXTERNALS, + parent_directory_display, + line); +} + +svn_error_t * +svn_wc_parse_externals_description3(apr_array_header_t **externals_p, + const char *parent_directory, + const char *desc, + svn_boolean_t canonicalize_url, + apr_pool_t *pool) +{ + int i; + apr_array_header_t *externals = NULL; + apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool); + const char *parent_directory_display = svn_path_is_url(parent_directory) ? + parent_directory : svn_dirent_local_style(parent_directory, pool); + + /* If an error occurs halfway through parsing, *externals_p should stay + * untouched. So, store the list in a local var first. */ + if (externals_p) + externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *)); + + for (i = 0; i < lines->nelts; i++) + { + const char *line = APR_ARRAY_IDX(lines, i, const char *); + apr_status_t status; + char **line_parts; + int num_line_parts; + svn_wc_external_item2_t *item; + const char *token0; + const char *token1; + svn_boolean_t token0_is_url; + svn_boolean_t token1_is_url; + + /* Index into line_parts where the revision specification + started. */ + int rev_idx = -1; + + if ((! line) || (line[0] == '#')) + continue; + + /* else proceed */ + + status = apr_tokenize_to_argv(line, &line_parts, pool); + if (status) + return svn_error_wrap_apr(status, + _("Can't split line into components: '%s'"), + line); + /* Count the number of tokens. */ + for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++) + ; + + SVN_ERR(svn_wc_external_item2_create(&item, pool)); + item->revision.kind = svn_opt_revision_unspecified; + item->peg_revision.kind = svn_opt_revision_unspecified; + + /* + * There are six different formats of externals: + * + * 1) DIR URL + * 2) DIR -r N URL + * 3) DIR -rN URL + * 4) URL DIR + * 5) -r N URL DIR + * 6) -rN URL DIR + * + * The last three allow peg revisions in the URL. + * + * With relative URLs and no '-rN' or '-r N', there is no way to + * distinguish between 'DIR URL' and 'URL DIR' when URL is a + * relative URL like /svn/repos/trunk, so this case is taken as + * case 4). + */ + if (num_line_parts < 2 || num_line_parts > 4) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Error parsing %s property on '%s': '%s'"), + SVN_PROP_EXTERNALS, + parent_directory_display, + line); + + /* To make it easy to check for the forms, find and remove -r N + or -rN from the line item array. If it is found, rev_idx + contains the index into line_parts where '-r' was found and + set item->revision to the parsed revision. */ + /* ### ugh. stupid cast. */ + SVN_ERR(find_and_remove_externals_revision(&rev_idx, + (const char **)line_parts, + num_line_parts, item, + parent_directory_display, + line, pool)); + + token0 = line_parts[0]; + token1 = line_parts[1]; + + token0_is_url = svn_path_is_url(token0); + token1_is_url = svn_path_is_url(token1); + + if (token0_is_url && token1_is_url) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Invalid %s property on '%s': " + "cannot use two absolute URLs ('%s' and '%s') in an external; " + "one must be a path where an absolute or relative URL is " + "checked out to"), + SVN_PROP_EXTERNALS, parent_directory_display, token0, token1); + + if (0 == rev_idx && token1_is_url) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Invalid %s property on '%s': " + "cannot use a URL '%s' as the target directory for an external " + "definition"), + SVN_PROP_EXTERNALS, parent_directory_display, token1); + + if (1 == rev_idx && token0_is_url) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Invalid %s property on '%s': " + "cannot use a URL '%s' as the target directory for an external " + "definition"), + SVN_PROP_EXTERNALS, parent_directory_display, token0); + + /* The appearence of -r N or -rN forces the type of external. + If -r is at the beginning of the line or the first token is + an absolute URL or if the second token is not an absolute + URL, then the URL supports peg revisions. */ + if (0 == rev_idx || + (-1 == rev_idx && (token0_is_url || ! token1_is_url))) + { + /* The URL is passed to svn_opt_parse_path in + uncanonicalized form so that the scheme relative URL + //hostname/foo is not collapsed to a server root relative + URL /hostname/foo. */ + SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url, + token0, pool)); + item->target_dir = token1; + } + else + { + item->target_dir = token0; + item->url = token1; + item->peg_revision = item->revision; + } + + SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision, + &item->revision, TRUE, FALSE, + pool)); + + item->target_dir = svn_dirent_internal_style(item->target_dir, pool); + + if (item->target_dir[0] == '\0' + || svn_dirent_is_absolute(item->target_dir) + || svn_path_is_backpath_present(item->target_dir) + || !svn_dirent_skip_ancestor("dummy", + svn_dirent_join("dummy", + item->target_dir, + pool))) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Invalid %s property on '%s': " + "target '%s' is an absolute path or involves '..'"), + SVN_PROP_EXTERNALS, + parent_directory_display, + item->target_dir); + + if (canonicalize_url) + { + /* Uh... this is stupid. But it's consistent with what our + code did before we split up the relpath/dirent/uri APIs. + Still, given this, it's no wonder that our own libraries + don't ask this function to canonicalize the results. */ + if (svn_path_is_url(item->url)) + item->url = svn_uri_canonicalize(item->url, pool); + else + item->url = svn_dirent_canonicalize(item->url, pool); + } + + if (externals) + APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item; + } + + if (externals_p) + *externals_p = externals; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets, + apr_array_header_t *externals, + apr_pool_t *pool, + apr_pool_t *scratch_pool) +{ + int i; + unsigned int len; + unsigned int len2; + const char *target; + apr_hash_t *targets = apr_hash_make(scratch_pool); + apr_hash_t *targets2 = NULL; + *duplicate_targets = NULL; + + for (i = 0; i < externals->nelts; i++) + { + target = APR_ARRAY_IDX(externals, i, + svn_wc_external_item2_t*)->target_dir; + len = apr_hash_count(targets); + svn_hash_sets(targets, target, ""); + if (len == apr_hash_count(targets)) + { + /* Hashtable length is unchanged. This must be a duplicate. */ + + /* Collapse multiple duplicates of the same target by using a second + * hash layer. */ + if (! targets2) + targets2 = apr_hash_make(scratch_pool); + len2 = apr_hash_count(targets2); + svn_hash_sets(targets2, target, ""); + if (len2 < apr_hash_count(targets2)) + { + /* The second hash list just got bigger, i.e. this target has + * not been counted as duplicate before. */ + if (! *duplicate_targets) + { + *duplicate_targets = apr_array_make( + pool, 1, sizeof(svn_wc_external_item2_t*)); + } + APR_ARRAY_PUSH((*duplicate_targets), const char *) = target; + } + /* Else, this same target has already been recorded as a duplicate, + * don't count it again. */ + } + } + return SVN_NO_ERROR; +} + +struct edit_baton +{ + apr_pool_t *pool; + svn_wc__db_t *db; + + /* We explicitly use wri_abspath and local_abspath here, because we + might want to install file externals in an obstructing working copy */ + const char *wri_abspath; /* The working defining the file external */ + const char *local_abspath; /* The file external itself */ + const char *name; /* The basename of the file external itself */ + + /* Information from the caller */ + svn_boolean_t use_commit_times; + const apr_array_header_t *ext_patterns; + const char *diff3cmd; + + const char *url; + const char *repos_root_url; + const char *repos_uuid; + + const char *record_ancestor_abspath; + const char *recorded_repos_relpath; + svn_revnum_t recorded_peg_revision; + svn_revnum_t recorded_revision; + + /* Introducing a new file external */ + svn_boolean_t added; + + svn_wc_conflict_resolver_func2_t conflict_func; + void *conflict_baton; + svn_cancel_func_t cancel_func; + void *cancel_baton; + svn_wc_notify_func2_t notify_func; + void *notify_baton; + + svn_revnum_t *target_revision; + + /* What was there before the update */ + svn_revnum_t original_revision; + const svn_checksum_t *original_checksum; + + /* What we are installing now */ + const char *new_pristine_abspath; + svn_checksum_t *new_sha1_checksum; + svn_checksum_t *new_md5_checksum; + + /* List of incoming propchanges */ + apr_array_header_t *propchanges; + + /* Array of svn_prop_inherited_item_t * structures representing the + properties inherited by the base node at LOCAL_ABSPATH. */ + apr_array_header_t *iprops; + + /* The last change information */ + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + + svn_boolean_t had_props; + + svn_boolean_t file_closed; +}; + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + *eb->target_revision = target_revision; + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *dir_pool, + void **root_baton) +{ + *root_baton = edit_baton; + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *file_pool, + void **file_baton) +{ + struct edit_baton *eb = parent_baton; + if (strcmp(path, eb->name)) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("This editor can only update '%s'"), + svn_dirent_local_style(eb->local_abspath, + file_pool)); + + *file_baton = eb; + eb->original_revision = SVN_INVALID_REVNUM; + eb->added = TRUE; + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *file_pool, + void **file_baton) +{ + struct edit_baton *eb = parent_baton; + svn_node_kind_t kind; + if (strcmp(path, eb->name)) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("This editor can only update '%s'"), + svn_dirent_local_style(eb->local_abspath, + file_pool)); + + *file_baton = eb; + SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision, + NULL, NULL, NULL, &eb->changed_rev, + &eb->changed_date, &eb->changed_author, + NULL, &eb->original_checksum, NULL, NULL, + &eb->had_props, NULL, NULL, + eb->db, eb->local_abspath, + eb->pool, file_pool)); + + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Node '%s' is no existing file external"), + svn_dirent_local_style(eb->local_abspath, + file_pool)); + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum_digest, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct edit_baton *eb = file_baton; + svn_stream_t *src_stream; + svn_stream_t *dest_stream; + + if (eb->original_checksum) + { + if (base_checksum_digest) + { + svn_checksum_t *expected_checksum; + const svn_checksum_t *original_md5; + + SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, + base_checksum_digest, pool)); + + if (eb->original_checksum->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5, + eb->db, eb->wri_abspath, + eb->original_checksum, + pool, pool)); + else + original_md5 = eb->original_checksum; + + if (!svn_checksum_match(expected_checksum, original_md5)) + return svn_error_trace(svn_checksum_mismatch_err( + expected_checksum, + original_md5, + pool, + _("Base checksum mismatch for '%s'"), + svn_dirent_local_style(eb->local_abspath, + pool))); + } + + SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db, + eb->wri_abspath, eb->original_checksum, + pool, pool)); + } + else + src_stream = svn_stream_empty(pool); + + SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath, + &eb->new_md5_checksum, + &eb->new_sha1_checksum, + eb->db, eb->wri_abspath, + eb->pool, pool)); + + svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool, + handler, handler_baton); + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct edit_baton *eb = file_baton; + svn_prop_t *propchange; + + propchange = apr_array_push(eb->propchanges); + propchange->name = apr_pstrdup(eb->pool, name); + propchange->value = value ? svn_string_dup(value, eb->pool) : NULL; + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +close_file(void *file_baton, + const char *expected_md5_digest, + apr_pool_t *pool) +{ + struct edit_baton *eb = file_baton; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; + svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown; + svn_boolean_t obstructed = FALSE; + + eb->file_closed = TRUE; /* We bump the revision here */ + + /* Check the checksum, if provided */ + if (expected_md5_digest) + { + svn_checksum_t *expected_md5_checksum; + const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum; + + SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5, + expected_md5_digest, pool)); + + if (actual_md5_checksum == NULL) + { + actual_md5_checksum = eb->original_checksum; + + if (actual_md5_checksum != NULL + && actual_md5_checksum->kind != svn_checksum_md5) + { + SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum, + eb->db, eb->wri_abspath, + actual_md5_checksum, + pool, pool)); + } + } + + if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum)) + return svn_checksum_mismatch_err( + expected_md5_checksum, + actual_md5_checksum, pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style(eb->local_abspath, pool)); + } + + /* First move the file in the pristine store; this hands over the cleanup + behavior to the pristine store. */ + if (eb->new_sha1_checksum) + { + SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath, + eb->new_sha1_checksum, + eb->new_md5_checksum, pool)); + + eb->new_pristine_abspath = NULL; + } + + /* Merge the changes */ + { + svn_skel_t *all_work_items = NULL; + svn_skel_t *conflict_skel = NULL; + svn_skel_t *work_item; + apr_hash_t *base_props = NULL; + apr_hash_t *actual_props = NULL; + apr_hash_t *new_pristine_props = NULL; + apr_hash_t *new_actual_props = NULL; + apr_hash_t *new_dav_props = NULL; + const svn_checksum_t *new_checksum = NULL; + const svn_checksum_t *original_checksum = NULL; + + svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision); + const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, + eb->url, pool); + + if (! added) + { + new_checksum = eb->original_checksum; + + if (eb->had_props) + SVN_ERR(svn_wc__db_base_get_props( + &base_props, eb->db, eb->local_abspath, pool, pool)); + + SVN_ERR(svn_wc__db_read_props( + &actual_props, eb->db, eb->local_abspath, pool, pool)); + } + + if (!base_props) + base_props = apr_hash_make(pool); + + if (!actual_props) + actual_props = apr_hash_make(pool); + + if (eb->new_sha1_checksum) + new_checksum = eb->new_sha1_checksum; + + /* Merge the properties */ + { + apr_array_header_t *entry_prop_changes; + apr_array_header_t *dav_prop_changes; + apr_array_header_t *regular_prop_changes; + int i; + + SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes, + &dav_prop_changes, ®ular_prop_changes, + pool)); + + /* Read the entry-prop changes to update the last-changed info. */ + for (i = 0; i < entry_prop_changes->nelts; i++) + { + const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i, + svn_prop_t); + + if (! prop->value) + continue; /* authz or something */ + + if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR)) + eb->changed_author = apr_pstrdup(pool, prop->value->data); + else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV)) + { + apr_int64_t rev; + SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data)); + eb->changed_rev = (svn_revnum_t)rev; + } + else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE)) + SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data, + pool)); + } + + /* Store the DAV-prop (aka WC-prop) changes. (This treats a list + * of changes as a list of new props, but we only use this when + * adding a new file and it's equivalent in that case.) */ + if (dav_prop_changes->nelts > 0) + new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool); + + /* Merge the regular prop changes. */ + if (regular_prop_changes->nelts > 0) + { + new_pristine_props = svn_prop__patch(base_props, regular_prop_changes, + pool); + SVN_ERR(svn_wc__merge_props(&conflict_skel, + &prop_state, + &new_actual_props, + eb->db, eb->local_abspath, + NULL /* server_baseprops*/, + base_props, + actual_props, + regular_prop_changes, + pool, pool)); + } + else + { + new_pristine_props = base_props; + new_actual_props = actual_props; + } + } + + /* Merge the text */ + if (eb->new_sha1_checksum) + { + svn_node_kind_t disk_kind; + svn_boolean_t install_pristine = FALSE; + const char *install_from = NULL; + + SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool)); + + if (disk_kind == svn_node_none) + { + /* Just install the file */ + install_pristine = TRUE; + content_state = svn_wc_notify_state_changed; + } + else if (disk_kind != svn_node_file + || (eb->added && disk_kind == svn_node_file)) + { + /* The node is obstructed; we just change the DB */ + obstructed = TRUE; + content_state = svn_wc_notify_state_unchanged; + } + else + { + svn_boolean_t is_mod; + SVN_ERR(svn_wc__internal_file_modified_p(&is_mod, + eb->db, eb->local_abspath, + FALSE, pool)); + + if (!is_mod) + { + install_pristine = TRUE; + content_state = svn_wc_notify_state_changed; + } + else + { + svn_boolean_t found_text_conflict; + + /* Ok, we have to do some work to merge a local change */ + SVN_ERR(svn_wc__perform_file_merge(&work_item, + &conflict_skel, + &found_text_conflict, + eb->db, + eb->local_abspath, + eb->wri_abspath, + new_checksum, + original_checksum, + actual_props, + eb->ext_patterns, + eb->original_revision, + *eb->target_revision, + eb->propchanges, + eb->diff3cmd, + eb->cancel_func, + eb->cancel_baton, + pool, pool)); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + pool); + + if (found_text_conflict) + content_state = svn_wc_notify_state_conflicted; + else + content_state = svn_wc_notify_state_merged; + } + } + if (install_pristine) + { + SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db, + eb->local_abspath, + install_from, + eb->use_commit_times, TRUE, + pool, pool)); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); + } + } + else + { + content_state = svn_wc_notify_state_unchanged; + /* ### Retranslate on magic property changes, etc. */ + } + + /* Generate a conflict description, if needed */ + if (conflict_skel) + { + SVN_ERR(svn_wc__conflict_skel_set_op_switch( + conflict_skel, + svn_wc_conflict_version_create2( + eb->repos_root_url, + eb->repos_uuid, + repos_relpath, + eb->original_revision, + svn_node_file, + pool), + svn_wc_conflict_version_create2( + eb->repos_root_url, + eb->repos_uuid, + repos_relpath, + *eb->target_revision, + svn_node_file, + pool), + pool, pool)); + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + eb->db, eb->local_abspath, + conflict_skel, + pool, pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + pool); + } + + /* Install the file in the DB */ + SVN_ERR(svn_wc__db_external_add_file( + eb->db, + eb->local_abspath, + eb->wri_abspath, + repos_relpath, + eb->repos_root_url, + eb->repos_uuid, + *eb->target_revision, + new_pristine_props, + eb->iprops, + eb->changed_rev, + eb->changed_date, + eb->changed_author, + new_checksum, + new_dav_props, + eb->record_ancestor_abspath, + eb->recorded_repos_relpath, + eb->recorded_peg_revision, + eb->recorded_revision, + TRUE, new_actual_props, + FALSE /* keep_recorded_info */, + conflict_skel, + all_work_items, + pool)); + + /* close_edit may also update iprops for switched files, catching + those for which close_file is never called (e.g. an update of a + file external with no changes). So as a minor optimization we + clear the iprops so as not to set them again in close_edit. */ + eb->iprops = NULL; + + /* Run the work queue to complete the installation */ + SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath, + eb->cancel_func, eb->cancel_baton, pool)); + } + + /* Notify */ + if (eb->notify_func) + { + svn_wc_notify_action_t action; + svn_wc_notify_t *notify; + + if (!eb->added) + action = obstructed ? svn_wc_notify_update_shadowed_update + : svn_wc_notify_update_update; + else + action = obstructed ? svn_wc_notify_update_shadowed_add + : svn_wc_notify_update_add; + + notify = svn_wc_create_notify(eb->local_abspath, action, pool); + notify->kind = svn_node_file; + + notify->revision = *eb->target_revision; + notify->prop_state = prop_state; + notify->content_state = content_state; + + notify->old_revision = eb->original_revision; + + eb->notify_func(eb->notify_baton, notify, pool); + } + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + if (!eb->file_closed + || eb->iprops) + { + apr_hash_t *wcroot_iprops = NULL; + + if (eb->iprops) + { + wcroot_iprops = apr_hash_make(pool); + svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops); + } + + /* The node wasn't updated, so we just have to bump its revision */ + SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db, + eb->local_abspath, + svn_depth_infinity, + NULL, NULL, NULL, + *eb->target_revision, + apr_hash_make(pool), + wcroot_iprops, + eb->notify_func, + eb->notify_baton, + pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__get_file_external_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *target_revision, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *wri_abspath, + const char *url, + const char *repos_root_url, + const char *repos_uuid, + apr_array_header_t *iprops, + svn_boolean_t use_commit_times, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + const char *record_ancestor_abspath, + const char *recorded_url, + const svn_opt_revision_t *recorded_peg_rev, + const svn_opt_revision_t *recorded_rev, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + apr_pool_t *edit_pool = result_pool; + struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb)); + svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool); + + eb->pool = edit_pool; + eb->db = db; + eb->local_abspath = apr_pstrdup(edit_pool, local_abspath); + if (wri_abspath) + eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath); + else + eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool); + eb->name = svn_dirent_basename(eb->local_abspath, NULL); + eb->target_revision = target_revision; + + eb->url = apr_pstrdup(edit_pool, url); + eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url); + eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid); + + eb->iprops = iprops; + + eb->use_commit_times = use_commit_times; + eb->ext_patterns = preserved_exts; + eb->diff3cmd = diff3_cmd; + + eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath); + eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url, + edit_pool); + + eb->changed_rev = SVN_INVALID_REVNUM; + + if (recorded_peg_rev->kind == svn_opt_revision_number) + eb->recorded_peg_revision = recorded_peg_rev->value.number; + else + eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */ + + if (recorded_rev->kind == svn_opt_revision_number) + eb->recorded_revision = recorded_rev->value.number; + else + eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */ + + eb->conflict_func = conflict_func; + eb->conflict_baton = conflict_baton; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + eb->notify_func = notify_func; + eb->notify_baton = notify_baton; + + eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t)); + + tree_editor->open_root = open_root; + tree_editor->set_target_revision = set_target_revision; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->close_file = close_file; + tree_editor->close_edit = close_edit; + + return svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + tree_editor, eb, + editor, edit_baton, + result_pool); +} + +svn_error_t * +svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_ra_reporter3_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_error_t *err; + svn_node_kind_t kind; + svn_wc__db_lock_t *lock; + svn_revnum_t revision; + const char *repos_root_url; + const char *repos_relpath; + svn_boolean_t update_root; + + err = svn_wc__db_base_get_info(NULL, &kind, &revision, + &repos_relpath, &repos_root_url, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &lock, + NULL, NULL, &update_root, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err + || kind == svn_node_dir + || !update_root) + { + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + /* We don't know about this node, so all we have to do is tell + the reporter that we don't know this node. + + But first we have to start the report by sending some basic + information for the root. */ + + SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity, + FALSE, NULL, scratch_pool)); + SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool)); + + /* Finish the report, which causes the update editor to be + driven. */ + SVN_ERR(reporter->finish_report(report_baton, scratch_pool)); + + return SVN_NO_ERROR; + } + else + { + if (restore_files) + { + svn_node_kind_t disk_kind; + SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); + + if (disk_kind == svn_node_none) + { + err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times, + scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + } + } + } + + /* Report that we know the path */ + SVN_ERR(reporter->set_path(report_baton, "", revision, + svn_depth_infinity, FALSE, NULL, + scratch_pool)); + + /* For compatibility with the normal update editor report we report + the target as switched. + + ### We can probably report a parent url and unswitched later */ + SVN_ERR(reporter->link_path(report_baton, "", + svn_path_url_add_component2(repos_root_url, + repos_relpath, + scratch_pool), + revision, + svn_depth_infinity, + FALSE /* start_empty*/, + lock ? lock->token : NULL, + scratch_pool)); + } + + return svn_error_trace(reporter->finish_report(report_baton, scratch_pool)); +} + +svn_error_t * +svn_wc__read_external_info(svn_node_kind_t *external_kind, + const char **defining_abspath, + const char **defining_url, + svn_revnum_t *defining_operational_revision, + svn_revnum_t *defining_revision, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + const char *local_abspath, + svn_boolean_t ignore_enoent, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *repos_root_url; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_error_t *err; + + err = svn_wc__db_external_read(&status, &kind, defining_abspath, + defining_url ? &repos_root_url : NULL, NULL, + defining_url, defining_operational_revision, + defining_revision, + wc_ctx->db, local_abspath, wri_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent) + return svn_error_trace(err); + + svn_error_clear(err); + + if (external_kind) + *external_kind = svn_node_none; + + if (defining_abspath) + *defining_abspath = NULL; + + if (defining_url) + *defining_url = NULL; + + if (defining_operational_revision) + *defining_operational_revision = SVN_INVALID_REVNUM; + + if (defining_revision) + *defining_revision = SVN_INVALID_REVNUM; + + return SVN_NO_ERROR; + } + + if (external_kind) + { + if (status != svn_wc__db_status_normal) + *external_kind = svn_node_unknown; + else + switch(kind) + { + case svn_node_file: + case svn_node_symlink: + *external_kind = svn_node_file; + break; + case svn_node_dir: + *external_kind = svn_node_dir; + break; + default: + *external_kind = svn_node_none; + } + } + + if (defining_url && *defining_url) + *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url, + result_pool); + + return SVN_NO_ERROR; +} + +/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and + * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and + * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */ +static svn_error_t * +is_external_rolled_out(svn_boolean_t *is_rolled_out, + svn_wc_context_t *wc_ctx, + svn_wc__committable_external_info_t *xinfo, + apr_pool_t *scratch_pool) +{ + const char *repos_relpath; + const char *repos_root_url; + svn_error_t *err; + + *is_rolled_out = FALSE; + + err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath, + &repos_root_url, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, xinfo->local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + } + + *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 && + strcmp(xinfo->repos_relpath, repos_relpath) == 0); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__committable_externals_below(apr_array_header_t **externals, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *orig_externals; + int i; + apr_pool_t *iterpool; + + /* For svn_depth_files, this also fetches dirs. They are filtered later. */ + SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals, + wc_ctx->db, + local_abspath, + depth != svn_depth_infinity, + result_pool, scratch_pool)); + + if (orig_externals == NULL) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < orig_externals->nelts; i++) + { + svn_boolean_t is_rolled_out; + + svn_wc__committable_external_info_t *xinfo = + APR_ARRAY_IDX(orig_externals, i, + svn_wc__committable_external_info_t *); + + /* Discard dirs for svn_depth_files (s.a.). */ + if (depth == svn_depth_files + && xinfo->kind == svn_node_dir) + continue; + + svn_pool_clear(iterpool); + + /* Discard those externals that are not currently checked out. */ + SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo, + iterpool)); + if (! is_rolled_out) + continue; + + if (*externals == NULL) + *externals = apr_array_make( + result_pool, 0, + sizeof(svn_wc__committable_external_info_t *)); + + APR_ARRAY_PUSH(*externals, + svn_wc__committable_external_info_t *) = xinfo; + + if (depth != svn_depth_infinity) + continue; + + /* Are there any nested externals? */ + SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx, + xinfo->local_abspath, + svn_depth_infinity, + result_pool, iterpool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__externals_defined_below(apr_hash_t **externals, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_externals_defined_below(externals, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc__external_register(svn_wc_context_t *wc_ctx, + const char *defining_abspath, + const char *local_abspath, + svn_node_kind_t kind, + const char *repos_root_url, + const char *repos_uuid, + const char *repos_relpath, + svn_revnum_t operational_revision, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(kind == svn_node_dir); + return svn_error_trace( + svn_wc__db_external_add_dir(wc_ctx->db, local_abspath, + defining_abspath, + repos_root_url, + repos_uuid, + defining_abspath, + repos_relpath, + operational_revision, + revision, + NULL, + scratch_pool)); +} + +svn_error_t * +svn_wc__external_remove(svn_wc_context_t *wc_ctx, + const char *wri_abspath, + const char *local_abspath, + svn_boolean_t declaration_only, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + + SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL, + NULL, NULL, + wc_ctx->db, local_abspath, wri_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath, + NULL, scratch_pool)); + + if (declaration_only) + return SVN_NO_ERROR; + + if (kind == svn_node_dir) + SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath, + TRUE, TRUE, + cancel_func, cancel_baton, + scratch_pool)); + else + { + SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath, + FALSE /* keep_as_working */, + TRUE /* queue_deletes */, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); + SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__externals_gather_definitions(apr_hash_t **externals, + apr_hash_t **depths, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (depth == svn_depth_infinity + || depth == svn_depth_unknown) + { + return svn_error_trace( + svn_wc__db_externals_gather_definitions(externals, depths, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); + } + else + { + const svn_string_t *value; + svn_error_t *err; + *externals = apr_hash_make(result_pool); + + local_abspath = apr_pstrdup(result_pool, local_abspath); + + err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, + SVN_PROP_EXTERNALS, result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + value = NULL; + } + + if (value) + svn_hash_sets(*externals, local_abspath, value->data); + + if (value && depths) + { + svn_depth_t node_depth; + *depths = apr_hash_make(result_pool); + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &node_depth, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth)); + } + + return SVN_NO_ERROR; + } +} + +svn_error_t * +svn_wc__close_db(const char *external_abspath, + svn_wc_context_t *wc_ctx, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* Return the scheme of @a uri in @a scheme allocated from @a pool. + If @a uri does not appear to be a valid URI, then @a scheme will + not be updated. */ +static svn_error_t * +uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool) +{ + apr_size_t i; + + for (i = 0; uri[i] && uri[i] != ':'; ++i) + if (uri[i] == '/') + goto error; + + if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/') + { + *scheme = apr_pstrmemdup(pool, uri, i); + return SVN_NO_ERROR; + } + +error: + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("URL '%s' does not begin with a scheme"), + uri); +} + +svn_error_t * +svn_wc__resolve_relative_external_url(const char **resolved_url, + const svn_wc_external_item2_t *item, + const char *repos_root_url, + const char *parent_dir_url, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *url = item->url; + apr_uri_t parent_dir_uri; + apr_status_t status; + + *resolved_url = item->url; + + /* If the URL is already absolute, there is nothing to do. */ + if (svn_path_is_url(url)) + { + /* "http://server/path" */ + *resolved_url = svn_uri_canonicalize(url, result_pool); + return SVN_NO_ERROR; + } + + if (url[0] == '/') + { + /* "/path", "//path", and "///path" */ + int num_leading_slashes = 1; + if (url[1] == '/') + { + num_leading_slashes++; + if (url[2] == '/') + num_leading_slashes++; + } + + /* "//schema-relative" and in some cases "///schema-relative". + This last format is supported on file:// schema relative. */ + url = apr_pstrcat(scratch_pool, + apr_pstrndup(scratch_pool, url, num_leading_slashes), + svn_relpath_canonicalize(url + num_leading_slashes, + scratch_pool), + (char*)NULL); + } + else + { + /* "^/path" and "../path" */ + url = svn_relpath_canonicalize(url, scratch_pool); + } + + /* Parse the parent directory URL into its parts. */ + status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri); + if (status) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Illegal parent directory URL '%s'"), + parent_dir_url); + + /* If the parent directory URL is at the server root, then the URL + may have no / after the hostname so apr_uri_parse() will leave + the URL's path as NULL. */ + if (! parent_dir_uri.path) + parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1); + parent_dir_uri.query = NULL; + parent_dir_uri.fragment = NULL; + + /* Handle URLs relative to the current directory or to the + repository root. The backpaths may only remove path elements, + not the hostname. This allows an external to refer to another + repository in the same server relative to the location of this + repository, say using SVNParentPath. */ + if ((0 == strncmp("../", url, 3)) || + (0 == strncmp("^/", url, 2))) + { + apr_array_header_t *base_components; + apr_array_header_t *relative_components; + int i; + + /* Decompose either the parent directory's URL path or the + repository root's URL path into components. */ + if (0 == strncmp("../", url, 3)) + { + base_components = svn_path_decompose(parent_dir_uri.path, + scratch_pool); + relative_components = svn_path_decompose(url, scratch_pool); + } + else + { + apr_uri_t repos_root_uri; + + status = apr_uri_parse(scratch_pool, repos_root_url, + &repos_root_uri); + if (status) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Illegal repository root URL '%s'"), + repos_root_url); + + /* If the repository root URL is at the server root, then + the URL may have no / after the hostname so + apr_uri_parse() will leave the URL's path as NULL. */ + if (! repos_root_uri.path) + repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1); + + base_components = svn_path_decompose(repos_root_uri.path, + scratch_pool); + relative_components = svn_path_decompose(url + 2, scratch_pool); + } + + for (i = 0; i < relative_components->nelts; ++i) + { + const char *component = APR_ARRAY_IDX(relative_components, + i, + const char *); + if (0 == strcmp("..", component)) + { + /* Constructing the final absolute URL together with + apr_uri_unparse() requires that the path be absolute, + so only pop a component if the component being popped + is not the component for the root directory. */ + if (base_components->nelts > 1) + apr_array_pop(base_components); + } + else + APR_ARRAY_PUSH(base_components, const char *) = component; + } + + parent_dir_uri.path = (char *)svn_path_compose(base_components, + scratch_pool); + *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool, + &parent_dir_uri, 0), + result_pool); + return SVN_NO_ERROR; + } + + /* The remaining URLs are relative to either the scheme or server root + and can only refer to locations inside that scope, so backpaths are + not allowed. */ + if (svn_path_is_backpath_present(url)) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("The external relative URL '%s' cannot have " + "backpaths, i.e. '..'"), + item->url); + + /* Relative to the scheme: Build a new URL from the parts we know. */ + if (0 == strncmp("//", url, 2)) + { + const char *scheme; + + SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool)); + *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme, + ":", url, (char *)NULL), + result_pool); + return SVN_NO_ERROR; + } + + /* Relative to the server root: Just replace the path portion of the + parent's URL. */ + if (url[0] == '/') + { + parent_dir_uri.path = (char *)url; + *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool, + &parent_dir_uri, 0), + result_pool); + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Unrecognized format for the relative external " + "URL '%s'"), + item->url); +} |