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