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
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
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 *
record_conflict(svn_wc_conflict_result_t ** result,const svn_wc_conflict_description2_t * description,void * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
switch_internal(svn_revnum_t * result_rev,apr_hash_t * conflicted_paths,const char * local_abspath,const char * anchor_abspath,const char * switch_url,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t depth_is_sticky,svn_boolean_t ignore_externals,svn_boolean_t allow_unver_obstructions,svn_boolean_t ignore_ancestry,svn_boolean_t * timestamp_sleep,svn_client_ctx_t * ctx,apr_pool_t * pool)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 SVN_ERR(svn_client__get_youngest_common_ancestor(
235 &yca, switch_loc, target_base_loc, ra_session, ctx,
236 pool, pool));
237 }
238 if (! yca)
239 return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
240 _("'%s' shares no common ancestry with '%s'"),
241 switch_url,
242 svn_dirent_local_style(local_abspath, pool));
243 }
244
245 wcroot_iprops = apr_hash_make(pool);
246
247 /* Will the base of LOCAL_ABSPATH require an iprop cache post-switch?
248 If we are switching LOCAL_ABSPATH to the root of the repository then
249 we don't need to cache inherited properties. In all other cases we
250 *might* need to cache iprops. */
251 if (strcmp(switch_loc->repos_root_url, switch_loc->url) != 0)
252 {
253 svn_boolean_t wc_root;
254 svn_boolean_t needs_iprop_cache = TRUE;
255
256 SVN_ERR(svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath,
257 pool));
258
259 /* Switching the WC root to anything but the repos root means
260 we need an iprop cache. */
261 if (!wc_root)
262 {
263 /* We know we are switching a subtree to something other than the
264 repos root, but if we are unswitching that subtree we don't
265 need an iprops cache. */
266 const char *target_parent_url;
267 const char *unswitched_url;
268
269 /* Calculate the URL LOCAL_ABSPATH would have if it was unswitched
270 relative to its parent. */
271 SVN_ERR(svn_wc__node_get_url(&target_parent_url, ctx->wc_ctx,
272 svn_dirent_dirname(local_abspath,
273 pool),
274 pool, pool));
275 unswitched_url = svn_path_url_add_component2(
276 target_parent_url,
277 svn_dirent_basename(local_abspath, pool),
278 pool);
279
280 /* If LOCAL_ABSPATH will be unswitched relative to its parent, then
281 it doesn't need an iprop cache. Note: It doesn't matter if
282 LOCAL_ABSPATH is withing a switched subtree, only if it's the
283 *root* of a switched subtree.*/
284 if (strcmp(unswitched_url, switch_loc->url) == 0)
285 needs_iprop_cache = FALSE;
286 }
287
288
289 if (needs_iprop_cache)
290 {
291 SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
292 "", switch_loc->rev, pool,
293 pool));
294 svn_hash_sets(wcroot_iprops, local_abspath, inherited_props);
295 }
296 }
297
298 SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool));
299
300 /* Fetch the switch (update) editor. If REVISION is invalid, that's
301 okay; the RA driver will call editor->set_target_revision() later on. */
302 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
303 SVN_RA_CAPABILITY_DEPTH, pool));
304
305 dfb.ra_session = ra_session;
306 dfb.anchor_url = anchor_url;
307 dfb.target_revision = switch_loc->rev;
308
309 SVN_ERR(svn_wc__get_switch_editor(&switch_editor, &switch_edit_baton,
310 &revnum, ctx->wc_ctx, anchor_abspath,
311 target, switch_loc->url, wcroot_iprops,
312 use_commit_times, depth,
313 depth_is_sticky, allow_unver_obstructions,
314 server_supports_depth,
315 diff3_cmd, preserved_exts,
316 svn_client__dirent_fetcher, &dfb,
317 conflicted_paths ? record_conflict : NULL,
318 conflicted_paths,
319 NULL, NULL,
320 ctx->cancel_func, ctx->cancel_baton,
321 ctx->notify_func2, ctx->notify_baton2,
322 pool, pool));
323
324 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
325 invalid revnum, that means RA will use the latest revision. */
326 SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
327 switch_loc->rev,
328 target,
329 depth_is_sticky ? depth : svn_depth_unknown,
330 switch_loc->url,
331 FALSE /* send_copyfrom_args */,
332 ignore_ancestry,
333 switch_editor, switch_edit_baton,
334 pool, pool));
335
336 /* Past this point, we assume the WC is going to be modified so we will
337 * need to sleep for timestamps. */
338 *timestamp_sleep = TRUE;
339
340 /* Drive the reporter structure, describing the revisions within
341 LOCAL_ABSPATH. When this calls reporter->finish_report, the
342 reporter will drive the switch_editor. */
343 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
344 report_baton, TRUE,
345 depth, (! depth_is_sticky),
346 (! server_supports_depth),
347 use_commit_times,
348 ctx->cancel_func, ctx->cancel_baton,
349 ctx->notify_func2, ctx->notify_baton2,
350 pool));
351
352 /* We handle externals after the switch is complete, so that
353 handling external items (and any errors therefrom) doesn't delay
354 the primary operation. */
355 if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
356 {
357 apr_hash_t *new_externals;
358 apr_hash_t *new_depths;
359 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
360 &new_depths,
361 ctx->wc_ctx, local_abspath,
362 depth, pool, pool));
363
364 SVN_ERR(svn_client__handle_externals(new_externals,
365 new_depths,
366 switch_loc->repos_root_url,
367 local_abspath,
368 depth, timestamp_sleep, ra_session,
369 ctx, pool));
370 }
371
372 /* Let everyone know we're finished here. */
373 if (ctx->notify_func2)
374 {
375 svn_wc_notify_t *notify
376 = svn_wc_create_notify(anchor_abspath, svn_wc_notify_update_completed,
377 pool);
378 notify->kind = svn_node_none;
379 notify->content_state = notify->prop_state
380 = svn_wc_notify_state_inapplicable;
381 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
382 notify->revision = revnum;
383 ctx->notify_func2(ctx->notify_baton2, notify, pool);
384 }
385
386 /* If the caller wants the result revision, give it to them. */
387 if (result_rev)
388 *result_rev = revnum;
389
390 return SVN_NO_ERROR;
391 }
392
393 svn_error_t *
svn_client__switch_internal(svn_revnum_t * result_rev,const char * path,const char * switch_url,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t depth_is_sticky,svn_boolean_t ignore_externals,svn_boolean_t allow_unver_obstructions,svn_boolean_t ignore_ancestry,svn_boolean_t * timestamp_sleep,svn_client_ctx_t * ctx,apr_pool_t * pool)394 svn_client__switch_internal(svn_revnum_t *result_rev,
395 const char *path,
396 const char *switch_url,
397 const svn_opt_revision_t *peg_revision,
398 const svn_opt_revision_t *revision,
399 svn_depth_t depth,
400 svn_boolean_t depth_is_sticky,
401 svn_boolean_t ignore_externals,
402 svn_boolean_t allow_unver_obstructions,
403 svn_boolean_t ignore_ancestry,
404 svn_boolean_t *timestamp_sleep,
405 svn_client_ctx_t *ctx,
406 apr_pool_t *pool)
407 {
408 const char *local_abspath, *anchor_abspath;
409 svn_boolean_t acquired_lock;
410 svn_error_t *err, *err1, *err2;
411 apr_hash_t *conflicted_paths
412 = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
413
414 SVN_ERR_ASSERT(path);
415
416 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
417
418 /* Rely on svn_wc__acquire_write_lock setting ANCHOR_ABSPATH even
419 when it returns SVN_ERR_WC_LOCKED */
420 err = svn_wc__acquire_write_lock(&anchor_abspath,
421 ctx->wc_ctx, local_abspath, TRUE,
422 pool, pool);
423 if (err && err->apr_err != SVN_ERR_WC_LOCKED)
424 return svn_error_trace(err);
425
426 acquired_lock = (err == SVN_NO_ERROR);
427 svn_error_clear(err);
428
429 err1 = switch_internal(result_rev, conflicted_paths,
430 local_abspath, anchor_abspath,
431 switch_url, peg_revision, revision,
432 depth, depth_is_sticky,
433 ignore_externals,
434 allow_unver_obstructions, ignore_ancestry,
435 timestamp_sleep, ctx, pool);
436
437 /* Give the conflict resolver callback the opportunity to
438 * resolve any conflicts that were raised. */
439 if (! err1 && ctx->conflict_func2)
440 {
441 err1 = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
442 }
443
444 if (acquired_lock)
445 err2 = svn_wc__release_write_lock(ctx->wc_ctx, anchor_abspath, pool);
446 else
447 err2 = SVN_NO_ERROR;
448
449 return svn_error_compose_create(err1, err2);
450 }
451
452 svn_error_t *
svn_client_switch3(svn_revnum_t * result_rev,const char * path,const char * switch_url,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t depth_is_sticky,svn_boolean_t ignore_externals,svn_boolean_t allow_unver_obstructions,svn_boolean_t ignore_ancestry,svn_client_ctx_t * ctx,apr_pool_t * pool)453 svn_client_switch3(svn_revnum_t *result_rev,
454 const char *path,
455 const char *switch_url,
456 const svn_opt_revision_t *peg_revision,
457 const svn_opt_revision_t *revision,
458 svn_depth_t depth,
459 svn_boolean_t depth_is_sticky,
460 svn_boolean_t ignore_externals,
461 svn_boolean_t allow_unver_obstructions,
462 svn_boolean_t ignore_ancestry,
463 svn_client_ctx_t *ctx,
464 apr_pool_t *pool)
465 {
466 svn_error_t *err;
467 svn_boolean_t sleep_here = FALSE;
468
469 if (svn_path_is_url(path))
470 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
471 _("'%s' is not a local path"), path);
472
473 err = svn_client__switch_internal(result_rev, path, switch_url,
474 peg_revision, revision, depth,
475 depth_is_sticky, ignore_externals,
476 allow_unver_obstructions,
477 ignore_ancestry, &sleep_here, ctx, pool);
478
479 /* Sleep to ensure timestamp integrity (we do this regardless of
480 errors in the actual switch operation(s)). */
481 if (sleep_here)
482 svn_io_sleep_for_timestamps(path, pool);
483
484 return svn_error_trace(err);
485 }
486