]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/switch.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_client / switch.c
1 /*
2  * switch.c:  implement 'switch' feature via WC & RA interfaces.
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 \f
28 /*** Includes. ***/
29
30 #include "svn_client.h"
31 #include "svn_error.h"
32 #include "svn_hash.h"
33 #include "svn_time.h"
34 #include "svn_dirent_uri.h"
35 #include "svn_path.h"
36 #include "svn_config.h"
37 #include "svn_pools.h"
38 #include "client.h"
39
40 #include "svn_private_config.h"
41 #include "private/svn_wc_private.h"
42
43 \f
44 /*** Code. ***/
45
46 /* This feature is essentially identical to 'svn update' (see
47    ./update.c), but with two differences:
48
49      - the reporter->finish_report() routine needs to make the server
50        run delta_dirs() on two *different* paths, rather than on two
51        identical paths.
52
53      - after the update runs, we need to more than just
54        ensure_uniform_revision;  we need to rewrite all the entries'
55        URL attributes.
56 */
57
58
59 /* A conflict callback that simply records the conflicted path in BATON.
60
61    Implements svn_wc_conflict_resolver_func2_t.
62 */
63 static svn_error_t *
64 record_conflict(svn_wc_conflict_result_t **result,
65                 const svn_wc_conflict_description2_t *description,
66                 void *baton,
67                 apr_pool_t *result_pool,
68                 apr_pool_t *scratch_pool)
69 {
70   apr_hash_t *conflicted_paths = baton;
71
72   svn_hash_sets(conflicted_paths,
73                 apr_pstrdup(apr_hash_pool_get(conflicted_paths),
74                             description->local_abspath), "");
75   *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
76                                           NULL, result_pool);
77   return SVN_NO_ERROR;
78 }
79
80 /* ...
81
82    Add the paths of any conflict victims to CONFLICTED_PATHS, if that
83    is not null.
84 */
85 static svn_error_t *
86 switch_internal(svn_revnum_t *result_rev,
87                 apr_hash_t *conflicted_paths,
88                 const char *local_abspath,
89                 const char *anchor_abspath,
90                 const char *switch_url,
91                 const svn_opt_revision_t *peg_revision,
92                 const svn_opt_revision_t *revision,
93                 svn_depth_t depth,
94                 svn_boolean_t depth_is_sticky,
95                 svn_boolean_t ignore_externals,
96                 svn_boolean_t allow_unver_obstructions,
97                 svn_boolean_t ignore_ancestry,
98                 svn_boolean_t *timestamp_sleep,
99                 svn_client_ctx_t *ctx,
100                 apr_pool_t *pool)
101 {
102   const svn_ra_reporter3_t *reporter;
103   void *report_baton;
104   const char *anchor_url, *target;
105   svn_client__pathrev_t *switch_loc;
106   svn_ra_session_t *ra_session;
107   svn_revnum_t revnum;
108   const char *diff3_cmd;
109   apr_hash_t *wcroot_iprops;
110   apr_array_header_t *inherited_props;
111   svn_boolean_t use_commit_times;
112   const svn_delta_editor_t *switch_editor;
113   void *switch_edit_baton;
114   const char *preserved_exts_str;
115   apr_array_header_t *preserved_exts;
116   svn_boolean_t server_supports_depth;
117   struct svn_client__dirent_fetcher_baton_t dfb;
118   svn_config_t *cfg = ctx->config
119                       ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
120                       : NULL;
121
122   /* An unknown depth can't be sticky. */
123   if (depth == svn_depth_unknown)
124     depth_is_sticky = FALSE;
125
126   /* Do not support the situation of both exclude and switch a target. */
127   if (depth == svn_depth_exclude)
128     return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
129                              _("Cannot both exclude and switch a path"));
130
131   /* Get the external diff3, if any. */
132   svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
133                  SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
134
135   if (diff3_cmd != NULL)
136     SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
137
138   /* See if the user wants last-commit timestamps instead of current ones. */
139   SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
140                               SVN_CONFIG_SECTION_MISCELLANY,
141                               SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
142
143   {
144     svn_boolean_t has_working;
145     SVN_ERR(svn_wc__node_has_working(&has_working, ctx->wc_ctx, local_abspath,
146                                      pool));
147
148     if (has_working)
149       return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
150                                _("Cannot switch '%s' because it is not in the "
151                                  "repository yet"),
152                                svn_dirent_local_style(local_abspath, pool));
153   }
154
155   /* See which files the user wants to preserve the extension of when
156      conflict files are made. */
157   svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
158                  SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
159   preserved_exts = *preserved_exts_str
160     ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool)
161     : NULL;
162
163   /* Sanity check.  Without these, the switch is meaningless. */
164   SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0'));
165
166   if (strcmp(local_abspath, anchor_abspath))
167     target = svn_dirent_basename(local_abspath, pool);
168   else
169     target = "";
170
171   SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
172                                pool, pool));
173   if (! anchor_url)
174     return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
175                              _("Directory '%s' has no URL"),
176                              svn_dirent_local_style(anchor_abspath, pool));
177
178   /* We may need to crop the tree if the depth is sticky */
179   if (depth_is_sticky && depth < svn_depth_infinity)
180     {
181       svn_node_kind_t target_kind;
182
183       if (depth == svn_depth_exclude)
184         {
185           SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
186                                  local_abspath,
187                                  ctx->cancel_func, ctx->cancel_baton,
188                                  ctx->notify_func2, ctx->notify_baton2,
189                                  pool));
190
191           /* Target excluded, we are done now */
192           return SVN_NO_ERROR;
193         }
194
195       SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
196                                 TRUE, TRUE, pool));
197
198       if (target_kind == svn_node_dir)
199         SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
200                                   ctx->cancel_func, ctx->cancel_baton,
201                                   ctx->notify_func2, ctx->notify_baton2,
202                                   pool));
203     }
204
205   /* Open an RA session to 'source' URL */
206   SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
207                                             switch_url, anchor_abspath,
208                                             peg_revision, revision,
209                                             ctx, pool));
210
211   /* Disallow a switch operation to change the repository root of the
212      target. */
213   if (! svn_uri__is_ancestor(switch_loc->repos_root_url, anchor_url))
214     return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL,
215                              _("'%s'\nis not the same repository as\n'%s'"),
216                              anchor_url, switch_loc->repos_root_url);
217
218   /* If we're not ignoring ancestry, then error out if the switch
219      source and target don't have a common ancestory.
220
221      ### We're acting on the anchor here, not the target.  Is that
222      ### okay? */
223   if (! ignore_ancestry)
224     {
225       svn_client__pathrev_t *target_base_loc, *yca;
226
227       SVN_ERR(svn_client__wc_node_get_base(&target_base_loc, local_abspath,
228                                            ctx->wc_ctx, pool, pool));
229
230       if (!target_base_loc)
231         yca = NULL; /* Not versioned */
232       else
233         {
234           /* ### It would be nice if this function could reuse the existing
235              ra session instead of opening two for its own use. */
236           SVN_ERR(svn_client__get_youngest_common_ancestor(
237                   &yca, switch_loc, target_base_loc, ra_session, ctx,
238                   pool, pool));
239         }
240       if (! yca)
241         return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
242                                  _("'%s' shares no common ancestry with '%s'"),
243                                  switch_url,
244                                  svn_dirent_local_style(local_abspath, pool));
245     }
246
247   wcroot_iprops = apr_hash_make(pool);
248
249   /* Will the base of LOCAL_ABSPATH require an iprop cache post-switch?
250      If we are switching LOCAL_ABSPATH to the root of the repository then
251      we don't need to cache inherited properties.  In all other cases we
252      *might* need to cache iprops. */
253   if (strcmp(switch_loc->repos_root_url, switch_loc->url) != 0)
254     {
255       svn_boolean_t wc_root;
256       svn_boolean_t needs_iprop_cache = TRUE;
257
258       SVN_ERR(svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath,
259                                 pool));
260
261       /* Switching the WC root to anything but the repos root means
262          we need an iprop cache. */
263       if (!wc_root)
264         {
265           /* We know we are switching a subtree to something other than the
266              repos root, but if we are unswitching that subtree we don't
267              need an iprops cache. */
268           const char *target_parent_url;
269           const char *unswitched_url;
270
271           /* Calculate the URL LOCAL_ABSPATH would have if it was unswitched
272              relative to its parent. */
273           SVN_ERR(svn_wc__node_get_url(&target_parent_url, ctx->wc_ctx,
274                                        svn_dirent_dirname(local_abspath,
275                                                           pool),
276                                        pool, pool));
277           unswitched_url = svn_path_url_add_component2(
278             target_parent_url,
279             svn_dirent_basename(local_abspath, pool),
280             pool);
281
282           /* If LOCAL_ABSPATH will be unswitched relative to its parent, then
283              it doesn't need an iprop cache.  Note: It doesn't matter if
284              LOCAL_ABSPATH is withing a switched subtree, only if it's the
285              *root* of a switched subtree.*/
286           if (strcmp(unswitched_url, switch_loc->url) == 0)
287             needs_iprop_cache = FALSE;
288       }
289
290
291       if (needs_iprop_cache)
292         {
293           SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
294                                              "", switch_loc->rev, pool,
295                                              pool));
296           svn_hash_sets(wcroot_iprops, local_abspath, inherited_props);
297         }
298     }
299
300   SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool));
301
302   /* Fetch the switch (update) editor.  If REVISION is invalid, that's
303      okay; the RA driver will call editor->set_target_revision() later on. */
304   SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
305                                 SVN_RA_CAPABILITY_DEPTH, pool));
306
307   dfb.ra_session = ra_session;
308   dfb.anchor_url = anchor_url;
309   dfb.target_revision = switch_loc->rev;
310
311   SVN_ERR(svn_wc__get_switch_editor(&switch_editor, &switch_edit_baton,
312                                     &revnum, ctx->wc_ctx, anchor_abspath,
313                                     target, switch_loc->url, wcroot_iprops,
314                                     use_commit_times, depth,
315                                     depth_is_sticky, allow_unver_obstructions,
316                                     server_supports_depth,
317                                     diff3_cmd, preserved_exts,
318                                     svn_client__dirent_fetcher, &dfb,
319                                     conflicted_paths ? record_conflict : NULL,
320                                     conflicted_paths,
321                                     NULL, NULL,
322                                     ctx->cancel_func, ctx->cancel_baton,
323                                     ctx->notify_func2, ctx->notify_baton2,
324                                     pool, pool));
325
326   /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
327      invalid revnum, that means RA will use the latest revision. */
328   SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
329                             switch_loc->rev,
330                             target,
331                             depth_is_sticky ? depth : svn_depth_unknown,
332                             switch_loc->url,
333                             FALSE /* send_copyfrom_args */,
334                             ignore_ancestry,
335                             switch_editor, switch_edit_baton,
336                             pool, pool));
337
338   /* Past this point, we assume the WC is going to be modified so we will
339    * need to sleep for timestamps. */
340   *timestamp_sleep = TRUE;
341
342   /* Drive the reporter structure, describing the revisions within
343      PATH.  When we call reporter->finish_report, the update_editor
344      will be driven by svn_repos_dir_delta2. */
345   SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
346                                   report_baton, TRUE,
347                                   depth, (! depth_is_sticky),
348                                   (! server_supports_depth),
349                                   use_commit_times,
350                                   ctx->cancel_func, ctx->cancel_baton,
351                                   ctx->notify_func2, ctx->notify_baton2,
352                                   pool));
353
354   /* We handle externals after the switch is complete, so that
355      handling external items (and any errors therefrom) doesn't delay
356      the primary operation. */
357   if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
358     {
359       apr_hash_t *new_externals;
360       apr_hash_t *new_depths;
361       SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
362                                                    &new_depths,
363                                                    ctx->wc_ctx, local_abspath,
364                                                    depth, pool, pool));
365
366       SVN_ERR(svn_client__handle_externals(new_externals,
367                                            new_depths,
368                                            switch_loc->repos_root_url,
369                                            local_abspath,
370                                            depth, timestamp_sleep,
371                                            ctx, pool));
372     }
373
374   /* Let everyone know we're finished here. */
375   if (ctx->notify_func2)
376     {
377       svn_wc_notify_t *notify
378         = svn_wc_create_notify(anchor_abspath, svn_wc_notify_update_completed,
379                                pool);
380       notify->kind = svn_node_none;
381       notify->content_state = notify->prop_state
382         = svn_wc_notify_state_inapplicable;
383       notify->lock_state = svn_wc_notify_lock_state_inapplicable;
384       notify->revision = revnum;
385       (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
386     }
387
388   /* If the caller wants the result revision, give it to them. */
389   if (result_rev)
390     *result_rev = revnum;
391
392   return SVN_NO_ERROR;
393 }
394
395 svn_error_t *
396 svn_client__switch_internal(svn_revnum_t *result_rev,
397                             const char *path,
398                             const char *switch_url,
399                             const svn_opt_revision_t *peg_revision,
400                             const svn_opt_revision_t *revision,
401                             svn_depth_t depth,
402                             svn_boolean_t depth_is_sticky,
403                             svn_boolean_t ignore_externals,
404                             svn_boolean_t allow_unver_obstructions,
405                             svn_boolean_t ignore_ancestry,
406                             svn_boolean_t *timestamp_sleep,
407                             svn_client_ctx_t *ctx,
408                             apr_pool_t *pool)
409 {
410   const char *local_abspath, *anchor_abspath;
411   svn_boolean_t acquired_lock;
412   svn_error_t *err, *err1, *err2;
413   apr_hash_t *conflicted_paths
414     = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
415
416   SVN_ERR_ASSERT(path);
417
418   SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
419
420   /* Rely on svn_wc__acquire_write_lock setting ANCHOR_ABSPATH even
421      when it returns SVN_ERR_WC_LOCKED */
422   err = svn_wc__acquire_write_lock(&anchor_abspath,
423                                    ctx->wc_ctx, local_abspath, TRUE,
424                                    pool, pool);
425   if (err && err->apr_err != SVN_ERR_WC_LOCKED)
426     return svn_error_trace(err);
427
428   acquired_lock = (err == SVN_NO_ERROR);
429   svn_error_clear(err);
430
431   err1 = switch_internal(result_rev, conflicted_paths,
432                          local_abspath, anchor_abspath,
433                          switch_url, peg_revision, revision,
434                          depth, depth_is_sticky,
435                          ignore_externals,
436                          allow_unver_obstructions, ignore_ancestry,
437                          timestamp_sleep, ctx, pool);
438
439   /* Give the conflict resolver callback the opportunity to
440    * resolve any conflicts that were raised. */
441   if (! err1 && ctx->conflict_func2)
442     {
443       err1 = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
444     }
445
446   if (acquired_lock)
447     err2 = svn_wc__release_write_lock(ctx->wc_ctx, anchor_abspath, pool);
448   else
449     err2 = SVN_NO_ERROR;
450
451   return svn_error_compose_create(err1, err2);
452 }
453
454 svn_error_t *
455 svn_client_switch3(svn_revnum_t *result_rev,
456                    const char *path,
457                    const char *switch_url,
458                    const svn_opt_revision_t *peg_revision,
459                    const svn_opt_revision_t *revision,
460                    svn_depth_t depth,
461                    svn_boolean_t depth_is_sticky,
462                    svn_boolean_t ignore_externals,
463                    svn_boolean_t allow_unver_obstructions,
464                    svn_boolean_t ignore_ancestry,
465                    svn_client_ctx_t *ctx,
466                    apr_pool_t *pool)
467 {
468   svn_error_t *err;
469   svn_boolean_t sleep_here = FALSE;
470
471   if (svn_path_is_url(path))
472     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
473                              _("'%s' is not a local path"), path);
474
475   err = svn_client__switch_internal(result_rev, path, switch_url,
476                                     peg_revision, revision, depth,
477                                     depth_is_sticky, ignore_externals,
478                                     allow_unver_obstructions,
479                                     ignore_ancestry, &sleep_here, ctx, pool);
480
481   /* Sleep to ensure timestamp integrity (we do this regardless of
482      errors in the actual switch operation(s)). */
483   if (sleep_here)
484     svn_io_sleep_for_timestamps(path, pool);
485
486   return svn_error_trace(err);
487 }