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