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