/* The main logic of the public svn_client_add4. * * EXISTING_PARENT_ABSPATH is the absolute path to the first existing * parent directory of local_abspath. If not NULL, all missing parents * of LOCAL_ABSPATH must be created before LOCAL_ABSPATH can be added. */ static svn_error_t * add(const char *local_abspath, svn_depth_t depth, svn_boolean_t force, svn_boolean_t no_ignore, const char *existing_parent_abspath, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_node_kind_t kind; svn_error_t *err; svn_magic__cookie_t *magic_cookie; svn_magic__init(&magic_cookie, scratch_pool); if (existing_parent_abspath) { const char *parent_abspath; const char *child_relpath; apr_array_header_t *components; int i; apr_pool_t *iterpool; parent_abspath = existing_parent_abspath; child_relpath = svn_dirent_is_child(existing_parent_abspath, local_abspath, NULL); components = svn_path_decompose(child_relpath, scratch_pool); iterpool = svn_pool_create(scratch_pool); for (i = 0; i < components->nelts - 1; i++) { const char *component; svn_node_kind_t disk_kind; svn_pool_clear(iterpool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); component = APR_ARRAY_IDX(components, i, const char *); parent_abspath = svn_dirent_join(parent_abspath, component, scratch_pool); SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, iterpool)); if (disk_kind != svn_node_none && disk_kind != svn_node_dir) return svn_error_createf(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, _("'%s' prevents creating parent of '%s'"), parent_abspath, local_abspath); SVN_ERR(svn_io_make_dir_recursively(parent_abspath, scratch_pool)); SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, parent_abspath, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); } svn_pool_destroy(iterpool); } SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); if (kind == svn_node_dir) { /* We use add_dir_recursive for all directory targets and pass depth along no matter what it is, so that the target's depth will be set correctly. */ err = add_dir_recursive(local_abspath, depth, force, no_ignore, magic_cookie, ctx, scratch_pool); } else if (kind == svn_node_file) err = add_file(local_abspath, magic_cookie, ctx, scratch_pool); else if (kind == svn_node_none) { svn_boolean_t tree_conflicted; /* Provide a meaningful error message if the node does not exist * on disk but is a tree conflict victim. */ err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, ctx->wc_ctx, local_abspath, scratch_pool); if (err) svn_error_clear(err); else if (tree_conflicted) return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL, _("'%s' is an existing item in conflict; " "please mark the conflict as resolved " "before adding a new item here"), svn_dirent_local_style(local_abspath, scratch_pool)); return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, _("'%s' not found"), svn_dirent_local_style(local_abspath, scratch_pool)); } else return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Unsupported node kind for path '%s'"), svn_dirent_local_style(local_abspath, scratch_pool)); /* Ignore SVN_ERR_ENTRY_EXISTS when FORCE is set. */ if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force) { svn_error_clear(err); err = SVN_NO_ERROR; } return svn_error_trace(err); }
static svn_error_t * new_node_record(void **node_baton, apr_hash_t *headers, void *revision_baton, apr_pool_t *pool) { struct revision_baton *rb = revision_baton; const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor; void *commit_edit_baton = rb->pb->commit_edit_baton; struct node_baton *nb; apr_hash_index_t *hi; void *child_baton; const char *nb_dirname; nb = apr_pcalloc(rb->pool, sizeof(*nb)); nb->rb = rb; nb->is_added = FALSE; nb->copyfrom_path = NULL; nb->copyfrom_url = NULL; nb->copyfrom_rev = SVN_INVALID_REVNUM; nb->prop_changes = apr_hash_make(rb->pool); /* If the creation of commit_editor is pending, create it now and open_root on it; also create a top-level directory baton. */ if (!commit_editor) { /* The revprop_table should have been filled in with important information like svn:log in set_revision_property. We can now use it all this information to create our commit_editor. But first, clear revprops that we aren't allowed to set with the commit_editor. We'll set them separately using the RA API after closing the editor (see close_revision). */ svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL); svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL); SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session, get_shim_callbacks(rb, rb->pool))); SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, &commit_edit_baton, rb->revprop_table, commit_callback, revision_baton, NULL, FALSE, rb->pool)); rb->pb->commit_editor = commit_editor; rb->pb->commit_edit_baton = commit_edit_baton; SVN_ERR(commit_editor->open_root(commit_edit_baton, rb->rev - rb->rev_offset - 1, rb->pool, &child_baton)); LDR_DBG(("Opened root %p\n", child_baton)); /* child_baton corresponds to the root directory baton here */ push_directory(rb, child_baton, "", TRUE /*is_added*/, NULL, SVN_INVALID_REVNUM); } for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi)) { const char *hname = svn__apr_hash_index_key(hi); const char *hval = svn__apr_hash_index_val(hi); /* Parse the different kinds of headers we can encounter and stuff them into the node_baton for writing later */ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0) nb->path = apr_pstrdup(rb->pool, hval); if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0) nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir; if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0) { if (strcmp(hval, "add") == 0) nb->action = svn_node_action_add; if (strcmp(hval, "change") == 0) nb->action = svn_node_action_change; if (strcmp(hval, "delete") == 0) nb->action = svn_node_action_delete; if (strcmp(hval, "replace") == 0) nb->action = svn_node_action_replace; } if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0) nb->base_checksum = apr_pstrdup(rb->pool, hval); if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0) nb->copyfrom_rev = atoi(hval); if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0) nb->copyfrom_path = apr_pstrdup(rb->pool, hval); } nb_dirname = svn_relpath_dirname(nb->path, pool); if (svn_path_compare_paths(nb_dirname, rb->db->relpath) != 0) { char *ancestor_path; apr_size_t residual_close_count; apr_array_header_t *residual_open_path; int i; apr_size_t n; /* Before attempting to handle the action, call open_directory for all the path components and set the directory baton accordingly */ ancestor_path = svn_relpath_get_longest_ancestor(nb_dirname, rb->db->relpath, pool); residual_close_count = svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path, rb->db->relpath)); residual_open_path = svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path, nb_dirname), pool); /* First close all as many directories as there are after skip_ancestor, and then open fresh directories */ for (n = 0; n < residual_close_count; n ++) { /* Don't worry about destroying the actual rb->db object, since the pool we're using has the lifetime of one revision anyway */ LDR_DBG(("Closing dir %p\n", rb->db->baton)); SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); rb->db = rb->db->parent; } for (i = 0; i < residual_open_path->nelts; i ++) { char *relpath_compose = svn_relpath_join(rb->db->relpath, APR_ARRAY_IDX(residual_open_path, i, const char *), rb->pool); SVN_ERR(commit_editor->open_directory(relpath_compose, rb->db->baton, rb->rev - rb->rev_offset - 1, rb->pool, &child_baton)); LDR_DBG(("Opened dir %p\n", child_baton)); push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/, NULL, SVN_INVALID_REVNUM); } }
/* Add PATH to the operations tree rooted at OPERATION, creating any intermediate nodes that are required. Here's what's expected for each action type: ACTION URL REV SRC-FILE PROPNAME ------------ ----- ------- -------- -------- ACTION_MKDIR NULL invalid NULL NULL ACTION_CP valid valid NULL NULL ACTION_PUT NULL invalid valid NULL ACTION_RM NULL invalid NULL NULL ACTION_PROPSET valid invalid NULL valid Node type information is obtained for any copy source (to determine whether to create a file or directory) and for any deleted path (to ensure it exists since svn_delta_editor_t->delete_entry doesn't return an error on non-existent nodes). */ static svn_error_t * build(struct editor_baton *eb, action_code_t action, const char *relpath, const char *url, svn_revnum_t rev, apr_hash_t *props, const char *src_file, svn_revnum_t head, apr_pool_t *scratch_pool) { apr_array_header_t *path_bits = svn_path_decompose(relpath, scratch_pool); const char *path_so_far = ""; struct operation *operation = &eb->root; int i; /* We should only see PROPS when action is ACTION_PROPSET. */ SVN_ERR_ASSERT((props && action == ACTION_PROPSET) || (!props && action != ACTION_PROPSET) ); /* Look for any previous operations we've recognized for PATH. If any of PATH's ancestors have not yet been traversed, we'll be creating OP_OPEN operations for them as we walk down PATH's path components. */ for (i = 0; i < path_bits->nelts; ++i) { const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *); path_so_far = svn_relpath_join(path_so_far, path_bit, scratch_pool); operation = get_operation(path_so_far, operation, eb->edit_pool); } /* Handle property changes. */ if (props) { SVN_ERR(eb->fetch_kind_func(&operation->kind, eb->fetch_kind_baton, relpath, scratch_pool)); /* If we're not adding this thing ourselves, check for existence. */ if (! ((operation->operation == OP_ADD) || (operation->operation == OP_REPLACE))) { if ((operation->kind == svn_node_file) && (operation->operation == OP_OPEN)) operation->operation = OP_PROPSET; } operation->props = svn_prop_hash_dup(props, eb->edit_pool); if (!operation->rev) operation->rev = rev; return SVN_NO_ERROR; } if (action == ACTION_RM) operation->operation = OP_DELETE; /* Handle copy operations (which can be adds or replacements). */ else if (action == ACTION_CP) { operation->operation = operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; SVN_ERR(eb->fetch_kind_func(&operation->kind, eb->fetch_kind_baton, relpath, scratch_pool)); operation->url = url; operation->rev = rev; } /* Handle mkdir operations (which can be adds or replacements). */ else if (action == ACTION_MKDIR) { operation->operation = operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; operation->kind = svn_node_dir; } /* Handle put operations (which can be adds, replacements, or opens). */ else if (action == ACTION_PUT) { if (operation->operation == OP_DELETE) { operation->operation = OP_REPLACE; } else { SVN_ERR(eb->fetch_kind_func(&operation->kind, eb->fetch_kind_baton, relpath, scratch_pool)); if (operation->kind == svn_node_file) operation->operation = OP_OPEN; else if (operation->kind == svn_node_none) operation->operation = OP_ADD; else return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' is not a file", relpath); } operation->kind = svn_node_file; operation->src_file = src_file; } else { /* We shouldn't get here. */ SVN_ERR_MALFUNCTION(); } return SVN_NO_ERROR; }
CStringA CPathUtils::GetAbsoluteURL ( const CStringA& URL , const CStringA& repositoryRootURL , const CStringA& parentPathURL) { CStringA errorResult; SVNPool pool; /* If the URL is already absolute, there is nothing to do. */ const char *canonicalized_url = svn_uri_canonicalize (URL, pool); if (svn_path_is_url (canonicalized_url)) return canonicalized_url; /* Parse the parent directory URL into its parts. */ apr_uri_t parent_dir_parsed_uri; if (apr_uri_parse (pool, parentPathURL, &parent_dir_parsed_uri)) return errorResult; /* 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_parsed_uri.path) parent_dir_parsed_uri.path = apr_pstrmemdup (pool, "/", 1); /* 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 = NULL; apr_array_header_t *relative_components = NULL; /* 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_parsed_uri.path, pool); relative_components = svn_path_decompose (canonicalized_url, pool); } else { apr_uri_t repos_root_parsed_uri; if (apr_uri_parse(pool, repositoryRootURL, &repos_root_parsed_uri)) return errorResult; /* 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_parsed_uri.path) repos_root_parsed_uri.path = apr_pstrmemdup (pool, "/", 1); base_components = svn_path_decompose (repos_root_parsed_uri.path, pool); relative_components = svn_path_decompose (canonicalized_url + 2, pool); } for (int 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_parsed_uri.path = (char *)svn_path_compose(base_components, pool); parent_dir_parsed_uri.query = NULL; parent_dir_parsed_uri.fragment = NULL; return apr_uri_unparse (pool, &parent_dir_parsed_uri, 0); }
/* Add PATH to the operations tree rooted at OPERATION, creating any intermediate nodes that are required. Here's what's expected for each action type: ACTION URL REV SRC-FILE PROPNAME ------------ ----- ------- -------- -------- ACTION_MKDIR NULL invalid NULL NULL ACTION_CP valid valid NULL NULL ACTION_PUT NULL invalid valid NULL ACTION_RM NULL invalid NULL NULL ACTION_PROPSET valid invalid NULL valid ACTION_PROPDEL valid invalid NULL valid Node type information is obtained for any copy source (to determine whether to create a file or directory) and for any deleted path (to ensure it exists since svn_delta_editor_t->delete_entry doesn't return an error on non-existent nodes). */ static svn_error_t * build(action_code_t action, const char *path, const char *url, svn_revnum_t rev, const char *prop_name, const char *prop_value, const char *src_file, svn_revnum_t head, const char *anchor, svn_ra_session_t *session, struct operation *operation, apr_pool_t *pool) { apr_array_header_t *path_bits = svn_path_decompose(path, pool); const char *path_so_far = ""; const char *copy_src = NULL; svn_revnum_t copy_rev = SVN_INVALID_REVNUM; int i; /* Look for any previous operations we've recognized for PATH. If any of PATH's ancestors have not yet been traversed, we'll be creating OP_OPEN operations for them as we walk down PATH's path components. */ for (i = 0; i < path_bits->nelts; ++i) { const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *); path_so_far = svn_path_join(path_so_far, path_bit, pool); operation = get_operation(path_so_far, operation, pool); /* If we cross a replace- or add-with-history, remember the source of those things in case we need to lookup the node kind of one of their children. And if this isn't such a copy, but we've already seen one in of our parent paths, we just need to extend that copy source path by our current path component. */ if (operation->url && SVN_IS_VALID_REVNUM(operation->rev) && (operation->operation == OP_REPLACE || operation->operation == OP_ADD)) { copy_src = subtract_anchor(anchor, operation->url, pool); copy_rev = operation->rev; } else if (copy_src) { copy_src = svn_path_join(copy_src, path_bit, pool); } } /* Handle property changes. */ if (prop_name) { if (operation->operation == OP_DELETE) return svn_error_createf(SVN_ERR_BAD_URL, NULL, "cannot set properties on a location being" " deleted ('%s')", path); SVN_ERR(svn_ra_check_path(session, copy_src ? copy_src : path, copy_src ? copy_rev : head, &operation->kind, pool)); if (operation->kind == svn_node_none) return svn_error_createf(SVN_ERR_BAD_URL, NULL, "propset: '%s' not found", path); else if ((operation->kind == svn_node_file) && (operation->operation == OP_OPEN)) operation->operation = OP_PROPSET; apr_table_set(operation->props, prop_name, prop_value); if (!operation->rev) operation->rev = rev; return SVN_NO_ERROR; } /* We won't fuss about multiple operations on the same path in the following cases: - the prior operation was, in fact, a no-op (open) - the prior operation was a propset placeholder - the prior operation was a deletion Note: while the operation structure certainly supports the ability to do a copy of a file followed by a put of new contents for the file, we don't let that happen (yet). */ if (operation->operation != OP_OPEN && operation->operation != OP_PROPSET && operation->operation != OP_DELETE) return svn_error_createf(SVN_ERR_BAD_URL, NULL, "unsupported multiple operations on '%s'", path); /* For deletions, we validate that there's actually something to delete. If this is a deletion of the child of a copied directory, we need to remember to look in the copy source tree to verify that this thing actually exists. */ if (action == ACTION_RM) { operation->operation = OP_DELETE; SVN_ERR(svn_ra_check_path(session, copy_src ? copy_src : path, copy_src ? copy_rev : head, &operation->kind, pool)); if (operation->kind == svn_node_none) { if (copy_src && strcmp(path, copy_src)) return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' (from '%s:%ld') not found", path, copy_src, copy_rev); else return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found", path); } } /* Handle copy operations (which can be adds or replacements). */ else if (action == ACTION_CP) { if (rev > head) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, "Copy source revision cannot be younger " "than base revision"); operation->operation = operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; if (operation->operation == OP_ADD) { /* There is a bug in the current version of mod_dav_svn which incorrectly replaces existing directories. Therefore we need to check if the target exists and raise an error here. */ SVN_ERR(svn_ra_check_path(session, copy_src ? copy_src : path, copy_src ? copy_rev : head, &operation->kind, pool)); if (operation->kind != svn_node_none) { if (copy_src && strcmp(path, copy_src)) return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' (from '%s:%ld') already exists", path, copy_src, copy_rev); else return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' already exists", path); } } SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool), rev, &operation->kind, pool)); if (operation->kind == svn_node_none) return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found", subtract_anchor(anchor, url, pool)); operation->url = url; operation->rev = rev; } /* Handle mkdir operations (which can be adds or replacements). */ else if (action == ACTION_MKDIR) { operation->operation = operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; operation->kind = svn_node_dir; } /* Handle put operations (which can be adds, replacements, or opens). */ else if (action == ACTION_PUT) { if (operation->operation == OP_DELETE) { operation->operation = OP_REPLACE; } else { SVN_ERR(svn_ra_check_path(session, copy_src ? copy_src : path, copy_src ? copy_rev : head, &operation->kind, pool)); if (operation->kind == svn_node_file) operation->operation = OP_OPEN; else if (operation->kind == svn_node_none) operation->operation = OP_ADD; else return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' is not a file", path); } operation->kind = svn_node_file; operation->src_file = src_file; } else { /* We shouldn't get here. */ abort(); } return SVN_NO_ERROR; }
static svn_error_t * 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; }