1/* 2 * relocate.c: wrapper around wc relocation functionality. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_wc.h" 31#include "svn_client.h" 32#include "svn_pools.h" 33#include "svn_error.h" 34#include "svn_dirent_uri.h" 35#include "svn_path.h" 36#include "client.h" 37 38#include "private/svn_wc_private.h" 39 40#include "svn_private_config.h" 41 42 43/*** Code. ***/ 44 45/* Repository root and UUID for a repository. */ 46struct url_uuid_t 47{ 48 const char *root; 49 const char *uuid; 50}; 51 52struct validator_baton_t 53{ 54 svn_client_ctx_t *ctx; 55 const char *path; 56 apr_array_header_t *url_uuids; 57 apr_pool_t *pool; 58 59}; 60 61 62static svn_error_t * 63validator_func(void *baton, 64 const char *uuid, 65 const char *url, 66 const char *root_url, 67 apr_pool_t *pool) 68{ 69 struct validator_baton_t *b = baton; 70 struct url_uuid_t *url_uuid = NULL; 71 const char *disable_checks; 72 73 apr_array_header_t *uuids = b->url_uuids; 74 int i; 75 76 for (i = 0; i < uuids->nelts; ++i) 77 { 78 struct url_uuid_t *uu = &APR_ARRAY_IDX(uuids, i, 79 struct url_uuid_t); 80 if (svn_uri__is_ancestor(uu->root, url)) 81 { 82 url_uuid = uu; 83 break; 84 } 85 } 86 87 disable_checks = getenv("SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION"); 88 if (disable_checks && (strcmp(disable_checks, "yes") == 0)) 89 { 90 /* Lie about URL_UUID's components, claiming they match the 91 expectations of the validation code below. */ 92 url_uuid = apr_pcalloc(pool, sizeof(*url_uuid)); 93 url_uuid->root = apr_pstrdup(pool, root_url); 94 url_uuid->uuid = apr_pstrdup(pool, uuid); 95 } 96 97 /* We use an RA session in a subpool to get the UUID of the 98 repository at the new URL so we can force the RA session to close 99 by destroying the subpool. */ 100 if (! url_uuid) 101 { 102 apr_pool_t *sesspool = svn_pool_create(pool); 103 104 url_uuid = &APR_ARRAY_PUSH(uuids, struct url_uuid_t); 105 SVN_ERR(svn_client_get_repos_root(&url_uuid->root, 106 &url_uuid->uuid, 107 url, b->ctx, 108 pool, sesspool)); 109 110 svn_pool_destroy(sesspool); 111 } 112 113 /* Make sure the url is a repository root if desired. */ 114 if (root_url 115 && strcmp(root_url, url_uuid->root) != 0) 116 return svn_error_createf(SVN_ERR_CLIENT_INVALID_RELOCATION, NULL, 117 _("'%s' is not the root of the repository"), 118 url); 119 120 /* Make sure the UUIDs match. */ 121 if (uuid && strcmp(uuid, url_uuid->uuid) != 0) 122 return svn_error_createf 123 (SVN_ERR_CLIENT_INVALID_RELOCATION, NULL, 124 _("The repository at '%s' has uuid '%s', but the WC has '%s'"), 125 url, url_uuid->uuid, uuid); 126 127 return SVN_NO_ERROR; 128} 129 130 131/* Examing the array of svn_wc_external_item2_t's EXT_DESC (parsed 132 from the svn:externals property set on LOCAL_ABSPATH) and determine 133 if the external working copies described by such should be 134 relocated as a side-effect of the relocation of their parent 135 working copy (from OLD_PARENT_REPOS_ROOT_URL to 136 NEW_PARENT_REPOS_ROOT_URL). If so, attempt said relocation. */ 137static svn_error_t * 138relocate_externals(const char *local_abspath, 139 apr_array_header_t *ext_desc, 140 const char *old_parent_repos_root_url, 141 const char *new_parent_repos_root_url, 142 svn_client_ctx_t *ctx, 143 apr_pool_t *scratch_pool) 144{ 145 apr_pool_t *iterpool; 146 int i; 147 148 /* Parse an externals definition into an array of external items. */ 149 150 iterpool = svn_pool_create(scratch_pool); 151 152 for (i = 0; i < ext_desc->nelts; i++) 153 { 154 svn_wc_external_item2_t *ext_item = 155 APR_ARRAY_IDX(ext_desc, i, svn_wc_external_item2_t *); 156 const char *target_repos_root_url; 157 const char *target_abspath; 158 svn_error_t *err; 159 160 svn_pool_clear(iterpool); 161 162 /* If this external isn't pulled in via a relative URL, ignore 163 it. There's no sense in relocating a working copy only to 164 have the next 'svn update' try to point it back to another 165 location. */ 166 if (! ((strncmp("../", ext_item->url, 3) == 0) || 167 (strncmp("^/", ext_item->url, 2) == 0))) 168 continue; 169 170 /* If the external working copy's not-yet-relocated repos root 171 URL matches the primary working copy's pre-relocated 172 repository root URL, try to relocate that external, too. 173 You might wonder why this check is needed, given that we're 174 already limiting ourselves to externals pulled via URLs 175 relative to their primary working copy. Well, it's because 176 you can use "../" to "crawl up" above one repository's URL 177 space and down into another one. */ 178 SVN_ERR(svn_dirent_get_absolute(&target_abspath, 179 svn_dirent_join(local_abspath, 180 ext_item->target_dir, 181 iterpool), 182 iterpool)); 183 err = svn_client_get_repos_root(&target_repos_root_url, NULL /* uuid */, 184 target_abspath, ctx, iterpool, iterpool); 185 186 /* Ignore externals that aren't present in the working copy. 187 * This can happen if an external is deleted from disk accidentally, 188 * or if an external is configured on a locally added directory. */ 189 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 190 { 191 svn_error_clear(err); 192 continue; 193 } 194 else 195 SVN_ERR(err); 196 197 if (strcmp(target_repos_root_url, old_parent_repos_root_url) == 0) 198 SVN_ERR(svn_client_relocate2(target_abspath, 199 old_parent_repos_root_url, 200 new_parent_repos_root_url, 201 FALSE, ctx, iterpool)); 202 } 203 204 svn_pool_destroy(iterpool); 205 206 return SVN_NO_ERROR; 207} 208 209svn_error_t * 210svn_client_relocate2(const char *wcroot_dir, 211 const char *from_prefix, 212 const char *to_prefix, 213 svn_boolean_t ignore_externals, 214 svn_client_ctx_t *ctx, 215 apr_pool_t *pool) 216{ 217 struct validator_baton_t vb; 218 const char *local_abspath; 219 apr_hash_t *externals_hash = NULL; 220 apr_hash_index_t *hi; 221 apr_pool_t *iterpool = NULL; 222 const char *old_repos_root_url, *new_repos_root_url; 223 224 /* Populate our validator callback baton, and call the relocate code. */ 225 vb.ctx = ctx; 226 vb.path = wcroot_dir; 227 vb.url_uuids = apr_array_make(pool, 1, sizeof(struct url_uuid_t)); 228 vb.pool = pool; 229 230 if (svn_path_is_url(wcroot_dir)) 231 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 232 _("'%s' is not a local path"), 233 wcroot_dir); 234 235 SVN_ERR(svn_dirent_get_absolute(&local_abspath, wcroot_dir, pool)); 236 237 /* If we're ignoring externals, just relocate and get outta here. */ 238 if (ignore_externals) 239 { 240 return svn_error_trace(svn_wc_relocate4(ctx->wc_ctx, local_abspath, 241 from_prefix, to_prefix, 242 validator_func, &vb, pool)); 243 } 244 245 /* Fetch our current root URL. */ 246 SVN_ERR(svn_client_get_repos_root(&old_repos_root_url, NULL /* uuid */, 247 local_abspath, ctx, pool, pool)); 248 249 /* Perform the relocation. */ 250 SVN_ERR(svn_wc_relocate4(ctx->wc_ctx, local_abspath, from_prefix, to_prefix, 251 validator_func, &vb, pool)); 252 253 /* Now fetch new current root URL. */ 254 SVN_ERR(svn_client_get_repos_root(&new_repos_root_url, NULL /* uuid */, 255 local_abspath, ctx, pool, pool)); 256 257 258 /* Relocate externals, too (if any). */ 259 SVN_ERR(svn_wc__externals_gather_definitions(&externals_hash, NULL, 260 ctx->wc_ctx, local_abspath, 261 svn_depth_infinity, 262 pool, pool)); 263 if (! apr_hash_count(externals_hash)) 264 return SVN_NO_ERROR; 265 266 iterpool = svn_pool_create(pool); 267 268 for (hi = apr_hash_first(pool, externals_hash); 269 hi != NULL; 270 hi = apr_hash_next(hi)) 271 { 272 const char *this_abspath = svn__apr_hash_index_key(hi); 273 const char *value = svn__apr_hash_index_val(hi); 274 apr_array_header_t *ext_desc; 275 276 svn_pool_clear(iterpool); 277 278 SVN_ERR(svn_wc_parse_externals_description3(&ext_desc, this_abspath, 279 value, FALSE, 280 iterpool)); 281 if (ext_desc->nelts) 282 SVN_ERR(relocate_externals(this_abspath, ext_desc, old_repos_root_url, 283 new_repos_root_url, ctx, iterpool)); 284 } 285 286 svn_pool_destroy(iterpool); 287 288 return SVN_NO_ERROR; 289} 290