diff options
Diffstat (limited to 'subversion/libsvn_wc/externals.c')
-rw-r--r-- | subversion/libsvn_wc/externals.c | 581 |
1 files changed, 516 insertions, 65 deletions
diff --git a/subversion/libsvn_wc/externals.c b/subversion/libsvn_wc/externals.c index ccb86f9..3725b22 100644 --- a/subversion/libsvn_wc/externals.c +++ b/subversion/libsvn_wc/externals.c @@ -30,6 +30,7 @@ #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" @@ -44,6 +45,7 @@ #include "svn_wc.h" #include "private/svn_skel.h" +#include "private/svn_subr_private.h" #include "wc.h" #include "adm_files.h" @@ -162,13 +164,16 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p, svn_boolean_t canonicalize_url, apr_pool_t *pool) { - apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, 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_p = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *)); + externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *)); for (i = 0; i < lines->nelts; i++) { @@ -200,8 +205,7 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p, for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++) ; - SVN_ERR(svn_wc_external_item_create - ((const svn_wc_external_item2_t **) &item, pool)); + SVN_ERR(svn_wc_external_item2_create(&item, pool)); item->revision.kind = svn_opt_revision_unspecified; item->peg_revision.kind = svn_opt_revision_unspecified; @@ -327,13 +331,63 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p, item->url = svn_dirent_canonicalize(item->url, pool); } - if (externals_p) - APR_ARRAY_PUSH(*externals_p, svn_wc_external_item2_t *) = item; + 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 { @@ -360,6 +414,9 @@ struct edit_baton 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; @@ -381,6 +438,10 @@ struct edit_baton /* 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; @@ -433,6 +494,7 @@ add_file(const char *path, *file_baton = eb; eb->original_revision = SVN_INVALID_REVNUM; + eb->added = TRUE; return SVN_NO_ERROR; } @@ -446,7 +508,7 @@ open_file(const char *path, void **file_baton) { struct edit_baton *eb = parent_baton; - svn_wc__db_kind_t kind; + 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'"), @@ -458,11 +520,11 @@ open_file(const char *path, NULL, NULL, NULL, &eb->changed_rev, &eb->changed_date, &eb->changed_author, NULL, &eb->original_checksum, NULL, NULL, - &eb->had_props, NULL, + &eb->had_props, NULL, NULL, eb->db, eb->local_abspath, eb->pool, file_pool)); - if (kind != svn_wc__db_kind_file) + 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, @@ -559,6 +621,7 @@ close_file(void *file_baton, eb->file_closed = TRUE; /* We bump the revision here */ + /* Check the checksum, if provided */ if (expected_md5_digest) { svn_checksum_t *expected_md5_checksum; @@ -600,10 +663,10 @@ close_file(void *file_baton, eb->new_pristine_abspath = NULL; } - /* ### TODO: Merge the changes */ - + /* 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; @@ -614,20 +677,19 @@ close_file(void *file_baton, const svn_checksum_t *original_checksum = NULL; svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision); - const char *repos_relpath = svn_uri__is_child(eb->repos_root_url, - eb->url, pool); + 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_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)); + SVN_ERR(svn_wc__db_read_props( + &actual_props, eb->db, eb->local_abspath, pool, pool)); } if (!base_props) @@ -639,6 +701,7 @@ close_file(void *file_baton, 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; @@ -649,6 +712,7 @@ close_file(void *file_baton, &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, @@ -670,31 +734,26 @@ close_file(void *file_baton, 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) { - SVN_ERR(svn_wc__merge_props(&work_item, &prop_state, - &new_pristine_props, + 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, - svn_wc__db_kind_file, - NULL, NULL, NULL /* server_baseprops*/, base_props, actual_props, regular_prop_changes, - TRUE /* base_merge */, - FALSE /* dry_run */, - eb->conflict_func, - eb->conflict_baton, - eb->cancel_func, eb->cancel_baton, pool, pool)); - - if (work_item) - all_work_items = svn_wc__wq_merge(all_work_items, work_item, - pool); } else { @@ -703,6 +762,7 @@ close_file(void *file_baton, } } + /* Merge the text */ if (eb->new_sha1_checksum) { svn_node_kind_t disk_kind; @@ -717,7 +777,8 @@ close_file(void *file_baton, install_pristine = TRUE; content_state = svn_wc_notify_state_changed; } - else if (disk_kind != svn_node_file) + 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; @@ -737,10 +798,12 @@ close_file(void *file_baton, } else { - enum svn_wc_merge_outcome_t merge_outcome; + 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, - &merge_outcome, + &conflict_skel, + &found_text_conflict, eb->db, eb->local_abspath, eb->wri_abspath, @@ -752,8 +815,6 @@ close_file(void *file_baton, *eb->target_revision, eb->propchanges, eb->diff3cmd, - eb->conflict_func, - eb->conflict_baton, eb->cancel_func, eb->cancel_baton, pool, pool)); @@ -761,7 +822,7 @@ close_file(void *file_baton, all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); - if (merge_outcome == svn_wc_merge_conflict) + if (found_text_conflict) content_state = svn_wc_notify_state_conflicted; else content_state = svn_wc_notify_state_merged; @@ -784,6 +845,35 @@ close_file(void *file_baton, /* ### 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, @@ -793,6 +883,7 @@ close_file(void *file_baton, eb->repos_uuid, *eb->target_revision, new_pristine_props, + eb->iprops, eb->changed_rev, eb->changed_date, eb->changed_author, @@ -804,19 +895,28 @@ close_file(void *file_baton, 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 (SVN_IS_VALID_REVNUM(eb->original_revision)) + if (!eb->added) action = obstructed ? svn_wc_notify_update_shadowed_update : svn_wc_notify_update_update; else @@ -835,7 +935,6 @@ close_file(void *file_baton, eb->notify_func(eb->notify_baton, notify, pool); } - return SVN_NO_ERROR; } @@ -848,14 +947,60 @@ close_edit(void *edit_baton, if (!eb->file_closed) { - /* 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), - pool)); + /* The file wasn't updated, but its url or revision might have... + e.g. switch between branches for relative externals. + + Just bump the information as that is just as expensive as + investigating when we should and shouldn't update it... + and avoid hard to debug edge cases */ + + svn_node_kind_t kind; + const char *old_repos_relpath; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const svn_checksum_t *checksum; + apr_hash_t *pristine_props; + const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, + eb->url, pool); + + SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, NULL, &old_repos_relpath, + NULL, NULL, &changed_rev, &changed_date, + &changed_author, NULL, &checksum, NULL, + NULL, NULL, &pristine_props, NULL, + eb->db, eb->local_abspath, + pool, 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, + pool)); + + 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, + pristine_props, + eb->iprops, + eb->changed_rev, + eb->changed_date, + eb->changed_author, + checksum, + NULL /* clear dav props */, + eb->record_ancestor_abspath, + eb->recorded_repos_relpath, + eb->recorded_peg_revision, + eb->recorded_revision, + FALSE, NULL, + TRUE /* keep_recorded_info */, + NULL /* conflict_skel */, + NULL /* work_items */, + pool)); } return SVN_NO_ERROR; @@ -871,6 +1016,7 @@ svn_wc__get_file_external_editor(const svn_delta_editor_t **editor, 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, @@ -906,13 +1052,15 @@ svn_wc__get_file_external_editor(const svn_delta_editor_t **editor, 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__is_child(repos_root_url, recorded_url, - edit_pool); + eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url, + edit_pool); eb->changed_rev = SVN_INVALID_REVNUM; @@ -965,7 +1113,7 @@ svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx, { svn_wc__db_t *db = wc_ctx->db; svn_error_t *err; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_wc__db_lock_t *lock; svn_revnum_t revision; const char *repos_root_url; @@ -975,12 +1123,12 @@ svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx, err = svn_wc__db_base_get_info(NULL, &kind, &revision, &repos_relpath, &repos_root_url, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &lock, - NULL, &update_root, + NULL, NULL, &update_root, db, local_abspath, scratch_pool, scratch_pool); if (err - || kind == svn_wc__db_kind_dir + || kind == svn_node_dir || !update_root) { if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) @@ -1064,7 +1212,7 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind, { const char *repos_root_url; svn_wc__db_status_t status; - svn_wc__db_kind_t kind; + svn_node_kind_t kind; svn_error_t *err; err = svn_wc__db_external_read(&status, &kind, defining_abspath, @@ -1106,11 +1254,11 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind, else switch(kind) { - case svn_wc__db_kind_file: - case svn_wc__db_kind_symlink: + case svn_node_file: + case svn_node_symlink: *external_kind = svn_node_file; break; - case svn_wc__db_kind_dir: + case svn_node_dir: *external_kind = svn_node_dir; break; default: @@ -1125,6 +1273,108 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind, 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, @@ -1168,12 +1418,13 @@ 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_wc__db_kind_t kind; + svn_node_kind_t kind; SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL, @@ -1183,15 +1434,25 @@ svn_wc__external_remove(svn_wc_context_t *wc_ctx, SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath, NULL, scratch_pool)); - if (kind == svn_wc__db_kind_dir) + 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, FALSE, + TRUE, TRUE, cancel_func, cancel_baton, scratch_pool)); else { - SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath, scratch_pool)); - SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); + SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath, + FALSE /* keep_as_working */, + TRUE /* queue_deletes */, + FALSE /* remove_locks */, + 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; @@ -1235,8 +1496,7 @@ svn_wc__externals_gather_definitions(apr_hash_t **externals, } if (value) - apr_hash_set(*externals, local_abspath, APR_HASH_KEY_STRING, - value->data); + svn_hash_sets(*externals, local_abspath, value->data); if (value && depths) { @@ -1251,8 +1511,7 @@ svn_wc__externals_gather_definitions(apr_hash_t **externals, wc_ctx->db, local_abspath, scratch_pool, scratch_pool)); - apr_hash_set(*depths, local_abspath, APR_HASH_KEY_STRING, - svn_depth_to_word(node_depth)); + svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth)); } return SVN_NO_ERROR; @@ -1268,3 +1527,195 @@ svn_wc__close_db(const char *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); +} |