1 /*
2 * commit.c: wrappers around wc commit 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 <string.h>
31 #include <apr_strings.h>
32 #include <apr_hash.h>
33 #include "svn_hash.h"
34 #include "svn_wc.h"
35 #include "svn_ra.h"
36 #include "svn_client.h"
37 #include "svn_string.h"
38 #include "svn_pools.h"
39 #include "svn_error.h"
40 #include "svn_error_codes.h"
41 #include "svn_dirent_uri.h"
42 #include "svn_path.h"
43 #include "svn_sorts.h"
44
45 #include "client.h"
46 #include "private/svn_wc_private.h"
47 #include "private/svn_ra_private.h"
48 #include "private/svn_sorts_private.h"
49
50 #include "svn_private_config.h"
51
52 struct capture_baton_t {
53 svn_commit_callback2_t original_callback;
54 void *original_baton;
55
56 svn_commit_info_t **info;
57 apr_pool_t *pool;
58 };
59
60
61 static svn_error_t *
capture_commit_info(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)62 capture_commit_info(const svn_commit_info_t *commit_info,
63 void *baton,
64 apr_pool_t *pool)
65 {
66 struct capture_baton_t *cb = baton;
67
68 *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
69
70 if (cb->original_callback)
71 SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
72
73 return SVN_NO_ERROR;
74 }
75
76
77 static svn_error_t *
get_ra_editor(const svn_delta_editor_t ** editor,void ** edit_baton,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,const char * log_msg,const apr_array_header_t * commit_items,const apr_hash_t * revprop_table,apr_hash_t * lock_tokens,svn_boolean_t keep_locks,svn_commit_callback2_t commit_callback,void * commit_baton,apr_pool_t * pool)78 get_ra_editor(const svn_delta_editor_t **editor,
79 void **edit_baton,
80 svn_ra_session_t *ra_session,
81 svn_client_ctx_t *ctx,
82 const char *log_msg,
83 const apr_array_header_t *commit_items,
84 const apr_hash_t *revprop_table,
85 apr_hash_t *lock_tokens,
86 svn_boolean_t keep_locks,
87 svn_commit_callback2_t commit_callback,
88 void *commit_baton,
89 apr_pool_t *pool)
90 {
91 apr_hash_t *commit_revprops;
92 apr_hash_t *relpath_map = NULL;
93
94 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
95 log_msg, ctx, pool));
96
97 #ifdef ENABLE_EV2_SHIMS
98 if (commit_items)
99 {
100 int i;
101 apr_pool_t *iterpool = svn_pool_create(pool);
102
103 relpath_map = apr_hash_make(pool);
104 for (i = 0; i < commit_items->nelts; i++)
105 {
106 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
107 svn_client_commit_item3_t *);
108 const char *relpath;
109
110 if (!item->path)
111 continue;
112
113 svn_pool_clear(iterpool);
114 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
115 NULL, NULL,
116 ctx->wc_ctx, item->path, FALSE, pool,
117 iterpool));
118 if (relpath)
119 svn_hash_sets(relpath_map, relpath, item->path);
120 }
121 svn_pool_destroy(iterpool);
122 }
123 #endif
124
125 /* Fetch RA commit editor. */
126 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
127 svn_client__get_shim_callbacks(ctx->wc_ctx,
128 relpath_map, pool)));
129 SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton,
130 commit_revprops, commit_callback,
131 commit_baton, lock_tokens, keep_locks,
132 pool));
133
134 return SVN_NO_ERROR;
135 }
136
137
138 /*** Public Interfaces. ***/
139
140 static svn_error_t *
reconcile_errors(svn_error_t * commit_err,svn_error_t * unlock_err,svn_error_t * bump_err,apr_pool_t * pool)141 reconcile_errors(svn_error_t *commit_err,
142 svn_error_t *unlock_err,
143 svn_error_t *bump_err,
144 apr_pool_t *pool)
145 {
146 svn_error_t *err;
147
148 /* Early release (for good behavior). */
149 if (! (commit_err || unlock_err || bump_err))
150 return SVN_NO_ERROR;
151
152 /* If there was a commit error, start off our error chain with
153 that. */
154 if (commit_err)
155 {
156 commit_err = svn_error_quick_wrap
157 (commit_err, _("Commit failed (details follow):"));
158 err = commit_err;
159 }
160
161 /* Else, create a new "general" error that will lead off the errors
162 that follow. */
163 else
164 err = svn_error_create(SVN_ERR_BASE, NULL,
165 _("Commit succeeded, but other errors follow:"));
166
167 /* If there was an unlock error... */
168 if (unlock_err)
169 {
170 /* Wrap the error with some headers. */
171 unlock_err = svn_error_quick_wrap
172 (unlock_err, _("Error unlocking locked dirs (details follow):"));
173
174 /* Append this error to the chain. */
175 svn_error_compose(err, unlock_err);
176 }
177
178 /* If there was a bumping error... */
179 if (bump_err)
180 {
181 /* Wrap the error with some headers. */
182 bump_err = svn_error_quick_wrap
183 (bump_err, _("Error bumping revisions post-commit (details follow):"));
184
185 /* Append this error to the chain. */
186 svn_error_compose(err, bump_err);
187 }
188
189 return err;
190 }
191
192 /* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them
193 to a new hashtable allocated in POOL. *RESULT is set to point to this
194 new hash table. *RESULT will be keyed on const char * URI-decoded paths
195 relative to BASE_URL. The lock tokens will not be duplicated. */
196 static svn_error_t *
collect_lock_tokens(apr_hash_t ** result,apr_hash_t * all_tokens,const char * base_url,apr_pool_t * pool)197 collect_lock_tokens(apr_hash_t **result,
198 apr_hash_t *all_tokens,
199 const char *base_url,
200 apr_pool_t *pool)
201 {
202 apr_hash_index_t *hi;
203
204 *result = apr_hash_make(pool);
205
206 for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
207 {
208 const char *url = apr_hash_this_key(hi);
209 const char *token = apr_hash_this_val(hi);
210 const char *relpath = svn_uri_skip_ancestor(base_url, url, pool);
211
212 if (relpath)
213 {
214 svn_hash_sets(*result, relpath, token);
215 }
216 }
217
218 return SVN_NO_ERROR;
219 }
220
221 /* Put ITEM onto QUEUE, allocating it in QUEUE's pool...
222 * If a checksum is provided, it can be the MD5 and/or the SHA1. */
223 static svn_error_t *
post_process_commit_item(svn_wc_committed_queue_t * queue,const svn_client_commit_item3_t * item,svn_wc_context_t * wc_ctx,svn_boolean_t keep_changelists,svn_boolean_t keep_locks,svn_boolean_t commit_as_operations,const svn_checksum_t * sha1_checksum,apr_pool_t * scratch_pool)224 post_process_commit_item(svn_wc_committed_queue_t *queue,
225 const svn_client_commit_item3_t *item,
226 svn_wc_context_t *wc_ctx,
227 svn_boolean_t keep_changelists,
228 svn_boolean_t keep_locks,
229 svn_boolean_t commit_as_operations,
230 const svn_checksum_t *sha1_checksum,
231 apr_pool_t *scratch_pool)
232 {
233 svn_boolean_t loop_recurse = FALSE;
234 svn_boolean_t remove_lock;
235
236 if (! commit_as_operations
237 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
238 && (item->kind == svn_node_dir)
239 && (item->copyfrom_url))
240 loop_recurse = TRUE;
241
242 remove_lock = (! keep_locks && (item->state_flags
243 & (SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN
244 | SVN_CLIENT_COMMIT_ITEM_ADD
245 | SVN_CLIENT_COMMIT_ITEM_DELETE)));
246
247 /* When the node was deleted (or replaced), we need to always remove the
248 locks, as they're invalidated on the server. We cannot honor the
249 SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN flag here because it does not tell
250 us whether we have locked children. */
251 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
252 remove_lock = TRUE;
253
254 return svn_error_trace(
255 svn_wc_queue_committed4(queue, wc_ctx, item->path,
256 loop_recurse,
257 0 != (item->state_flags &
258 (SVN_CLIENT_COMMIT_ITEM_ADD
259 | SVN_CLIENT_COMMIT_ITEM_DELETE
260 | SVN_CLIENT_COMMIT_ITEM_TEXT_MODS
261 | SVN_CLIENT_COMMIT_ITEM_PROP_MODS)),
262 item->incoming_prop_changes,
263 remove_lock, !keep_changelists,
264 sha1_checksum, scratch_pool));
265 }
266
267 /* Given a list of committables described by their common base abspath
268 BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine
269 which absolute paths must be locked to commit all these targets and
270 return this as a const char * array in LOCK_TARGETS
271
272 Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
273 storage */
274 static svn_error_t *
determine_lock_targets(apr_array_header_t ** lock_targets,svn_wc_context_t * wc_ctx,const char * base_abspath,const apr_array_header_t * target_relpaths,apr_pool_t * result_pool,apr_pool_t * scratch_pool)275 determine_lock_targets(apr_array_header_t **lock_targets,
276 svn_wc_context_t *wc_ctx,
277 const char *base_abspath,
278 const apr_array_header_t *target_relpaths,
279 apr_pool_t *result_pool,
280 apr_pool_t *scratch_pool)
281 {
282 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
283 apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */
284 apr_hash_index_t *hi;
285 int i;
286
287 wc_items = apr_hash_make(scratch_pool);
288
289 /* Create an array of targets for each working copy used */
290 for (i = 0; i < target_relpaths->nelts; i++)
291 {
292 const char *target_abspath;
293 const char *wcroot_abspath;
294 apr_array_header_t *wc_targets;
295 svn_error_t *err;
296 const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
297 const char *);
298
299 svn_pool_clear(iterpool);
300 target_abspath = svn_dirent_join(base_abspath, target_relpath,
301 scratch_pool);
302
303 err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
304 iterpool, iterpool);
305
306 if (err)
307 {
308 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
309 {
310 svn_error_clear(err);
311 continue;
312 }
313 return svn_error_trace(err);
314 }
315
316 wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
317
318 if (! wc_targets)
319 {
320 wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
321 svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
322 wc_targets);
323 }
324
325 APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
326 }
327
328 *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
329 sizeof(const char *));
330
331 /* For each working copy determine where to lock */
332 for (hi = apr_hash_first(scratch_pool, wc_items);
333 hi;
334 hi = apr_hash_next(hi))
335 {
336 const char *common;
337 const char *wcroot_abspath = apr_hash_this_key(hi);
338 apr_array_header_t *wc_targets = apr_hash_this_val(hi);
339
340 svn_pool_clear(iterpool);
341
342 if (wc_targets->nelts == 1)
343 {
344 const char *target_abspath;
345 target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
346
347 if (! strcmp(wcroot_abspath, target_abspath))
348 {
349 APR_ARRAY_PUSH(*lock_targets, const char *)
350 = apr_pstrdup(result_pool, target_abspath);
351 }
352 else
353 {
354 /* Lock the parent to allow deleting the target */
355 APR_ARRAY_PUSH(*lock_targets, const char *)
356 = svn_dirent_dirname(target_abspath, result_pool);
357 }
358 }
359 else if (wc_targets->nelts > 1)
360 {
361 SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
362 FALSE, iterpool, iterpool));
363
364 svn_sort__array(wc_targets, svn_sort_compare_paths);
365
366 if (wc_targets->nelts == 0
367 || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
368 || !strcmp(common, wcroot_abspath))
369 {
370 APR_ARRAY_PUSH(*lock_targets, const char *)
371 = apr_pstrdup(result_pool, common);
372 }
373 else
374 {
375 /* Lock the parent to allow deleting the target */
376 APR_ARRAY_PUSH(*lock_targets, const char *)
377 = svn_dirent_dirname(common, result_pool);
378 }
379 }
380 }
381
382 svn_pool_destroy(iterpool);
383 return SVN_NO_ERROR;
384 }
385
386 /* Baton for check_url_kind */
387 struct check_url_kind_baton
388 {
389 apr_pool_t *pool;
390 svn_ra_session_t *session;
391 const char *repos_root_url;
392 svn_client_ctx_t *ctx;
393 };
394
395 /* Implements svn_client__check_url_kind_t for svn_client_commit5 */
396 static svn_error_t *
check_url_kind(void * baton,svn_node_kind_t * kind,const char * url,svn_revnum_t revision,apr_pool_t * scratch_pool)397 check_url_kind(void *baton,
398 svn_node_kind_t *kind,
399 const char *url,
400 svn_revnum_t revision,
401 apr_pool_t *scratch_pool)
402 {
403 struct check_url_kind_baton *cukb = baton;
404
405 /* If we don't have a session or can't use the session, get one */
406 if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url))
407 {
408 SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx,
409 cukb->pool, scratch_pool));
410 SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url,
411 cukb->pool));
412 }
413 else
414 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
415
416 return svn_error_trace(
417 svn_ra_check_path(cukb->session, "", revision,
418 kind, scratch_pool));
419 }
420
421 /* Recurse into every target in REL_TARGETS, finding committable externals
422 * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS
423 * are assumed to be / will be created relative to BASE_ABSPATH. The remaining
424 * arguments correspond to those of svn_client_commit6(). */
425 static svn_error_t*
append_externals_as_explicit_targets(apr_array_header_t * rel_targets,const char * base_abspath,svn_boolean_t include_file_externals,svn_boolean_t include_dir_externals,svn_depth_t depth,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)426 append_externals_as_explicit_targets(apr_array_header_t *rel_targets,
427 const char *base_abspath,
428 svn_boolean_t include_file_externals,
429 svn_boolean_t include_dir_externals,
430 svn_depth_t depth,
431 svn_client_ctx_t *ctx,
432 apr_pool_t *result_pool,
433 apr_pool_t *scratch_pool)
434 {
435 int rel_targets_nelts_fixed;
436 int i;
437 apr_pool_t *iterpool;
438
439 if (! (include_file_externals || include_dir_externals))
440 return SVN_NO_ERROR;
441
442 /* Easy part of applying DEPTH to externals. */
443 if (depth == svn_depth_empty)
444 {
445 /* Don't recurse. */
446 return SVN_NO_ERROR;
447 }
448
449 /* Iterate *and* grow REL_TARGETS at the same time. */
450 rel_targets_nelts_fixed = rel_targets->nelts;
451
452 iterpool = svn_pool_create(scratch_pool);
453
454 for (i = 0; i < rel_targets_nelts_fixed; i++)
455 {
456 int j;
457 const char *target;
458 apr_array_header_t *externals = NULL;
459
460 svn_pool_clear(iterpool);
461
462 target = svn_dirent_join(base_abspath,
463 APR_ARRAY_IDX(rel_targets, i, const char *),
464 iterpool);
465
466 /* ### TODO: Possible optimization: No need to do this for file targets.
467 * ### But what's cheaper, stat'ing the file system or querying the db?
468 * ### --> future. */
469
470 SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
471 target, depth,
472 iterpool, iterpool));
473
474 if (externals != NULL)
475 {
476 const char *rel_target;
477
478 for (j = 0; j < externals->nelts; j++)
479 {
480 svn_wc__committable_external_info_t *xinfo =
481 APR_ARRAY_IDX(externals, j,
482 svn_wc__committable_external_info_t *);
483
484 if ((xinfo->kind == svn_node_file && ! include_file_externals)
485 || (xinfo->kind == svn_node_dir && ! include_dir_externals))
486 continue;
487
488 rel_target = svn_dirent_skip_ancestor(base_abspath,
489 xinfo->local_abspath);
490
491 SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
492
493 APR_ARRAY_PUSH(rel_targets, const char *) =
494 apr_pstrdup(result_pool, rel_target);
495 }
496 }
497 }
498
499 svn_pool_destroy(iterpool);
500 return SVN_NO_ERROR;
501 }
502
503 /* Crawl the working copy for commit items.
504 */
505 static svn_error_t *
harvest_committables(apr_array_header_t ** commit_items_p,apr_hash_t ** committables_by_path_p,apr_hash_t ** lock_tokens,const char * base_dir_abspath,const apr_array_header_t * targets,int depth_empty_start,svn_depth_t depth,svn_boolean_t just_locked,const apr_array_header_t * changelists,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)506 harvest_committables(apr_array_header_t **commit_items_p,
507 apr_hash_t **committables_by_path_p,
508 apr_hash_t **lock_tokens,
509 const char *base_dir_abspath,
510 const apr_array_header_t *targets,
511 int depth_empty_start,
512 svn_depth_t depth,
513 svn_boolean_t just_locked,
514 const apr_array_header_t *changelists,
515 svn_client_ctx_t *ctx,
516 apr_pool_t *result_pool,
517 apr_pool_t *scratch_pool)
518 {
519 struct check_url_kind_baton cukb;
520 svn_client__committables_t *committables;
521 apr_hash_index_t *hi;
522
523 /* Prepare for when we have a copy containing not-present nodes. */
524 cukb.pool = scratch_pool;
525 cukb.session = NULL; /* ### Can we somehow reuse session? */
526 cukb.repos_root_url = NULL;
527 cukb.ctx = ctx;
528
529 SVN_ERR(svn_client__harvest_committables(&committables, lock_tokens,
530 base_dir_abspath, targets,
531 depth_empty_start, depth,
532 just_locked,
533 changelists,
534 check_url_kind, &cukb,
535 ctx, result_pool, scratch_pool));
536 if (apr_hash_count(committables->by_repository) == 0)
537 {
538 *commit_items_p = NULL;
539 return SVN_NO_ERROR; /* Nothing to do */
540 }
541 else if (apr_hash_count(committables->by_repository) > 1)
542 {
543 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
544 _("Commit can only commit to a single repository at a time.\n"
545 "Are all targets part of the same working copy?"));
546 }
547
548 hi = apr_hash_first(scratch_pool, committables->by_repository);
549 *commit_items_p = apr_hash_this_val(hi);
550 if (committables_by_path_p)
551 *committables_by_path_p = committables->by_path;
552 return SVN_NO_ERROR;
553 }
554
555 svn_error_t *
svn_client__wc_replay(const char * src_wc_abspath,const apr_array_header_t * targets,svn_depth_t depth,const apr_array_header_t * changelists,const svn_delta_editor_t * editor,void * edit_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)556 svn_client__wc_replay(const char *src_wc_abspath,
557 const apr_array_header_t *targets,
558 svn_depth_t depth,
559 const apr_array_header_t *changelists,
560 const svn_delta_editor_t *editor,
561 void *edit_baton,
562 svn_wc_notify_func2_t notify_func,
563 void *notify_baton,
564 svn_client_ctx_t *ctx,
565 apr_pool_t *pool)
566 {
567 const char *base_abspath;
568 apr_array_header_t *rel_targets;
569 apr_hash_t *lock_tokens;
570 apr_array_header_t *commit_items;
571 svn_client__pathrev_t *base;
572 const char *base_url;
573 svn_wc_notify_func2_t saved_notify_func;
574 void *saved_notify_baton;
575
576 /* Condense the target list. This makes all targets absolute. */
577 SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
578 FALSE, pool, pool));
579
580 /* No targets means nothing to commit, so just return. */
581 if (base_abspath == NULL)
582 return SVN_NO_ERROR;
583
584 SVN_ERR_ASSERT(rel_targets != NULL);
585
586 /* If we calculated only a base and no relative targets, this
587 must mean that we are being asked to commit (effectively) a
588 single path. */
589 if (rel_targets->nelts == 0)
590 APR_ARRAY_PUSH(rel_targets, const char *) = "";
591
592 /* Crawl the working copy for commit items. */
593 SVN_ERR(harvest_committables(&commit_items, NULL /*committables_by_path_p*/,
594 &lock_tokens,
595 base_abspath, rel_targets,
596 -1 /*depth_empty_start*/,
597 depth,
598 FALSE /*just_locked*/,
599 changelists,
600 ctx, pool, pool));
601 if (!commit_items)
602 {
603 return SVN_NO_ERROR;
604 }
605
606 SVN_ERR(svn_client__wc_node_get_base(&base,
607 src_wc_abspath, ctx->wc_ctx, pool, pool));
608 base_url = base->url;
609 /* Sort our COMMIT_ITEMS by URL and find their relative URL-paths. */
610 SVN_ERR(svn_client__condense_commit_items2(base_url, commit_items, pool));
611
612 saved_notify_func = ctx->notify_func2;
613 saved_notify_baton = ctx->notify_baton2;
614 ctx->notify_func2 = notify_func;
615 ctx->notify_baton2 = notify_baton;
616 /* BASE_URL is only used here in notifications & errors */
617 SVN_ERR(svn_client__do_commit(base_url, commit_items,
618 editor, edit_baton,
619 NULL /*notify_prefix*/, NULL /*sha1_checksums*/,
620 ctx, pool, pool));
621 ctx->notify_func2 = saved_notify_func;
622 ctx->notify_baton2 = saved_notify_baton;
623 return SVN_NO_ERROR;
624 }
625
626 svn_error_t *
svn_client_commit6(const apr_array_header_t * targets,svn_depth_t depth,svn_boolean_t keep_locks,svn_boolean_t keep_changelists,svn_boolean_t commit_as_operations,svn_boolean_t include_file_externals,svn_boolean_t include_dir_externals,const apr_array_header_t * changelists,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)627 svn_client_commit6(const apr_array_header_t *targets,
628 svn_depth_t depth,
629 svn_boolean_t keep_locks,
630 svn_boolean_t keep_changelists,
631 svn_boolean_t commit_as_operations,
632 svn_boolean_t include_file_externals,
633 svn_boolean_t include_dir_externals,
634 const apr_array_header_t *changelists,
635 const apr_hash_t *revprop_table,
636 svn_commit_callback2_t commit_callback,
637 void *commit_baton,
638 svn_client_ctx_t *ctx,
639 apr_pool_t *pool)
640 {
641 const svn_delta_editor_t *editor;
642 void *edit_baton;
643 struct capture_baton_t cb;
644 svn_ra_session_t *ra_session;
645 const char *log_msg;
646 const char *base_abspath;
647 const char *base_url;
648 apr_array_header_t *rel_targets;
649 apr_array_header_t *lock_targets;
650 apr_array_header_t *locks_obtained;
651 apr_hash_t *committables_by_path;
652 apr_hash_t *lock_tokens;
653 apr_hash_t *sha1_checksums;
654 apr_array_header_t *commit_items;
655 svn_error_t *cmt_err = SVN_NO_ERROR;
656 svn_error_t *bump_err = SVN_NO_ERROR;
657 svn_error_t *unlock_err = SVN_NO_ERROR;
658 svn_boolean_t commit_in_progress = FALSE;
659 svn_boolean_t timestamp_sleep = FALSE;
660 svn_commit_info_t *commit_info = NULL;
661 apr_pool_t *iterpool = svn_pool_create(pool);
662 const char *current_abspath;
663 const char *notify_prefix;
664 int depth_empty_after = -1;
665 apr_hash_t *move_youngest = NULL;
666 int i;
667
668 SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
669
670 /* Committing URLs doesn't make sense, so error if it's tried. */
671 for (i = 0; i < targets->nelts; i++)
672 {
673 const char *target = APR_ARRAY_IDX(targets, i, const char *);
674 if (svn_path_is_url(target))
675 return svn_error_createf
676 (SVN_ERR_ILLEGAL_TARGET, NULL,
677 _("'%s' is a URL, but URLs cannot be commit targets"), target);
678 }
679
680 /* Condense the target list. This makes all targets absolute. */
681 SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
682 FALSE, pool, iterpool));
683
684 /* No targets means nothing to commit, so just return. */
685 if (base_abspath == NULL)
686 return SVN_NO_ERROR;
687
688 SVN_ERR_ASSERT(rel_targets != NULL);
689
690 /* If we calculated only a base and no relative targets, this
691 must mean that we are being asked to commit (effectively) a
692 single path. */
693 if (rel_targets->nelts == 0)
694 APR_ARRAY_PUSH(rel_targets, const char *) = "";
695
696 if (include_file_externals || include_dir_externals)
697 {
698 if (depth != svn_depth_unknown && depth != svn_depth_infinity)
699 {
700 /* All targets after this will be handled as depth empty */
701 depth_empty_after = rel_targets->nelts;
702 }
703
704 SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
705 include_file_externals,
706 include_dir_externals,
707 depth, ctx,
708 pool, pool));
709 }
710
711 SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
712 rel_targets, pool, iterpool));
713
714 locks_obtained = apr_array_make(pool, lock_targets->nelts,
715 sizeof(const char *));
716
717 for (i = 0; i < lock_targets->nelts; i++)
718 {
719 const char *lock_root;
720 const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
721
722 svn_pool_clear(iterpool);
723
724 cmt_err = svn_error_trace(
725 svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
726 FALSE, pool, iterpool));
727
728 if (cmt_err)
729 goto cleanup;
730
731 APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
732 }
733
734 /* Determine prefix to strip from the commit notify messages */
735 SVN_ERR(svn_dirent_get_absolute(¤t_abspath, "", pool));
736 notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
737 base_abspath,
738 pool);
739
740 /* Crawl the working copy for commit items. */
741 cmt_err = svn_error_trace(
742 harvest_committables(&commit_items, &committables_by_path,
743 &lock_tokens,
744 base_abspath,
745 rel_targets,
746 depth_empty_after,
747 depth,
748 ! keep_locks,
749 changelists,
750 ctx,
751 pool,
752 iterpool));
753 svn_pool_clear(iterpool);
754
755 if (cmt_err)
756 goto cleanup;
757
758 if (!commit_items)
759 {
760 goto cleanup; /* Nothing to do */
761 }
762
763 /* If our array of targets contains only locks (and no actual file
764 or prop modifications), then we return here to avoid committing a
765 revision with no changes. */
766 {
767 svn_boolean_t found_changed_path = FALSE;
768
769 for (i = 0; i < commit_items->nelts; ++i)
770 {
771 svn_client_commit_item3_t *item =
772 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
773
774 if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
775 {
776 found_changed_path = TRUE;
777 break;
778 }
779 }
780
781 if (!found_changed_path)
782 goto cleanup;
783 }
784
785 /* For every target that was moved verify that both halves of the
786 * move are part of the commit. */
787 for (i = 0; i < commit_items->nelts; i++)
788 {
789 svn_client_commit_item3_t *item =
790 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
791
792 svn_pool_clear(iterpool);
793
794 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
795 {
796 /* ### item->moved_from_abspath contains the move origin */
797 const char *moved_from_abspath;
798 const char *delete_op_root_abspath;
799
800 cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
801 &moved_from_abspath,
802 &delete_op_root_abspath,
803 ctx->wc_ctx, item->path,
804 iterpool, iterpool));
805 if (cmt_err)
806 goto cleanup;
807
808 if (moved_from_abspath && delete_op_root_abspath)
809 {
810 svn_client_commit_item3_t *delete_half =
811 svn_hash_gets(committables_by_path, delete_op_root_abspath);
812
813 if (!delete_half)
814 {
815 cmt_err = svn_error_createf(
816 SVN_ERR_ILLEGAL_TARGET, NULL,
817 _("Cannot commit '%s' because it was moved from "
818 "'%s' which is not part of the commit; both "
819 "sides of the move must be committed together"),
820 svn_dirent_local_style(item->path, iterpool),
821 svn_dirent_local_style(delete_op_root_abspath,
822 iterpool));
823
824 if (ctx->notify_func2)
825 {
826 svn_wc_notify_t *notify;
827 notify = svn_wc_create_notify(
828 delete_op_root_abspath,
829 svn_wc_notify_failed_requires_target,
830 iterpool);
831 notify->err = cmt_err;
832
833 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
834 }
835
836 goto cleanup;
837 }
838 else if (delete_half->revision == item->copyfrom_rev)
839 {
840 /* Ok, now we know that we perform an out-of-date check
841 on the copyfrom location. Remember this for a fixup
842 round right before committing. */
843
844 if (!move_youngest)
845 move_youngest = apr_hash_make(pool);
846
847 svn_hash_sets(move_youngest, item->path, item);
848 }
849 }
850 }
851
852 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
853 {
854 const char *moved_to_abspath;
855 const char *copy_op_root_abspath;
856
857 cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
858 &moved_to_abspath,
859 ©_op_root_abspath,
860 ctx->wc_ctx, item->path,
861 iterpool, iterpool));
862 if (cmt_err)
863 goto cleanup;
864
865 if (moved_to_abspath && copy_op_root_abspath &&
866 strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
867 svn_hash_gets(committables_by_path, copy_op_root_abspath)
868 == NULL)
869 {
870 cmt_err = svn_error_createf(
871 SVN_ERR_ILLEGAL_TARGET, NULL,
872 _("Cannot commit '%s' because it was moved to '%s' "
873 "which is not part of the commit; both sides of "
874 "the move must be committed together"),
875 svn_dirent_local_style(item->path, iterpool),
876 svn_dirent_local_style(copy_op_root_abspath,
877 iterpool));
878
879 if (ctx->notify_func2)
880 {
881 svn_wc_notify_t *notify;
882 notify = svn_wc_create_notify(
883 copy_op_root_abspath,
884 svn_wc_notify_failed_requires_target,
885 iterpool);
886 notify->err = cmt_err;
887
888 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
889 }
890
891 goto cleanup;
892 }
893 }
894 }
895
896 /* Go get a log message. If an error occurs, or no log message is
897 specified, abort the operation. */
898 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
899 {
900 const char *tmp_file;
901 cmt_err = svn_error_trace(
902 svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
903 ctx, pool));
904
905 if (cmt_err || (! log_msg))
906 goto cleanup;
907 }
908 else
909 log_msg = "";
910
911 /* Sort and condense our COMMIT_ITEMS. */
912 cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
913 commit_items,
914 pool));
915
916 if (cmt_err)
917 goto cleanup;
918
919 /* Collect our lock tokens with paths relative to base_url. */
920 cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
921 base_url, pool));
922
923 if (cmt_err)
924 goto cleanup;
925
926 cb.original_callback = commit_callback;
927 cb.original_baton = commit_baton;
928 cb.info = &commit_info;
929 cb.pool = pool;
930
931 /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
932 * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
933 * parent of nested working copies. We don't support commits to multiple
934 * repositories so using the first WC to get the RA session is safe. */
935 cmt_err = svn_error_trace(
936 svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
937 APR_ARRAY_IDX(lock_targets,
938 0,
939 const char *),
940 commit_items,
941 TRUE, TRUE, ctx,
942 pool, pool));
943
944 if (cmt_err)
945 goto cleanup;
946
947 if (move_youngest != NULL)
948 {
949 apr_hash_index_t *hi;
950 svn_revnum_t youngest;
951
952 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, pool));
953
954 for (hi = apr_hash_first(iterpool, move_youngest);
955 hi;
956 hi = apr_hash_next(hi))
957 {
958 svn_client_commit_item3_t *item = apr_hash_this_val(hi);
959
960 /* We delete the original side with its original revision and will
961 receive an out-of-date error if that node changed since that
962 revision.
963
964 The copy is of that same revision and we know that this revision
965 didn't change between this revision and youngest. So we can just
966 as well commit a copy from youngest.
967
968 Note that it is still possible to see gaps between the delete and
969 copy revisions as the repository might handle multiple commits
970 at the same time (or when an out of date proxy is involved), but
971 in general it should decrease the number of gaps. */
972
973 if (item->copyfrom_rev < youngest)
974 item->copyfrom_rev = youngest;
975 }
976 }
977
978 cmt_err = svn_error_trace(
979 get_ra_editor(&editor, &edit_baton, ra_session, ctx,
980 log_msg, commit_items, revprop_table,
981 lock_tokens, keep_locks, capture_commit_info,
982 &cb, pool));
983
984 if (cmt_err)
985 goto cleanup;
986
987 /* Make a note that we have a commit-in-progress. */
988 commit_in_progress = TRUE;
989
990 /* We'll assume that, once we pass this point, we are going to need to
991 * sleep for timestamps. Really, we may not need to do unless and until
992 * we reach the point where we post-commit 'bump' the WC metadata. */
993 timestamp_sleep = TRUE;
994
995 /* Perform the commit. */
996 cmt_err = svn_error_trace(
997 svn_client__do_commit(base_url, commit_items, editor, edit_baton,
998 notify_prefix, &sha1_checksums, ctx, pool,
999 iterpool));
1000
1001 /* Handle a successful commit. */
1002 if ((! cmt_err)
1003 || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
1004 {
1005 svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
1006
1007 /* Make a note that our commit is finished. */
1008 commit_in_progress = FALSE;
1009
1010 for (i = 0; i < commit_items->nelts; i++)
1011 {
1012 svn_client_commit_item3_t *item
1013 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1014
1015 svn_pool_clear(iterpool);
1016 bump_err = post_process_commit_item(
1017 queue, item, ctx->wc_ctx,
1018 keep_changelists, keep_locks, commit_as_operations,
1019 svn_hash_gets(sha1_checksums, item->path),
1020 iterpool);
1021 if (bump_err)
1022 goto cleanup;
1023 }
1024
1025 SVN_ERR_ASSERT(commit_info);
1026 bump_err = svn_wc_process_committed_queue2(
1027 queue, ctx->wc_ctx,
1028 commit_info->revision,
1029 commit_info->date,
1030 commit_info->author,
1031 ctx->cancel_func, ctx->cancel_baton,
1032 iterpool);
1033
1034 if (bump_err)
1035 goto cleanup;
1036 }
1037
1038 cleanup:
1039 /* Sleep to ensure timestamp integrity. BASE_ABSPATH may have been
1040 removed by the commit or it may the common ancestor of multiple
1041 working copies. */
1042 if (timestamp_sleep)
1043 {
1044 const char *sleep_abspath;
1045 svn_error_t *err = svn_wc__get_wcroot(&sleep_abspath, ctx->wc_ctx,
1046 base_abspath, pool, pool);
1047 if (err)
1048 {
1049 svn_error_clear(err);
1050 sleep_abspath = base_abspath;
1051 }
1052
1053 svn_io_sleep_for_timestamps(sleep_abspath, pool);
1054 }
1055
1056 /* Abort the commit if it is still in progress. */
1057 svn_pool_clear(iterpool); /* Close open handles before aborting */
1058 if (commit_in_progress)
1059 cmt_err = svn_error_compose_create(cmt_err,
1060 editor->abort_edit(edit_baton, pool));
1061
1062 /* A bump error is likely to occur while running a working copy log file,
1063 explicitly unlocking and removing temporary files would be wrong in
1064 that case. A commit error (cmt_err) should only occur before any
1065 attempt to modify the working copy, so it doesn't prevent explicit
1066 clean-up. */
1067 if (! bump_err)
1068 {
1069 /* Release all locks we obtained */
1070 for (i = 0; i < locks_obtained->nelts; i++)
1071 {
1072 const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
1073 const char *);
1074
1075 svn_pool_clear(iterpool);
1076
1077 unlock_err = svn_error_compose_create(
1078 svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
1079 iterpool),
1080 unlock_err);
1081 }
1082 }
1083
1084 svn_pool_destroy(iterpool);
1085
1086 return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,
1087 pool));
1088 }
1089