| /* |
| * externals.c: handle the svn:externals property |
| * |
| * ==================================================================== |
| * 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. |
| * ==================================================================== |
| */ |
| |
| /* ==================================================================== */ |
| |
| |
| |
| /*** Includes. ***/ |
| |
| #include <apr_uri.h> |
| #include "svn_wc.h" |
| #include "svn_pools.h" |
| #include "svn_client.h" |
| #include "svn_hash.h" |
| #include "svn_types.h" |
| #include "svn_error.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_path.h" |
| #include "svn_config.h" |
| #include "client.h" |
| |
| #include "svn_private_config.h" |
| #include "private/svn_wc_private.h" |
| |
| |
| /* Closure for handle_external_item_change. */ |
| struct handle_external_item_change_baton |
| { |
| /* As returned by svn_wc_parse_externals_description(). */ |
| apr_hash_t *new_desc; |
| apr_hash_t *old_desc; |
| |
| /* The directory that has this externals property. */ |
| const char *parent_dir_abspath; |
| |
| /* The URL for the directory that has this externals property. */ |
| const char *parent_dir_url; |
| |
| /* The URL for the repository root. */ |
| const char *repos_root_url; |
| |
| /* Passed through to svn_client_* functions. */ |
| svn_client_ctx_t *ctx; |
| |
| /* Passed to svn_client_exportX() */ |
| const char *native_eol; |
| |
| svn_boolean_t *timestamp_sleep; |
| svn_boolean_t is_export; |
| |
| /* A long lived pool. Put anything in here that needs to outlive |
| the hash diffing callback, such as updates to the hash |
| entries. */ |
| apr_pool_t *pool; |
| |
| /* A scratchwork pool -- do not put anything in here that needs to |
| outlive the hash diffing callback! */ |
| apr_pool_t *iter_pool; |
| }; |
| |
| |
| struct relegate_dir_external_with_write_lock_baton { |
| const char *local_abspath; |
| svn_wc_context_t *wc_ctx; |
| svn_cancel_func_t cancel_func; |
| void *cancel_baton; |
| }; |
| |
| /* Note: All arguments are in the baton above. |
| * |
| * Remove the directory at LOCAL_ABSPATH from revision control, and do the |
| * same to any revision controlled directories underneath LOCAL_ABSPATH |
| * (including directories not referred to by parent svn administrative areas); |
| * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a |
| * unique name in the same parent directory. |
| * |
| * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control. |
| * |
| * Use SCRATCH_POOL for all temporary allocation. |
| */ |
| static svn_error_t * |
| relegate_dir_external(void *baton, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| struct relegate_dir_external_with_write_lock_baton *b = baton; |
| svn_error_t *err = SVN_NO_ERROR; |
| |
| err = svn_wc_remove_from_revision_control2(b->wc_ctx, b->local_abspath, |
| TRUE, FALSE, |
| b->cancel_func, b->cancel_baton, |
| scratch_pool); |
| if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) |
| { |
| const char *parent_dir; |
| const char *dirname; |
| const char *new_path; |
| |
| svn_error_clear(err); |
| err = SVN_NO_ERROR; |
| |
| svn_dirent_split(&parent_dir, &dirname, b->local_abspath, scratch_pool); |
| |
| /* Reserve the new dir name. */ |
| SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path, |
| parent_dir, dirname, ".OLD", |
| svn_io_file_del_none, |
| scratch_pool, scratch_pool)); |
| |
| /* Sigh... We must fall ever so slightly from grace. |
| |
| Ideally, there would be no window, however brief, when we |
| don't have a reservation on the new name. Unfortunately, |
| at least in the Unix (Linux?) version of apr_file_rename(), |
| you can't rename a directory over a file, because it's just |
| calling stdio rename(), which says: |
| |
| ENOTDIR |
| A component used as a directory in oldpath or newpath |
| path is not, in fact, a directory. Or, oldpath is |
| a directory, and newpath exists but is not a directory |
| |
| So instead, we get the name, then remove the file (ugh), then |
| rename the directory, hoping that nobody has gotten that name |
| in the meantime -- which would never happen in real life, so |
| no big deal. |
| */ |
| /* Do our best, but no biggy if it fails. The rename will fail. */ |
| svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool)); |
| |
| /* Rename. */ |
| SVN_ERR(svn_io_file_rename(b->local_abspath, new_path, scratch_pool)); |
| } |
| |
| return svn_error_return(err); |
| } |
| |
| /* Try to update a directory external at PATH to URL at REVISION. |
| Use POOL for temporary allocations, and use the client context CTX. */ |
| static svn_error_t * |
| switch_dir_external(const char *path, |
| const char *url, |
| const svn_opt_revision_t *revision, |
| const svn_opt_revision_t *peg_revision, |
| svn_boolean_t *timestamp_sleep, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_node_kind_t kind; |
| svn_error_t *err; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| const char *local_abspath; |
| |
| SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); |
| |
| /* If path is a directory, try to update/switch to the correct URL |
| and revision. */ |
| SVN_ERR(svn_io_check_path(path, &kind, pool)); |
| if (kind == svn_node_dir) |
| { |
| const char *node_url; |
| |
| /* Doubles as an "is versioned" check. */ |
| err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath, |
| pool, subpool); |
| if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) |
| { |
| svn_error_clear(err); |
| err = SVN_NO_ERROR; |
| goto relegate; |
| } |
| else if (err) |
| return svn_error_return(err); |
| |
| if (node_url) |
| { |
| const char *repos_root_url; |
| |
| /* If we have what appears to be a version controlled |
| subdir, and its top-level URL matches that of our |
| externals definition, perform an update. */ |
| if (strcmp(node_url, url) == 0) |
| { |
| SVN_ERR(svn_client__update_internal(NULL, local_abspath, |
| revision, svn_depth_unknown, |
| FALSE, FALSE, FALSE, |
| timestamp_sleep, TRUE, FALSE, |
| ctx, subpool)); |
| svn_pool_destroy(subpool); |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR(svn_wc__node_get_repos_info(&repos_root_url, NULL, |
| ctx->wc_ctx, local_abspath, |
| FALSE, FALSE, pool, subpool)); |
| if (repos_root_url) |
| { |
| /* URLs don't match. Try to relocate (if necessary) and then |
| switch. */ |
| if (! svn_uri_is_ancestor(repos_root_url, url)) |
| { |
| const char *repos_root; |
| svn_ra_session_t *ra_session; |
| |
| /* Get the repos root of the new URL. */ |
| SVN_ERR(svn_client__open_ra_session_internal |
| (&ra_session, NULL, url, NULL, NULL, |
| FALSE, TRUE, ctx, subpool)); |
| SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, |
| subpool)); |
| |
| err = svn_client_relocate2(path, repos_root_url, repos_root, |
| FALSE, ctx, subpool); |
| /* If the relocation failed because the new URL points |
| to another repository, then we need to relegate and |
| check out a new WC. */ |
| if (err |
| && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION |
| || (err->apr_err |
| == SVN_ERR_CLIENT_INVALID_RELOCATION))) |
| { |
| svn_error_clear(err); |
| goto relegate; |
| } |
| else if (err) |
| return svn_error_return(err); |
| } |
| |
| SVN_ERR(svn_client__switch_internal(NULL, path, url, |
| peg_revision, revision, |
| svn_depth_infinity, |
| TRUE, timestamp_sleep, |
| FALSE, FALSE, TRUE, ctx, |
| subpool)); |
| |
| svn_pool_destroy(subpool); |
| return SVN_NO_ERROR; |
| } |
| } |
| } |
| |
| relegate: |
| |
| /* Fall back on removing the WC and checking out a new one. */ |
| |
| /* Ensure that we don't have any RA sessions or WC locks from failed |
| operations above. */ |
| svn_pool_destroy(subpool); |
| |
| if (kind == svn_node_dir) |
| { |
| struct relegate_dir_external_with_write_lock_baton baton; |
| |
| baton.local_abspath = local_abspath; |
| baton.wc_ctx = ctx->wc_ctx; |
| baton.cancel_func = ctx->cancel_func; |
| baton.cancel_baton = ctx->cancel_baton; |
| |
| /* Buh-bye, old and busted ... */ |
| SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, local_abspath, |
| FALSE, pool, pool)); |
| |
| SVN_ERR(relegate_dir_external(&baton, pool, pool)); |
| } |
| else |
| { |
| /* The target dir might have multiple components. Guarantee |
| the path leading down to the last component. */ |
| const char *parent = svn_dirent_dirname(path, pool); |
| SVN_ERR(svn_io_make_dir_recursively(parent, pool)); |
| } |
| |
| /* ... Hello, new hotness. */ |
| return svn_client__checkout_internal(NULL, url, local_abspath, peg_revision, |
| revision, NULL, svn_depth_infinity, |
| FALSE, FALSE, TRUE, timestamp_sleep, |
| ctx, pool); |
| } |
| |
| /* Try to update a file external at PATH to URL at REVISION using a |
| access baton that has a write lock. Use POOL for temporary |
| allocations, and use the client context CTX. */ |
| static svn_error_t * |
| switch_file_external(const char *path, |
| const char *url, |
| const svn_opt_revision_t *peg_revision, |
| const svn_opt_revision_t *revision, |
| svn_ra_session_t *ra_session, |
| const char *ra_session_url, |
| svn_revnum_t ra_revnum, |
| const char *repos_root_url, |
| svn_boolean_t *timestamp_sleep, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create(pool); |
| const char *anchor; |
| const char *anchor_abspath; |
| const char *local_abspath; |
| const char *target; |
| svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, |
| SVN_CONFIG_CATEGORY_CONFIG, |
| APR_HASH_KEY_STRING) : NULL; |
| svn_boolean_t use_commit_times; |
| svn_boolean_t unlink_file = FALSE; |
| svn_boolean_t revert_file = FALSE; |
| svn_boolean_t remove_from_revision_control = FALSE; |
| svn_boolean_t locked_here; |
| svn_error_t *err = NULL; |
| svn_node_kind_t kind; |
| |
| /* See if the user wants last-commit timestamps instead of current ones. */ |
| SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, |
| SVN_CONFIG_SECTION_MISCELLANY, |
| SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); |
| |
| /* There must be a working copy to place the file external into. */ |
| SVN_ERR(svn_wc_get_actual_target2(&anchor, &target, ctx->wc_ctx, path, |
| subpool, subpool)); |
| SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, subpool)); |
| SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, subpool)); |
| |
| /* Try to get a access baton for the anchor using the input access |
| baton. If this fails and returns SVN_ERR_WC_NOT_LOCKED, then try |
| to get a new access baton to support inserting a file external |
| into a directory external. */ |
| SVN_ERR(svn_wc_locked2(&locked_here, NULL, ctx->wc_ctx, anchor_abspath, |
| subpool)); |
| if (!locked_here) |
| { |
| const char *dest_wc_repos_root_url; |
| svn_opt_revision_t peg_rev; |
| |
| /* Check that the repository root URL for the newly opened |
| wc is the same as the file external. */ |
| peg_rev.kind = svn_opt_revision_base; |
| SVN_ERR(svn_client__get_repos_root(&dest_wc_repos_root_url, |
| anchor_abspath, &peg_rev, |
| ctx, subpool, subpool)); |
| |
| if (0 != strcmp(repos_root_url, dest_wc_repos_root_url)) |
| return svn_error_createf |
| (SVN_ERR_RA_REPOS_ROOT_URL_MISMATCH, NULL, |
| _("Cannot insert a file external from '%s' into a working " |
| "copy from a different repository rooted at '%s'"), |
| url, dest_wc_repos_root_url); |
| |
| SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, anchor_abspath, |
| FALSE, subpool, subpool)); |
| } |
| |
| err = svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, FALSE, subpool); |
| if (err) |
| goto cleanup; |
| |
| |
| /* Only one notification is done for the external, so don't notify |
| for any following steps. Use the following trick to add the file |
| then switch it to the external URL. */ |
| |
| /* If there is a versioned item with this name, ensure it's a file |
| external before working with it. If there is no entry in the |
| working copy, then create an empty file and add it to the working |
| copy. */ |
| if (kind != svn_node_none && kind != svn_node_unknown) |
| { |
| svn_boolean_t file_external; |
| err = svn_wc__node_is_file_external(&file_external, ctx->wc_ctx, |
| local_abspath, subpool); |
| if (err) |
| goto cleanup; |
| |
| if (! file_external) |
| { |
| if (!locked_here) |
| SVN_ERR(svn_wc__release_write_lock(ctx->wc_ctx, anchor_abspath, |
| subpool)); |
| |
| return svn_error_createf( |
| SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0, |
| _("The file external from '%s' cannot overwrite the existing " |
| "versioned item at '%s'"), |
| url, svn_dirent_local_style(path, pool)); |
| } |
| } |
| else |
| { |
| apr_file_t *f; |
| svn_boolean_t text_conflicted; |
| svn_boolean_t prop_conflicted; |
| svn_boolean_t tree_conflicted; |
| |
| /* Check for a conflict on the containing directory. Because a |
| switch is done on the added file later, it will leave a |
| conflict on the directory. To prevent resolving a conflict |
| due to another change on the directory, do not allow a file |
| external to be added when one exists. */ |
| err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted, |
| &tree_conflicted, ctx->wc_ctx, |
| anchor_abspath, subpool); |
| if (err) |
| goto cleanup; |
| |
| if (text_conflicted || prop_conflicted || tree_conflicted) |
| return svn_error_createf |
| (SVN_ERR_WC_FOUND_CONFLICT, 0, |
| _("The file external from '%s' cannot be written to '%s' while " |
| "'%s' remains in conflict"), |
| url, path, anchor); |
| |
| /* Try to create an empty file. If there is a file already |
| there, then don't touch it. */ |
| err = svn_io_file_open(&f, |
| local_abspath, |
| APR_WRITE | APR_CREATE | APR_EXCL, |
| APR_OS_DEFAULT, |
| subpool); |
| if (err) |
| goto cleanup; |
| |
| unlink_file = TRUE; |
| err = svn_io_file_close(f, pool); |
| if (err) |
| goto cleanup; |
| |
| err = svn_wc__register_file_external(ctx->wc_ctx, local_abspath, url, |
| peg_revision, revision, subpool); |
| if (err) |
| goto cleanup; |
| } |
| |
| err = svn_client__switch_internal(NULL, path, url, peg_revision, revision, |
| svn_depth_empty, |
| FALSE, /* depth_is_sticky */ |
| timestamp_sleep, |
| TRUE, /* ignore_externals */ |
| FALSE, /* allow_unver_obstructions */ |
| FALSE, /* innerswitch */ |
| ctx, |
| pool); |
| if (err) |
| goto cleanup; |
| |
| /* Do some additional work if the file external is newly added to |
| the wc. */ |
| if (unlink_file) |
| { |
| /* At this point the newly created file external is switched, so |
| if there is an error, to back everything out the file cannot |
| be reverted, it needs to be removed forcibly from the wc.. */ |
| revert_file = FALSE; |
| remove_from_revision_control = TRUE; |
| } |
| |
| if (!locked_here) |
| SVN_ERR(svn_wc__release_write_lock(ctx->wc_ctx, anchor_abspath, subpool)); |
| |
| svn_pool_destroy(subpool); |
| return SVN_NO_ERROR; |
| |
| cleanup: |
| if (revert_file) |
| { |
| svn_error_clear( |
| svn_wc_revert4(ctx->wc_ctx, local_abspath, svn_depth_empty, |
| use_commit_times, |
| NULL, /* apr_array_header_t *changelists */ |
| ctx->cancel_func, |
| ctx->cancel_baton, |
| NULL, /* svn_wc_notify_func2_t */ |
| NULL, /* void *notify_baton */ |
| subpool)); |
| } |
| |
| if (remove_from_revision_control) |
| { |
| svn_error_clear( |
| svn_wc_remove_from_revision_control2(ctx->wc_ctx, local_abspath, |
| TRUE, FALSE, |
| ctx->cancel_func, |
| ctx->cancel_baton, |
| subpool)); |
| } |
| |
| if (unlink_file) |
| svn_error_clear(svn_io_remove_file2(path, TRUE, subpool)); |
| |
| if (!locked_here) |
| svn_error_clear(svn_wc__release_write_lock(ctx->wc_ctx, anchor_abspath, |
| subpool)); |
| |
| svn_pool_destroy(subpool); |
| return svn_error_return(err); |
| } |
| |
| /* 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); |
| } |
| |
| /* If the URL for @a item is relative, then using the repository root |
| URL @a repos_root_url and the parent directory URL @parent_dir_url, |
| resolve it into an absolute URL and save it in @a item. |
| |
| Regardless if the URL is absolute or not, if there are no errors, |
| the URL in @a item will be canonicalized. |
| |
| The following relative URL formats are supported: |
| |
| ../ relative to the parent directory of the external |
| ^/ relative to the repository root |
| // relative to the scheme |
| / relative to the server's hostname |
| |
| The ../ and ^/ relative URLs may use .. to remove path elements up |
| to the server root. |
| |
| The external URL should not be canonicalized otherwise the scheme |
| relative URL '//host/some/path' would have been canonicalized to |
| '/host/some/path' and we would not be able to match on the leading |
| '//'. */ |
| static svn_error_t * |
| resolve_relative_external_url(svn_wc_external_item2_t *item, |
| const char *repos_root_url, |
| const char *parent_dir_url, |
| apr_pool_t *pool) |
| { |
| const char *url = item->url; |
| apr_uri_t parent_dir_uri; |
| apr_status_t status; |
| |
| /* If the URL is already absolute, there is nothing to do. */ |
| if (svn_path_is_url(url)) |
| { |
| /* "http://server/path" */ |
| item->url = svn_uri_canonicalize(url, 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(pool, |
| apr_pstrndup(pool, url, num_leading_slashes), |
| svn_relpath_canonicalize(url + num_leading_slashes, |
| pool), |
| NULL); |
| } |
| else |
| { |
| /* "^/path" and "../path" */ |
| url = svn_relpath_canonicalize(url, pool); |
| } |
| |
| /* Parse the parent directory URL into its parts. */ |
| status = apr_uri_parse(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(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, pool); |
| relative_components = svn_path_decompose(url, pool); |
| } |
| else |
| { |
| apr_uri_t repos_root_uri; |
| |
| status = apr_uri_parse(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(pool, "/", 1); |
| |
| base_components = svn_path_decompose(repos_root_uri.path, |
| pool); |
| relative_components = svn_path_decompose(url + 2, 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, pool); |
| item->url = svn_uri_canonicalize(apr_uri_unparse(pool, &parent_dir_uri, |
| 0), pool); |
| return SVN_NO_ERROR; |
| } |
| |
| /* The remaining URLs are relative to the 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 + 2)) |
| 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, pool)); |
| item->url = svn_uri_canonicalize(apr_pstrcat(pool, scheme, ":", |
| url, NULL), 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; |
| item->url = svn_uri_canonicalize(apr_uri_unparse(pool, &parent_dir_uri, |
| 0), pool); |
| return SVN_NO_ERROR; |
| } |
| |
| return svn_error_createf(SVN_ERR_BAD_URL, 0, |
| _("Unrecognized format for the relative external " |
| "URL '%s'"), |
| item->url); |
| } |
| |
| /* This implements the 'svn_hash_diff_func_t' interface. |
| BATON is of type 'struct handle_external_item_change_baton *'. */ |
| static svn_error_t * |
| handle_external_item_change(const void *key, apr_ssize_t klen, |
| enum svn_hash_diff_key_status status, |
| void *baton) |
| { |
| struct handle_external_item_change_baton *ib = baton; |
| svn_wc_external_item2_t *old_item, *new_item; |
| const char *local_abspath = svn_dirent_join(ib->parent_dir_abspath, |
| (const char *) key, |
| ib->iter_pool); |
| svn_ra_session_t *ra_session; |
| svn_node_kind_t kind; |
| svn_client__ra_session_from_path_results ra_cache = { 0 }; |
| |
| /* Don't bother to check status, since we'll get that for free by |
| attempting to retrieve the hash values anyway. */ |
| |
| /* When creating the absolute URL, use the pool and not the |
| iterpool, since the hash table values outlive the iterpool and |
| any pointers they have should also outlive the iterpool. */ |
| if ((ib->old_desc) && (! ib->is_export)) |
| { |
| old_item = apr_hash_get(ib->old_desc, key, klen); |
| if (old_item) |
| SVN_ERR(resolve_relative_external_url(old_item, ib->repos_root_url, |
| ib->parent_dir_url, |
| ib->pool)); |
| } |
| else |
| old_item = NULL; |
| |
| if (ib->new_desc) |
| { |
| new_item = apr_hash_get(ib->new_desc, key, klen); |
| if (new_item) |
| SVN_ERR(resolve_relative_external_url(new_item, ib->repos_root_url, |
| ib->parent_dir_url, |
| ib->pool)); |
| } |
| else |
| new_item = NULL; |
| |
| /* We couldn't possibly be here if both values were null, right? */ |
| SVN_ERR_ASSERT(old_item || new_item); |
| |
| /* There's one potential ugliness. If a target subdir changed, but |
| its URL did not, then ideally we'd just rename the subdir, rather |
| than remove the old subdir only to do a new checkout into the new |
| subdir. |
| |
| We could solve this by "sneaking around the back" and looking in |
| ib->new_desc, ib->old_desc to check if anything else in this |
| parent_dir has the same URL. Of course, if an external gets |
| moved into some other directory, then we'd lose anyway. The only |
| way to fully handle this would be to harvest a global list based |
| on urls/revs, and consult the list every time we're about to |
| delete an external subdir: whenever a deletion is really part of |
| a rename, then we'd do the rename on the spot. |
| |
| IMHO, renames aren't going to be frequent enough to make the |
| extra bookkeeping worthwhile. |
| */ |
| |
| /* If the external is being checked out, exported or updated, |
| determine if the external is a file or directory. */ |
| if (new_item) |
| { |
| /* Get the RA connection. */ |
| SVN_ERR(svn_client__ra_session_from_path(&ra_session, |
| &ra_cache.ra_revnum, |
| &ra_cache.ra_session_url, |
| new_item->url, NULL, |
| &(new_item->peg_revision), |
| &(new_item->revision), ib->ctx, |
| ib->iter_pool)); |
| |
| SVN_ERR(svn_ra_get_uuid2(ra_session, &ra_cache.repos_uuid, |
| ib->iter_pool)); |
| SVN_ERR(svn_ra_get_repos_root2(ra_session, &ra_cache.repos_root_url, |
| ib->iter_pool)); |
| SVN_ERR(svn_ra_check_path(ra_session, "", ra_cache.ra_revnum, &kind, |
| ib->iter_pool)); |
| |
| if (svn_node_none == kind) |
| return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
| _("URL '%s' at revision %ld doesn't exist"), |
| ra_cache.ra_session_url, |
| ra_cache.ra_revnum); |
| |
| if (svn_node_dir != kind && svn_node_file != kind) |
| return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
| _("URL '%s' at revision %ld is not a file " |
| "or a directory"), |
| ra_cache.ra_session_url, |
| ra_cache.ra_revnum); |
| |
| ra_cache.kind_p = &kind; |
| } |
| |
| /* Not protecting against recursive externals. Detecting them in |
| the global case is hard, and it should be pretty obvious to a |
| user when it happens. Worst case: your disk fills up :-). */ |
| |
| if (! old_item) |
| { |
| /* This branch is only used during a checkout or an export. */ |
| const char *parent_abspath; |
| |
| /* First notify that we're about to handle an external. */ |
| if (ib->ctx->notify_func2) |
| (*ib->ctx->notify_func2) |
| (ib->ctx->notify_baton2, |
| svn_wc_create_notify(local_abspath, svn_wc_notify_update_external, |
| ib->iter_pool), ib->iter_pool); |
| |
| switch (*ra_cache.kind_p) |
| { |
| case svn_node_dir: |
| /* The target dir might have multiple components. Guarantee |
| the path leading down to the last component. */ |
| parent_abspath = svn_dirent_dirname(local_abspath, ib->iter_pool); |
| SVN_ERR(svn_io_make_dir_recursively(parent_abspath, ib->iter_pool)); |
| |
| /* If we were handling renames the fancy way, then before |
| checking out a new subdir here, we would somehow learn if |
| it's really just a rename of an old one. That would work in |
| tandem with the next case -- this case would do nothing, |
| knowing that the next case either already has, or soon will, |
| rename the external subdirectory. */ |
| |
| if (ib->is_export) |
| /* ### It should be okay to "force" this export. Externals |
| only get created in subdirectories of versioned |
| directories, so an external directory couldn't already |
| exist before the parent export process unless a versioned |
| directory above it did, which means the user would have |
| already had to force these creations to occur. */ |
| SVN_ERR(svn_client_export4(NULL, new_item->url, local_abspath, |
| &(new_item->peg_revision), |
| &(new_item->revision), |
| TRUE, FALSE, svn_depth_infinity, |
| ib->native_eol, |
| ib->ctx, ib->iter_pool)); |
| else |
| SVN_ERR(svn_client__checkout_internal |
| (NULL, new_item->url, local_abspath, |
| &(new_item->peg_revision), &(new_item->revision), |
| &ra_cache, |
| SVN_DEPTH_INFINITY_OR_FILES(TRUE), |
| FALSE, FALSE, TRUE, ib->timestamp_sleep, ib->ctx, |
| ib->iter_pool)); |
| break; |
| case svn_node_file: |
| if (ib->is_export) |
| /* Do not overwrite an existing file with this file |
| external. */ |
| SVN_ERR(svn_client_export4(NULL, new_item->url, local_abspath, |
| &(new_item->peg_revision), |
| &(new_item->revision), |
| FALSE, TRUE, svn_depth_infinity, |
| ib->native_eol, |
| ib->ctx, ib->iter_pool)); |
| else |
| SVN_ERR(switch_file_external(local_abspath, |
| new_item->url, |
| &new_item->peg_revision, |
| &new_item->revision, |
| ra_session, |
| ra_cache.ra_session_url, |
| ra_cache.ra_revnum, |
| ra_cache.repos_root_url, |
| ib->timestamp_sleep, ib->ctx, |
| ib->iter_pool)); |
| break; |
| default: |
| SVN_ERR_MALFUNCTION(); |
| break; |
| } |
| } |
| else if (! new_item) |
| { |
| /* This branch is only used when an external is deleted from the |
| repository and the working copy is updated. */ |
| |
| /* See comment in above case about fancy rename handling. Here, |
| before removing an old subdir, we would see if it wants to |
| just be renamed to a new one. */ |
| |
| svn_error_t *err; |
| svn_boolean_t lock_existed; |
| |
| SVN_ERR(svn_wc_locked2(&lock_existed, NULL, ib->ctx->wc_ctx, |
| local_abspath, ib->iter_pool)); |
| |
| if (! lock_existed) |
| { |
| SVN_ERR(svn_wc__acquire_write_lock(NULL, ib->ctx->wc_ctx, |
| local_abspath, FALSE, |
| ib->iter_pool, |
| ib->iter_pool)); |
| } |
| |
| /* We don't use relegate_dir_external() here, because we know that |
| nothing else in this externals description (at least) is |
| going to need this directory, and therefore it's better to |
| leave stuff where the user expects it. */ |
| err = svn_wc_remove_from_revision_control2( |
| ib->ctx->wc_ctx, local_abspath, TRUE, FALSE, |
| ib->ctx->cancel_func, ib->ctx->cancel_baton, |
| ib->iter_pool); |
| |
| if (ib->ctx->notify_func2) |
| { |
| svn_wc_notify_t *notify = |
| svn_wc_create_notify(local_abspath, |
| svn_wc_notify_update_external_removed, |
| ib->iter_pool); |
| |
| notify->kind = svn_node_dir; |
| notify->err = err; |
| |
| (ib->ctx->notify_func2)(ib->ctx->notify_baton2, |
| notify, ib->iter_pool); |
| } |
| |
| /* ### Ugly. Unlock only if not going to return an error. Revisit */ |
| if (! lock_existed |
| && (! err || err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) |
| { |
| svn_error_t *err2 = svn_wc__release_write_lock(ib->ctx->wc_ctx, |
| local_abspath, |
| ib->iter_pool); |
| if (err2) |
| { |
| if (! err) |
| err = err2; |
| else |
| svn_error_clear(err2); |
| } |
| } |
| |
| if (err && (err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD)) |
| return svn_error_return(err); |
| svn_error_clear(err); |
| |
| /* ### If there were multiple path components leading down to |
| that wc, we could try to remove them too. */ |
| } |
| else |
| { |
| /* This branch handles all other changes. */ |
| |
| /* First notify that we're about to handle an external. */ |
| if (ib->ctx->notify_func2) |
| (*ib->ctx->notify_func2) |
| (ib->ctx->notify_baton2, |
| svn_wc_create_notify(local_abspath, svn_wc_notify_update_external, |
| ib->iter_pool), ib->iter_pool); |
| |
| /* Either the URL changed, or the exact same item is present in |
| both hashes, and caller wants to update such unchanged items. |
| In the latter case, the call below will try to make sure that |
| the external really is a WC pointing to the correct |
| URL/revision. */ |
| switch (*ra_cache.kind_p) |
| { |
| case svn_node_dir: |
| SVN_ERR(switch_dir_external(local_abspath, new_item->url, |
| &(new_item->revision), |
| &(new_item->peg_revision), |
| ib->timestamp_sleep, ib->ctx, |
| ib->iter_pool)); |
| break; |
| case svn_node_file: |
| SVN_ERR(switch_file_external(local_abspath, |
| new_item->url, |
| &new_item->peg_revision, |
| &new_item->revision, |
| ra_session, |
| ra_cache.ra_session_url, |
| ra_cache.ra_revnum, |
| ra_cache.repos_root_url, |
| ib->timestamp_sleep, ib->ctx, |
| ib->iter_pool)); |
| break; |
| default: |
| SVN_ERR_MALFUNCTION(); |
| break; |
| } |
| } |
| |
| /* Clear ib->iter_pool -- we only use it for scratchwork (and this will |
| close any RA sessions still open in this pool). */ |
| svn_pool_clear(ib->iter_pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| handle_external_item_change_wrapper(const void *key, apr_ssize_t klen, |
| enum svn_hash_diff_key_status status, |
| void *baton) |
| { |
| struct handle_external_item_change_baton *ib = baton; |
| svn_error_t *err = handle_external_item_change(key, klen, status, baton); |
| |
| if (err && err->apr_err != SVN_ERR_CANCELLED) |
| { |
| if (ib->ctx->notify_func2) |
| { |
| const char *local_abspath = svn_dirent_join(ib->parent_dir_abspath, |
| key, ib->iter_pool); |
| svn_wc_notify_t *notifier = |
| svn_wc_create_notify(local_abspath, |
| svn_wc_notify_failed_external, |
| ib->iter_pool); |
| notifier->err = err; |
| ib->ctx->notify_func2(ib->ctx->notify_baton2, notifier, |
| ib->iter_pool); |
| } |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| |
| return svn_error_return(err); |
| } |
| |
| |
| /* Closure for handle_externals_change. */ |
| struct handle_externals_desc_change_baton |
| { |
| /* As returned by svn_wc_edited_externals(). */ |
| apr_hash_t *externals_new; |
| apr_hash_t *externals_old; |
| |
| /* The requested depth of the driving operation (e.g., update, switch). */ |
| svn_depth_t requested_depth; |
| |
| /* As returned by svn_wc_traversed_depths(). NULL means no ambient |
| depths available (e.g., svn export). */ |
| apr_hash_t *ambient_depths; |
| |
| /* These two map a URL to a path where the URL is either checked out |
| to or exported to. The to_path must be a substring of the |
| external item parent directory path. */ |
| const char *from_url; |
| const char *to_abspath; |
| |
| /* Passed through to handle_external_item_change_baton. */ |
| svn_client_ctx_t *ctx; |
| const char *repos_root_url; |
| svn_boolean_t *timestamp_sleep; |
| svn_boolean_t is_export; |
| |
| /* Passed to svn_client_exportX() */ |
| const char *native_eol; |
| |
| apr_pool_t *pool; |
| }; |
| |
| |
| /* This implements the 'svn_hash_diff_func_t' interface. |
| BATON is of type 'struct handle_externals_desc_change_baton *'. |
| */ |
| static svn_error_t * |
| handle_externals_desc_change(const void *key, apr_ssize_t klen, |
| enum svn_hash_diff_key_status status, |
| void *baton) |
| { |
| struct handle_externals_desc_change_baton *cb = baton; |
| struct handle_external_item_change_baton ib = { 0 }; |
| const char *old_desc_text, *new_desc_text; |
| apr_array_header_t *old_desc, *new_desc; |
| apr_hash_t *old_desc_hash, *new_desc_hash; |
| apr_size_t len; |
| int i; |
| svn_wc_external_item2_t *item; |
| const char *ambient_depth_w; |
| svn_depth_t ambient_depth; |
| const char *url; |
| svn_error_t *err; |
| |
| if (cb->ambient_depths) |
| { |
| ambient_depth_w = apr_hash_get(cb->ambient_depths, key, klen); |
| if (ambient_depth_w == NULL) |
| { |
| return svn_error_createf |
| (SVN_ERR_WC_CORRUPT, NULL, |
| _("Traversal of '%s' found no ambient depth"), |
| (const char *) key); |
| } |
| else |
| { |
| ambient_depth = svn_depth_from_word(ambient_depth_w); |
| } |
| } |
| else |
| { |
| ambient_depth = svn_depth_infinity; |
| } |
| |
| /* Bag out if the depth here is too shallow for externals action. */ |
| if ((cb->requested_depth < svn_depth_infinity |
| && cb->requested_depth != svn_depth_unknown) |
| || (ambient_depth < svn_depth_infinity |
| && cb->requested_depth < svn_depth_infinity)) |
| return SVN_NO_ERROR; |
| |
| if ((old_desc_text = apr_hash_get(cb->externals_old, key, klen))) |
| SVN_ERR(svn_wc_parse_externals_description3(&old_desc, key, old_desc_text, |
| FALSE, cb->pool)); |
| else |
| old_desc = NULL; |
| |
| if ((new_desc_text = apr_hash_get(cb->externals_new, key, klen))) |
| SVN_ERR(svn_wc_parse_externals_description3(&new_desc, key, new_desc_text, |
| FALSE, cb->pool)); |
| else |
| new_desc = NULL; |
| |
| old_desc_hash = apr_hash_make(cb->pool); |
| new_desc_hash = apr_hash_make(cb->pool); |
| |
| /* Create hashes of our two externals arrays so that we can |
| efficiently generate a diff for them. */ |
| for (i = 0; old_desc && (i < old_desc->nelts); i++) |
| { |
| item = APR_ARRAY_IDX(old_desc, i, svn_wc_external_item2_t *); |
| |
| apr_hash_set(old_desc_hash, item->target_dir, |
| APR_HASH_KEY_STRING, item); |
| } |
| |
| for (i = 0; new_desc && (i < new_desc->nelts); i++) |
| { |
| item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *); |
| |
| apr_hash_set(new_desc_hash, item->target_dir, |
| APR_HASH_KEY_STRING, item); |
| } |
| |
| ib.old_desc = old_desc_hash; |
| ib.new_desc = new_desc_hash; |
| ib.repos_root_url = cb->repos_root_url; |
| ib.ctx = cb->ctx; |
| ib.is_export = cb->is_export; |
| ib.native_eol = cb->native_eol; |
| ib.timestamp_sleep = cb->timestamp_sleep; |
| ib.pool = cb->pool; |
| ib.iter_pool = svn_pool_create(cb->pool); |
| SVN_ERR(svn_dirent_get_absolute(&ib.parent_dir_abspath, (const char *) key, |
| cb->pool)); |
| |
| err = svn_wc__node_get_url(&url, cb->ctx->wc_ctx, ib.parent_dir_abspath, |
| cb->pool, cb->pool); |
| |
| /* If we're doing an 'svn export' the current dir will not be a |
| working copy. We can't get the parent_dir. */ |
| if (err) |
| { |
| if (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY || |
| err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) |
| { |
| /* Get the URL of the parent directory by appending a portion of |
| parent_dir to from_url. from_url is the URL for to_path and |
| to_path is a substring of parent_dir, so append any characters in |
| parent_dir past strlen(to_path) to from_url (making sure to move |
| past a '/' in parent_dir, otherwise svn_path_url_add_component() |
| will error. */ |
| len = strlen(cb->to_abspath); |
| if (ib.parent_dir_abspath[len] == '/') |
| ++len; |
| ib.parent_dir_url = svn_path_url_add_component2(cb->from_url, |
| ib.parent_dir_abspath + len, |
| cb->pool); |
| svn_error_clear(err); |
| } |
| else |
| return svn_error_return(err); |
| } |
| else |
| ib.parent_dir_url = url; |
| |
| /* We must use a custom version of svn_hash_diff so that the diff |
| entries are processed in the order they were originally specified |
| in the svn:externals properties. */ |
| |
| for (i = 0; old_desc && (i < old_desc->nelts); i++) |
| { |
| item = APR_ARRAY_IDX(old_desc, i, svn_wc_external_item2_t *); |
| |
| if (apr_hash_get(new_desc_hash, item->target_dir, APR_HASH_KEY_STRING)) |
| SVN_ERR(handle_external_item_change_wrapper(item->target_dir, |
| APR_HASH_KEY_STRING, |
| svn_hash_diff_key_both, |
| &ib)); |
| else |
| SVN_ERR(handle_external_item_change_wrapper(item->target_dir, |
| APR_HASH_KEY_STRING, |
| svn_hash_diff_key_a, |
| &ib)); |
| } |
| for (i = 0; new_desc && (i < new_desc->nelts); i++) |
| { |
| item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *); |
| if (! apr_hash_get(old_desc_hash, item->target_dir, APR_HASH_KEY_STRING)) |
| SVN_ERR(handle_external_item_change_wrapper(item->target_dir, |
| APR_HASH_KEY_STRING, |
| svn_hash_diff_key_b, |
| &ib)); |
| } |
| |
| /* Now destroy the subpool we pass to the hash differ. This will |
| close any remaining RA sessions used by the hash diff callback. */ |
| svn_pool_destroy(ib.iter_pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_client__handle_externals(apr_hash_t *externals_old, |
| apr_hash_t *externals_new, |
| apr_hash_t *ambient_depths, |
| const char *from_url, |
| const char *to_abspath, |
| const char *repos_root_url, |
| svn_depth_t requested_depth, |
| svn_boolean_t *timestamp_sleep, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| struct handle_externals_desc_change_baton cb = { 0 }; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); |
| |
| /* Sanity check; see r30124. */ |
| if (! svn_path_is_url(from_url)) |
| return svn_error_createf |
| (SVN_ERR_BAD_URL, NULL, _("'%s' is not a URL"), from_url); |
| |
| cb.externals_new = externals_new; |
| cb.externals_old = externals_old; |
| cb.requested_depth = requested_depth; |
| cb.ambient_depths = ambient_depths; |
| cb.from_url = from_url; |
| cb.to_abspath = to_abspath; |
| cb.repos_root_url = repos_root_url; |
| cb.ctx = ctx; |
| cb.timestamp_sleep = timestamp_sleep; |
| cb.is_export = FALSE; |
| cb.native_eol = NULL; |
| cb.pool = pool; |
| |
| return svn_hash_diff(cb.externals_old, cb.externals_new, |
| handle_externals_desc_change, &cb, pool); |
| } |
| |
| |
| svn_error_t * |
| svn_client__fetch_externals(apr_hash_t *externals, |
| const char *from_url, |
| const char *to_abspath, |
| const char *repos_root_url, |
| svn_depth_t requested_depth, |
| svn_boolean_t is_export, |
| const char *native_eol, |
| svn_boolean_t *timestamp_sleep, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| struct handle_externals_desc_change_baton cb = { 0 }; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); |
| |
| cb.externals_new = externals; |
| cb.externals_old = apr_hash_make(pool); |
| cb.requested_depth = requested_depth; |
| cb.ambient_depths = NULL; |
| cb.ctx = ctx; |
| cb.from_url = from_url; |
| cb.to_abspath = to_abspath; |
| cb.repos_root_url = repos_root_url; |
| cb.timestamp_sleep = timestamp_sleep; |
| cb.native_eol = native_eol; |
| cb.is_export = is_export; |
| cb.pool = pool; |
| |
| return svn_hash_diff(cb.externals_old, cb.externals_new, |
| handle_externals_desc_change, &cb, pool); |
| } |
| |
| |
| svn_error_t * |
| svn_client__do_external_status(svn_client_ctx_t *ctx, |
| apr_hash_t *externals_new, |
| svn_depth_t depth, |
| svn_boolean_t get_all, |
| svn_boolean_t update, |
| svn_boolean_t no_ignore, |
| svn_client_status_func_t status_func, |
| void *status_baton, |
| apr_pool_t *pool) |
| { |
| apr_hash_index_t *hi; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| |
| /* Loop over the hash of new values (we don't care about the old |
| ones). This is a mapping of versioned directories to property |
| values. */ |
| for (hi = apr_hash_first(pool, externals_new); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| apr_array_header_t *exts; |
| const char *path = svn__apr_hash_index_key(hi); |
| const char *propval = svn__apr_hash_index_val(hi); |
| apr_pool_t *iterpool; |
| int i; |
| |
| /* Clear the subpool. */ |
| svn_pool_clear(subpool); |
| |
| /* Parse the svn:externals property value. This results in a |
| hash mapping subdirectories to externals structures. */ |
| SVN_ERR(svn_wc_parse_externals_description3(&exts, path, propval, |
| FALSE, subpool)); |
| |
| /* Make a sub-pool of SUBPOOL. */ |
| iterpool = svn_pool_create(subpool); |
| |
| /* Loop over the subdir array. */ |
| for (i = 0; exts && (i < exts->nelts); i++) |
| { |
| const char *fullpath; |
| svn_wc_external_item2_t *external; |
| svn_node_kind_t kind; |
| |
| svn_pool_clear(iterpool); |
| |
| external = APR_ARRAY_IDX(exts, i, svn_wc_external_item2_t *); |
| fullpath = svn_dirent_join(path, external->target_dir, iterpool); |
| |
| /* If the external target directory doesn't exist on disk, |
| just skip it. */ |
| SVN_ERR(svn_io_check_path(fullpath, &kind, iterpool)); |
| if (kind != svn_node_dir) |
| continue; |
| |
| /* Tell the client we're staring an external status set. */ |
| if (ctx->notify_func2) |
| (ctx->notify_func2) |
| (ctx->notify_baton2, |
| svn_wc_create_notify(fullpath, svn_wc_notify_status_external, |
| iterpool), iterpool); |
| |
| /* And then do the status. */ |
| SVN_ERR(svn_client_status5(NULL, ctx, fullpath, |
| &(external->revision), |
| depth, get_all, update, |
| no_ignore, FALSE, FALSE, NULL, |
| status_func, status_baton, |
| iterpool)); |
| } |
| } |
| |
| /* Destroy SUBPOOL and (implicitly) ITERPOOL. */ |
| svn_pool_destroy(subpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Implements the `svn_wc_externals_update_t' interface. */ |
| svn_error_t * |
| svn_client__external_info_gatherer(void *baton, |
| const char *local_abspath, |
| const svn_string_t *old_value, |
| const svn_string_t *new_value, |
| svn_depth_t depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_client__external_func_baton_t *efb = baton; |
| |
| local_abspath = apr_pstrdup(efb->result_pool, local_abspath); |
| |
| if (efb->externals_old != NULL && old_value != NULL) |
| apr_hash_set(efb->externals_old, local_abspath, APR_HASH_KEY_STRING, |
| apr_pstrndup(efb->result_pool, |
| old_value->data, old_value->len)); |
| |
| if (efb->externals_new != NULL && new_value != NULL) |
| apr_hash_set(efb->externals_new, local_abspath, APR_HASH_KEY_STRING, |
| apr_pstrndup(efb->result_pool, |
| new_value->data, new_value->len)); |
| |
| if (efb->ambient_depths != NULL) |
| apr_hash_set(efb->ambient_depths, local_abspath, APR_HASH_KEY_STRING, |
| svn_depth_to_word(depth)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Callback of type svn_wc_external_update_t. Just squirrels away an |
| svn:externals property value into BATON (which is an apr_hash_t * |
| keyed on local absolute path). */ |
| static svn_error_t * |
| externals_update_func(void *baton, |
| const char *local_abspath, |
| const svn_string_t *old_val, |
| const svn_string_t *new_val, |
| svn_depth_t depth, |
| apr_pool_t *scratch_pool) |
| { |
| apr_hash_t *externals_hash = baton; |
| apr_pool_t *hash_pool = apr_hash_pool_get(externals_hash); |
| |
| apr_hash_set(externals_hash, apr_pstrdup(hash_pool, local_abspath), |
| APR_HASH_KEY_STRING, svn_string_dup(new_val, hash_pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Callback of type svn_wc_status_func4_t. Does nothing. */ |
| static svn_error_t * |
| status_noop_func(void *baton, |
| const char *local_abspath, |
| const svn_wc_status3_t *status, |
| apr_pool_t *scratch_pool) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_client__crawl_for_externals(apr_hash_t **externals_p, |
| const char *local_abspath, |
| svn_depth_t depth, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool, |
| apr_pool_t *result_pool) |
| { |
| apr_hash_t *externals_hash = apr_hash_make(result_pool); |
| |
| /* Do a status run just to harvest externals definitions. */ |
| SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, local_abspath, depth, |
| FALSE, FALSE, NULL, status_noop_func, NULL, |
| externals_update_func, externals_hash, |
| ctx->cancel_func, ctx->cancel_baton, |
| scratch_pool)); |
| |
| *externals_p = externals_hash; |
| return SVN_NO_ERROR; |
| } |