upgrade.c revision 299742
1/*
2 * upgrade.c:  wrapper around wc upgrade 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_time.h"
31#include "svn_wc.h"
32#include "svn_client.h"
33#include "svn_config.h"
34#include "svn_dirent_uri.h"
35#include "svn_path.h"
36#include "svn_pools.h"
37#include "client.h"
38#include "svn_props.h"
39
40#include "svn_private_config.h"
41#include "private/svn_wc_private.h"
42
43
44/*** Code. ***/
45
46/* callback baton for fetch_repos_info */
47struct repos_info_baton
48{
49  apr_pool_t *state_pool;
50  svn_client_ctx_t *ctx;
51  const char *last_repos;
52  const char *last_uuid;
53};
54
55/* svn_wc_upgrade_get_repos_info_t implementation for calling
56   svn_wc_upgrade() from svn_client_upgrade() */
57static svn_error_t *
58fetch_repos_info(const char **repos_root,
59                 const char **repos_uuid,
60                 void *baton,
61                 const char *url,
62                 apr_pool_t *result_pool,
63                 apr_pool_t *scratch_pool)
64{
65  struct repos_info_baton *ri = baton;
66
67  /* The same info is likely to retrieved multiple times (e.g. externals) */
68  if (ri->last_repos && svn_uri__is_ancestor(ri->last_repos, url))
69    {
70      *repos_root = apr_pstrdup(result_pool, ri->last_repos);
71      *repos_uuid = apr_pstrdup(result_pool, ri->last_uuid);
72      return SVN_NO_ERROR;
73    }
74
75  SVN_ERR(svn_client_get_repos_root(repos_root, repos_uuid, url, ri->ctx,
76                                    result_pool, scratch_pool));
77
78  /* Store data for further calls */
79  ri->last_repos = apr_pstrdup(ri->state_pool, *repos_root);
80  ri->last_uuid = apr_pstrdup(ri->state_pool, *repos_uuid);
81
82  return SVN_NO_ERROR;
83}
84
85/* Forward definition. Upgrades svn:externals properties in the working copy
86   LOCAL_ABSPATH to the WC-NG  storage. INFO_BATON will be used to fetch
87   repository info using fetch_repos_info() function if needed.
88 */
89static svn_error_t *
90upgrade_externals_from_properties(svn_client_ctx_t *ctx,
91                                  const char *local_abspath,
92                                  struct repos_info_baton *info_baton,
93                                  apr_pool_t *scratch_pool);
94
95svn_error_t *
96svn_client_upgrade(const char *path,
97                   svn_client_ctx_t *ctx,
98                   apr_pool_t *scratch_pool)
99{
100  const char *local_abspath;
101  apr_hash_t *externals;
102  struct repos_info_baton info_baton;
103
104  info_baton.state_pool = scratch_pool;
105  info_baton.ctx = ctx;
106  info_baton.last_repos = NULL;
107  info_baton.last_uuid = NULL;
108
109  if (svn_path_is_url(path))
110    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
111                             _("'%s' is not a local path"), path);
112
113  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
114  SVN_ERR(svn_wc_upgrade(ctx->wc_ctx, local_abspath,
115                         fetch_repos_info, &info_baton,
116                         ctx->cancel_func, ctx->cancel_baton,
117                         ctx->notify_func2, ctx->notify_baton2,
118                         scratch_pool));
119
120  SVN_ERR(svn_wc__externals_defined_below(&externals,
121                                          ctx->wc_ctx, local_abspath,
122                                          scratch_pool, scratch_pool));
123
124  if (apr_hash_count(externals) > 0)
125    {
126      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
127      apr_hash_index_t *hi;
128
129      /* We are upgrading from >= 1.7. No need to upgrade from
130         svn:externals properties. And by that avoiding the removal
131         of recorded externals information (issue #4519)
132
133         Only directory externals need an explicit upgrade */
134      for (hi = apr_hash_first(scratch_pool, externals);
135           hi;
136           hi = apr_hash_next(hi))
137        {
138          const char *ext_abspath;
139          svn_node_kind_t kind;
140
141          svn_pool_clear(iterpool);
142
143          ext_abspath = apr_hash_this_key(hi);
144
145          SVN_ERR(svn_wc__read_external_info(&kind, NULL, NULL, NULL, NULL,
146                                             ctx->wc_ctx, local_abspath,
147                                             ext_abspath, FALSE,
148                                             iterpool, iterpool));
149
150          if (kind == svn_node_dir)
151            {
152              svn_error_t *err = svn_client_upgrade(ext_abspath, ctx, iterpool);
153
154              if (err)
155                {
156                  svn_wc_notify_t *notify =
157                            svn_wc_create_notify(ext_abspath,
158                                                 svn_wc_notify_failed_external,
159                                                 iterpool);
160                  notify->err = err;
161                  ctx->notify_func2(ctx->notify_baton2,
162                                    notify, iterpool);
163                  svn_error_clear(err);
164                  /* Next external node, please... */
165                }
166            }
167        }
168
169      svn_pool_destroy(iterpool);
170    }
171  else
172    {
173      /* Upgrading from <= 1.6, or no svn:properties defined.
174         (There is no way to detect the difference from libsvn_client :( ) */
175
176      SVN_ERR(upgrade_externals_from_properties(ctx, local_abspath,
177                                                &info_baton, scratch_pool));
178    }
179  return SVN_NO_ERROR;
180}
181
182static svn_error_t *
183upgrade_externals_from_properties(svn_client_ctx_t *ctx,
184                                  const char *local_abspath,
185                                  struct repos_info_baton *info_baton,
186                                  apr_pool_t *scratch_pool)
187{
188  apr_hash_index_t *hi;
189  apr_pool_t *iterpool;
190  apr_pool_t *iterpool2;
191  apr_hash_t *externals;
192  svn_opt_revision_t rev = {svn_opt_revision_unspecified, {0}};
193
194  /* Now it's time to upgrade the externals too. We do it after the wc
195     upgrade to avoid that errors in the externals causes the wc upgrade to
196     fail. Thanks to caching the performance penalty of walking the wc a
197     second time shouldn't be too severe */
198  SVN_ERR(svn_client_propget5(&externals, NULL, SVN_PROP_EXTERNALS,
199                              local_abspath, &rev, &rev, NULL,
200                              svn_depth_infinity, NULL, ctx,
201                              scratch_pool, scratch_pool));
202
203  iterpool = svn_pool_create(scratch_pool);
204  iterpool2 = svn_pool_create(scratch_pool);
205
206  for (hi = apr_hash_first(scratch_pool, externals); hi;
207       hi = apr_hash_next(hi))
208    {
209      int i;
210      const char *externals_parent_abspath;
211      const char *externals_parent_url;
212      const char *externals_parent_repos_root_url;
213      const char *externals_parent_repos_relpath;
214      const char *externals_parent = apr_hash_this_key(hi);
215      svn_string_t *external_desc = apr_hash_this_val(hi);
216      apr_array_header_t *externals_p;
217      svn_error_t *err;
218
219      svn_pool_clear(iterpool);
220      externals_p = apr_array_make(iterpool, 1,
221                                   sizeof(svn_wc_external_item2_t*));
222
223      /* In this loop, an error causes the respective externals definition, or
224       * the external (inner loop), to be skipped, so that upgrade carries on
225       * with the other externals. */
226
227      err = svn_dirent_get_absolute(&externals_parent_abspath,
228                                    externals_parent, iterpool);
229
230      if (!err)
231        err = svn_wc__node_get_repos_info(NULL,
232                                          &externals_parent_repos_relpath,
233                                          &externals_parent_repos_root_url,
234                                          NULL,
235                                          ctx->wc_ctx,
236                                          externals_parent_abspath,
237                                          iterpool, iterpool);
238
239      if (!err)
240        externals_parent_url = svn_path_url_add_component2(
241                                    externals_parent_repos_root_url,
242                                    externals_parent_repos_relpath,
243                                    iterpool);
244      if (!err)
245        err = svn_wc_parse_externals_description3(
246                  &externals_p, svn_dirent_dirname(local_abspath, iterpool),
247                  external_desc->data, FALSE, iterpool);
248      if (err)
249        {
250          svn_wc_notify_t *notify =
251              svn_wc_create_notify(externals_parent,
252                                   svn_wc_notify_failed_external,
253                                   scratch_pool);
254          notify->err = err;
255
256          ctx->notify_func2(ctx->notify_baton2,
257                            notify, scratch_pool);
258
259          svn_error_clear(err);
260
261          /* Next externals definition, please... */
262          continue;
263        }
264
265      for (i = 0; i < externals_p->nelts; i++)
266        {
267          svn_wc_external_item2_t *item;
268          const char *resolved_url;
269          const char *external_abspath;
270          const char *repos_relpath;
271          const char *repos_root_url;
272          const char *repos_uuid;
273          svn_node_kind_t external_kind;
274          svn_revnum_t peg_revision;
275          svn_revnum_t revision;
276
277          item = APR_ARRAY_IDX(externals_p, i, svn_wc_external_item2_t*);
278
279          svn_pool_clear(iterpool2);
280          external_abspath = svn_dirent_join(externals_parent_abspath,
281                                             item->target_dir,
282                                             iterpool2);
283
284          err = svn_wc__resolve_relative_external_url(
285                                              &resolved_url,
286                                              item,
287                                              externals_parent_repos_root_url,
288                                              externals_parent_url,
289                                              scratch_pool, scratch_pool);
290          if (err)
291            goto handle_error;
292
293          /* This is a hack. We only need to call svn_wc_upgrade() on external
294           * dirs, as file externals are upgraded along with their defining
295           * WC.  Reading the kind will throw an exception on an external dir,
296           * saying that the wc must be upgraded.  If it's a file, the lookup
297           * is done in an adm_dir belonging to the defining wc (which has
298           * already been upgraded) and no error is returned.  If it doesn't
299           * exist (external that isn't checked out yet), we'll just get
300           * svn_node_none. */
301          err = svn_wc_read_kind2(&external_kind, ctx->wc_ctx,
302                                  external_abspath, TRUE, FALSE, iterpool2);
303          if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
304            {
305              svn_error_clear(err);
306
307              err = svn_client_upgrade(external_abspath, ctx, iterpool2);
308              if (err)
309                goto handle_error;
310            }
311          else if (err)
312            goto handle_error;
313
314          /* The upgrade of any dir should be done now, get the now reliable
315           * kind. */
316          err = svn_wc_read_kind2(&external_kind, ctx->wc_ctx, external_abspath,
317                                  TRUE, FALSE, iterpool2);
318          if (err)
319            goto handle_error;
320
321          /* Update the EXTERNALS table according to the root URL,
322           * relpath and uuid known in the upgraded external WC. */
323
324          /* We should probably have a function that provides all three
325           * of root URL, repos relpath and uuid at once, but here goes... */
326
327          /* First get the relpath, as that returns SVN_ERR_WC_PATH_NOT_FOUND
328           * when the node is not present in the file system.
329           * svn_wc__node_get_repos_info() would try to derive the URL. */
330          err = svn_wc__node_get_repos_info(NULL,
331                                            &repos_relpath,
332                                            &repos_root_url,
333                                            &repos_uuid,
334                                            ctx->wc_ctx,
335                                            external_abspath,
336                                            iterpool2, iterpool2);
337          if (err)
338            goto handle_error;
339
340          /* If we haven't got any information from the checked out external,
341           * or if the URL information mismatches the external's definition,
342           * ask fetch_repos_info() to find out the repos root. */
343          if (0 != strcmp(resolved_url,
344                          svn_path_url_add_component2(repos_root_url,
345                                                      repos_relpath,
346                                                      scratch_pool)))
347            {
348              err = fetch_repos_info(&repos_root_url,
349                                     &repos_uuid,
350                                     info_baton,
351                                     resolved_url,
352                                     scratch_pool, scratch_pool);
353              if (err)
354                goto handle_error;
355
356              repos_relpath = svn_uri_skip_ancestor(repos_root_url,
357                                                    resolved_url,
358                                                    iterpool2);
359
360              /* There's just the URL, no idea what kind the external is.
361               * That's fine, as the external isn't even checked out yet.
362               * The kind will be set during the next 'update'. */
363              external_kind = svn_node_unknown;
364            }
365
366          if (err)
367            goto handle_error;
368
369          peg_revision = (item->peg_revision.kind == svn_opt_revision_number
370                          ? item->peg_revision.value.number
371                          : SVN_INVALID_REVNUM);
372
373          revision = (item->revision.kind == svn_opt_revision_number
374                      ? item->revision.value.number
375                      : SVN_INVALID_REVNUM);
376
377          err = svn_wc__upgrade_add_external_info(ctx->wc_ctx,
378                                                  external_abspath,
379                                                  external_kind,
380                                                  externals_parent,
381                                                  repos_relpath,
382                                                  repos_root_url,
383                                                  repos_uuid,
384                                                  peg_revision,
385                                                  revision,
386                                                  iterpool2);
387handle_error:
388          if (err)
389            {
390              svn_wc_notify_t *notify =
391                  svn_wc_create_notify(external_abspath,
392                                       svn_wc_notify_failed_external,
393                                       scratch_pool);
394              notify->err = err;
395              ctx->notify_func2(ctx->notify_baton2,
396                                notify, scratch_pool);
397              svn_error_clear(err);
398              /* Next external node, please... */
399            }
400        }
401    }
402
403  svn_pool_destroy(iterpool);
404  svn_pool_destroy(iterpool2);
405
406  return SVN_NO_ERROR;
407}
408