2 * commit_util.c: Driver for the WC commit process.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
29 #include <apr_pools.h>
34 #include "svn_dirent_uri.h"
36 #include "svn_types.h"
37 #include "svn_pools.h"
38 #include "svn_props.h"
44 #include "svn_private_config.h"
45 #include "private/svn_wc_private.h"
46 #include "private/svn_client_private.h"
47 #include "private/svn_sorts_private.h"
49 /*** Uncomment this to turn on commit driver debugging. ***/
51 #define SVN_CLIENT_COMMIT_DEBUG
54 /* Wrap an RA error in a nicer error if one is available. */
56 fixup_commit_error(const char *local_abspath,
61 svn_client_ctx_t *ctx,
62 apr_pool_t *scratch_pool)
64 if (err->apr_err == SVN_ERR_FS_NOT_FOUND
65 || err->apr_err == SVN_ERR_FS_CONFLICT
66 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS
67 || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE
68 || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND
69 || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS
70 || err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED
71 || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE))
73 if (ctx->notify_func2)
75 svn_wc_notify_t *notify;
78 notify = svn_wc_create_notify(local_abspath,
79 svn_wc_notify_failed_out_of_date,
82 notify = svn_wc_create_notify_url(
83 svn_path_url_add_component2(base_url, path,
85 svn_wc_notify_failed_out_of_date,
91 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
94 return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err,
96 ? _("Directory '%s' is out of date")
97 : _("File '%s' is out of date")),
99 ? svn_dirent_local_style(local_abspath,
101 : svn_path_url_add_component2(base_url,
105 else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN)
106 || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH
107 || err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN
108 || err->apr_err == SVN_ERR_RA_NOT_LOCKED)
110 if (ctx->notify_func2)
112 svn_wc_notify_t *notify;
115 notify = svn_wc_create_notify(local_abspath,
116 svn_wc_notify_failed_locked,
119 notify = svn_wc_create_notify_url(
120 svn_path_url_add_component2(base_url, path,
122 svn_wc_notify_failed_locked,
128 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
131 return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err,
132 (kind == svn_node_dir
133 ? _("Directory '%s' is locked in another working copy")
134 : _("File '%s' is locked in another working copy")),
136 ? svn_dirent_local_style(local_abspath,
138 : svn_path_url_add_component2(base_url,
142 else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)
143 || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE)
145 if (ctx->notify_func2)
147 svn_wc_notify_t *notify;
150 notify = svn_wc_create_notify(
152 svn_wc_notify_failed_forbidden_by_server,
155 notify = svn_wc_create_notify_url(
156 svn_path_url_add_component2(base_url, path,
158 svn_wc_notify_failed_forbidden_by_server,
164 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
167 return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err,
168 (kind == svn_node_dir
169 ? _("Changing directory '%s' is forbidden by the server")
170 : _("Changing file '%s' is forbidden by the server")),
172 ? svn_dirent_local_style(local_abspath,
174 : svn_path_url_add_component2(base_url,
183 /*** Harvesting Commit Candidates ***/
186 /* Add a new commit candidate (described by all parameters except
187 `COMMITTABLES') to the COMMITTABLES hash. All of the commit item's
188 members are allocated out of RESULT_POOL.
190 If the state flag specifies that a lock must be used, store the token in LOCK
194 add_committable(svn_client__committables_t *committables,
195 const char *local_abspath,
196 svn_node_kind_t kind,
197 const char *repos_root_url,
198 const char *repos_relpath,
199 svn_revnum_t revision,
200 const char *copyfrom_relpath,
201 svn_revnum_t copyfrom_rev,
202 const char *moved_from_abspath,
203 apr_byte_t state_flags,
204 apr_hash_t *lock_tokens,
205 const svn_lock_t *lock,
206 apr_pool_t *result_pool,
207 apr_pool_t *scratch_pool)
209 apr_array_header_t *array;
210 svn_client_commit_item3_t *new_item;
213 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
214 SVN_ERR_ASSERT(repos_root_url && repos_relpath);
216 /* ### todo: Get the canonical repository for this item, which will
217 be the real key for the COMMITTABLES hash, instead of the above
219 array = svn_hash_gets(committables->by_repository, repos_root_url);
221 /* E-gads! There is no array for this repository yet! Oh, no
222 problem, we'll just create (and add to the hash) one. */
225 array = apr_array_make(result_pool, 1, sizeof(new_item));
226 svn_hash_sets(committables->by_repository,
227 apr_pstrdup(result_pool, repos_root_url), array);
230 /* Now update pointer values, ensuring that their allocations live
232 new_item = svn_client_commit_item3_create(result_pool);
233 new_item->path = apr_pstrdup(result_pool, local_abspath);
234 new_item->kind = kind;
235 new_item->url = svn_path_url_add_component2(repos_root_url,
238 new_item->revision = revision;
239 new_item->copyfrom_url = copyfrom_relpath
240 ? svn_path_url_add_component2(repos_root_url,
244 new_item->copyfrom_rev = copyfrom_rev;
245 new_item->state_flags = state_flags;
246 new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
247 sizeof(svn_prop_t *));
249 if (moved_from_abspath)
250 new_item->moved_from_abspath = apr_pstrdup(result_pool,
253 /* Now, add the commit item to the array. */
254 APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item;
256 /* ... and to the hash. */
257 svn_hash_sets(committables->by_path, new_item->path, new_item);
261 && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN))
263 svn_hash_sets(lock_tokens, new_item->url,
264 apr_pstrdup(result_pool, lock->token));
270 /* If there is a commit item for PATH in COMMITTABLES, return it, else
271 return NULL. Use POOL for temporary allocation only. */
272 static svn_client_commit_item3_t *
273 look_up_committable(svn_client__committables_t *committables,
277 return (svn_client_commit_item3_t *)
278 svn_hash_gets(committables->by_path, path);
281 /* Helper function for svn_client__harvest_committables().
282 * Determine whether we are within a tree-conflicted subtree of the
283 * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */
285 bail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx,
286 const char *local_abspath,
287 svn_wc_notify_func2_t notify_func,
289 apr_pool_t *scratch_pool)
291 const char *wcroot_abspath;
293 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath,
294 scratch_pool, scratch_pool));
296 local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
298 while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath))
300 svn_boolean_t tree_conflicted;
302 /* Check if the parent has tree conflicts */
303 SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
304 wc_ctx, local_abspath, scratch_pool));
307 if (notify_func != NULL)
309 notify_func(notify_baton,
310 svn_wc_create_notify(local_abspath,
311 svn_wc_notify_failed_conflict,
316 return svn_error_createf(
317 SVN_ERR_WC_FOUND_CONFLICT, NULL,
318 _("Aborting commit: '%s' remains in tree-conflict"),
319 svn_dirent_local_style(local_abspath, scratch_pool));
323 if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
326 local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
333 /* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using
334 WC_CTX and add those candidates to COMMITTABLES. If in ADDS_ONLY modes,
335 only new additions are recognized.
337 DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH
338 when LOCAL_ABSPATH is itself a directory; see
339 svn_client__harvest_committables() for its behavior.
341 Lock tokens of candidates will be added to LOCK_TOKENS, if
342 non-NULL. JUST_LOCKED indicates whether to treat non-modified items with
343 lock tokens as commit candidates.
345 If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to
346 be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as
347 items to delete in the copy destination. COPY_MODE_ROOT should be set TRUE
348 for the first call for which COPY_MODE is TRUE, i.e. not for the
349 recursive calls, and FALSE otherwise.
351 If CHANGELISTS is non-NULL, it is a hash whose keys are const char *
352 changelist names used as a restrictive filter
353 when harvesting committables; that is, don't add a path to
354 COMMITTABLES unless it's a member of one of those changelists.
356 IS_EXPLICIT_TARGET should always be passed as TRUE, except when
357 harvest_committables() calls itself in recursion. This provides a way to
358 tell whether LOCAL_ABSPATH was an original target or whether it was reached
359 by recursing deeper into a dir target. (This is used to skip all file
360 externals that aren't explicit commit targets.)
362 DANGLERS is a hash table mapping const char* absolute paths of a parent
363 to a const char * absolute path of a child. See the comment about
364 danglers at the top of svn_client__harvest_committables().
366 If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see
367 if the user has cancelled the operation.
369 Any items added to COMMITTABLES are allocated from the COMITTABLES
370 hash pool, not POOL. SCRATCH_POOL is used for temporary allocations. */
375 const char *root_abspath;
376 svn_client__committables_t *committables;
377 apr_hash_t *lock_tokens;
378 const char *commit_relpath; /* Valid for the harvest root */
380 svn_boolean_t just_locked;
381 apr_hash_t *changelists;
382 apr_hash_t *danglers;
383 svn_client__check_url_kind_t check_url_func;
384 void *check_url_baton;
385 svn_wc_notify_func2_t notify_func;
387 svn_wc_context_t *wc_ctx;
388 apr_pool_t *result_pool;
390 /* Harvester state */
391 const char *skip_below_abspath; /* If non-NULL, skip everything below */
395 harvest_status_callback(void *status_baton,
396 const char *local_abspath,
397 const svn_wc_status3_t *status,
398 apr_pool_t *scratch_pool);
401 harvest_committables(const char *local_abspath,
402 svn_client__committables_t *committables,
403 apr_hash_t *lock_tokens,
404 const char *copy_mode_relpath,
406 svn_boolean_t just_locked,
407 apr_hash_t *changelists,
408 apr_hash_t *danglers,
409 svn_client__check_url_kind_t check_url_func,
410 void *check_url_baton,
411 svn_cancel_func_t cancel_func,
413 svn_wc_notify_func2_t notify_func,
415 svn_wc_context_t *wc_ctx,
416 apr_pool_t *result_pool,
417 apr_pool_t *scratch_pool)
419 struct harvest_baton baton;
421 SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked);
423 baton.root_abspath = local_abspath;
424 baton.committables = committables;
425 baton.lock_tokens = lock_tokens;
426 baton.commit_relpath = copy_mode_relpath;
428 baton.just_locked = just_locked;
429 baton.changelists = changelists;
430 baton.danglers = danglers;
431 baton.check_url_func = check_url_func;
432 baton.check_url_baton = check_url_baton;
433 baton.notify_func = notify_func;
434 baton.notify_baton = notify_baton;
435 baton.wc_ctx = wc_ctx;
436 baton.result_pool = result_pool;
438 baton.skip_below_abspath = NULL;
440 SVN_ERR(svn_wc_walk_status(wc_ctx,
443 (copy_mode_relpath != NULL) /* get_all */,
444 FALSE /* no_ignore */,
445 FALSE /* ignore_text_mods */,
446 NULL /* ignore_patterns */,
447 harvest_status_callback,
449 cancel_func, cancel_baton,
456 harvest_not_present_for_copy(svn_wc_context_t *wc_ctx,
457 const char *local_abspath,
458 svn_client__committables_t *committables,
459 const char *repos_root_url,
460 const char *commit_relpath,
461 svn_client__check_url_kind_t check_url_func,
462 void *check_url_baton,
463 apr_pool_t *result_pool,
464 apr_pool_t *scratch_pool)
466 const apr_array_header_t *children;
467 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
470 SVN_ERR_ASSERT(commit_relpath != NULL);
472 /* A function to retrieve not present children would be nice to have */
473 SVN_ERR(svn_wc__node_get_not_present_children(&children, wc_ctx,
475 scratch_pool, iterpool));
477 for (i = 0; i < children->nelts; i++)
479 const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
480 const char *name = svn_dirent_basename(this_abspath, NULL);
481 const char *this_commit_relpath;
482 svn_boolean_t not_present;
483 svn_node_kind_t kind;
485 svn_pool_clear(iterpool);
487 SVN_ERR(svn_wc__node_is_not_present(¬_present, NULL, NULL, wc_ctx,
488 this_abspath, FALSE, scratch_pool));
491 continue; /* Node is replaced */
493 this_commit_relpath = svn_relpath_join(commit_relpath, name,
496 /* We should check if we should really add a delete operation */
499 svn_revnum_t parent_rev;
500 const char *parent_repos_relpath;
501 const char *parent_repos_root_url;
502 const char *node_url;
504 /* Determine from what parent we would be the deleted child */
505 SVN_ERR(svn_wc__node_get_origin(
506 NULL, &parent_rev, &parent_repos_relpath,
507 &parent_repos_root_url, NULL, NULL, NULL,
509 svn_dirent_dirname(this_abspath,
511 FALSE, scratch_pool, scratch_pool));
513 node_url = svn_path_url_add_component2(
514 svn_path_url_add_component2(parent_repos_root_url,
515 parent_repos_relpath,
517 svn_dirent_basename(this_abspath, NULL),
520 SVN_ERR(check_url_func(check_url_baton, &kind,
521 node_url, parent_rev, iterpool));
523 if (kind == svn_node_none)
524 continue; /* This node can't be deleted */
527 SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath,
528 TRUE, TRUE, scratch_pool));
530 SVN_ERR(add_committable(committables, this_abspath, kind,
534 NULL /* copyfrom_relpath */,
535 SVN_INVALID_REVNUM /* copyfrom_rev */,
536 NULL /* moved_from_abspath */,
537 SVN_CLIENT_COMMIT_ITEM_DELETE,
539 result_pool, scratch_pool));
542 svn_pool_destroy(iterpool);
546 /* Implements svn_wc_status_func4_t */
548 harvest_status_callback(void *status_baton,
549 const char *local_abspath,
550 const svn_wc_status3_t *status,
551 apr_pool_t *scratch_pool)
553 apr_byte_t state_flags = 0;
554 svn_revnum_t node_rev;
555 const char *cf_relpath = NULL;
556 svn_revnum_t cf_rev = SVN_INVALID_REVNUM;
557 svn_boolean_t matches_changelists;
558 svn_boolean_t is_added;
559 svn_boolean_t is_deleted;
560 svn_boolean_t is_replaced;
561 svn_boolean_t is_op_root;
562 svn_revnum_t original_rev;
563 const char *original_relpath;
564 svn_boolean_t copy_mode;
566 struct harvest_baton *baton = status_baton;
567 svn_boolean_t is_harvest_root =
568 (strcmp(baton->root_abspath, local_abspath) == 0);
569 svn_client__committables_t *committables = baton->committables;
570 const char *repos_root_url = status->repos_root_url;
571 const char *commit_relpath = NULL;
572 svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root);
573 svn_boolean_t just_locked = baton->just_locked;
574 apr_hash_t *changelists = baton->changelists;
575 svn_wc_notify_func2_t notify_func = baton->notify_func;
576 void *notify_baton = baton->notify_baton;
577 svn_wc_context_t *wc_ctx = baton->wc_ctx;
578 apr_pool_t *result_pool = baton->result_pool;
579 const char *moved_from_abspath = NULL;
581 if (baton->commit_relpath)
582 commit_relpath = svn_relpath_join(
583 baton->commit_relpath,
584 svn_dirent_skip_ancestor(baton->root_abspath,
588 copy_mode = (commit_relpath != NULL);
590 if (baton->skip_below_abspath
591 && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath))
596 baton->skip_below_abspath = NULL; /* We have left the skip tree */
598 /* Return early for nodes that don't have a committable status */
599 switch (status->node_status)
601 case svn_wc_status_unversioned:
602 case svn_wc_status_ignored:
603 case svn_wc_status_external:
604 case svn_wc_status_none:
605 /* Unversioned nodes aren't committable, but are reported by the status
607 But if the unversioned node is the root of the walk, we have a user
610 return svn_error_createf(
611 SVN_ERR_ILLEGAL_TARGET, NULL,
612 _("'%s' is not under version control"),
613 svn_dirent_local_style(local_abspath, scratch_pool));
615 case svn_wc_status_normal:
616 /* Status normal nodes aren't modified, so we don't have to commit them
617 when we perform a normal commit. But if a node is conflicted we want
618 to stop the commit and if we are collecting lock tokens we want to
621 When in copy mode we need to compare the revision of the node against
622 the parent node to copy mixed-revision base nodes properly */
623 if (!copy_mode && !status->conflicted
624 && !(just_locked && status->lock))
632 /* Early out if the item is already marked as committable. */
633 if (look_up_committable(committables, local_abspath, scratch_pool))
636 SVN_ERR_ASSERT((copy_mode && commit_relpath)
637 || (! copy_mode && ! commit_relpath));
638 SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root);
640 /* Save the result for reuse. */
641 matches_changelists = ((changelists == NULL)
642 || (status->changelist != NULL
643 && svn_hash_gets(changelists, status->changelist)
647 if (status->kind != svn_node_dir && ! matches_changelists)
652 /* If NODE is in our changelist, then examine it for conflicts. We
653 need to bail out if any conflicts exist.
654 The status walker checked for conflict marker removal. */
655 if (status->conflicted && matches_changelists)
657 if (notify_func != NULL)
659 notify_func(notify_baton,
660 svn_wc_create_notify(local_abspath,
661 svn_wc_notify_failed_conflict,
666 return svn_error_createf(
667 SVN_ERR_WC_FOUND_CONFLICT, NULL,
668 _("Aborting commit: '%s' remains in conflict"),
669 svn_dirent_local_style(local_abspath, scratch_pool));
671 else if (status->node_status == svn_wc_status_obstructed)
673 /* A node's type has changed before attempting to commit.
674 This also catches symlink vs non symlink changes */
676 if (notify_func != NULL)
678 notify_func(notify_baton,
679 svn_wc_create_notify(local_abspath,
680 svn_wc_notify_failed_obstruction,
685 return svn_error_createf(
686 SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
687 _("Node '%s' has unexpectedly changed kind"),
688 svn_dirent_local_style(local_abspath, scratch_pool));
691 if (status->conflicted && status->kind == svn_node_unknown)
692 return SVN_NO_ERROR; /* Ignore delete-delete conflict */
694 /* Return error on unknown path kinds. We check both the entry and
695 the node itself, since a path might have changed kind since its
696 entry was written. */
697 SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted,
701 &original_rev, &original_relpath,
702 wc_ctx, local_abspath,
703 scratch_pool, scratch_pool));
705 /* Hande file externals only when passed as explicit target. Note that
706 * svn_client_commit6() passes all committable externals in as explicit
707 * targets iff they count. */
708 if (status->file_external && !is_harvest_root)
713 if (status->node_status == svn_wc_status_missing && matches_changelists)
715 /* Added files and directories must exist. See issue #3198. */
716 if (is_added && is_op_root)
718 if (notify_func != NULL)
720 notify_func(notify_baton,
721 svn_wc_create_notify(local_abspath,
722 svn_wc_notify_failed_missing,
726 return svn_error_createf(
727 SVN_ERR_WC_PATH_NOT_FOUND, NULL,
728 _("'%s' is scheduled for addition, but is missing"),
729 svn_dirent_local_style(local_abspath, scratch_pool));
735 if (is_deleted && !is_op_root /* && !is_added */)
736 return SVN_NO_ERROR; /* Not an operational delete and not an add. */
738 /* Check for the deletion case.
739 * We delete explicitly deleted nodes (duh!)
740 * We delete not-present children of copies
741 * We delete nodes that directly replace a node in its ancestor
744 if (is_deleted || is_replaced)
745 state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
747 /* Check for adds and copies */
748 if (is_added && is_op_root)
750 /* Root of local add or copy */
751 state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
753 if (original_relpath)
756 state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
757 cf_relpath = original_relpath;
758 cf_rev = original_rev;
760 if (status->moved_from_abspath && !copy_mode)
762 state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE;
763 moved_from_abspath = status->moved_from_abspath;
768 /* Further copies may occur in copy mode. */
770 && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
772 svn_revnum_t dir_rev = SVN_INVALID_REVNUM;
773 const char *dir_repos_relpath = NULL;
775 if (!copy_mode_root && !is_added)
776 SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, &dir_repos_relpath, NULL,
778 wc_ctx, svn_dirent_dirname(local_abspath,
780 FALSE /* ignore_enoent */,
781 scratch_pool, scratch_pool));
783 if (copy_mode_root || status->switched || node_rev != dir_rev)
785 state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD
786 | SVN_CLIENT_COMMIT_ITEM_IS_COPY);
790 /* Copy from original location */
791 cf_rev = original_rev;
792 cf_relpath = original_relpath;
796 /* Copy BASE location, to represent a mixed-rev or switch copy */
797 cf_rev = status->revision;
798 cf_relpath = status->repos_relpath;
801 if (!copy_mode_root && !is_added && baton->check_url_func
802 && dir_repos_relpath)
804 svn_node_kind_t me_kind;
805 /* Maybe we need to issue an delete (mixed rev/switched) */
807 SVN_ERR(baton->check_url_func(
808 baton->check_url_baton, &me_kind,
809 svn_path_url_add_component2(repos_root_url,
810 svn_relpath_join(dir_repos_relpath,
811 svn_dirent_basename(local_abspath,
815 dir_rev, scratch_pool));
816 if (me_kind != svn_node_none)
817 state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
822 if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
823 || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
825 svn_boolean_t text_mod = FALSE;
826 svn_boolean_t prop_mod = FALSE;
828 if (status->kind == svn_node_file)
830 /* Check for text modifications on files */
831 if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
832 && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
834 text_mod = TRUE; /* Local added files are always modified */
837 text_mod = (status->text_status != svn_wc_status_normal);
840 prop_mod = (status->prop_status != svn_wc_status_normal
841 && status->prop_status != svn_wc_status_none);
843 /* Set text/prop modification flags accordingly. */
845 state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
847 state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
850 /* If the entry has a lock token and it is already a commit candidate,
851 or the caller wants unmodified locked items to be treated as
852 such, note this fact. */
853 if (status->lock && baton->lock_tokens && (state_flags || just_locked))
855 state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN;
858 /* Now, if this is something to commit, add it to our list. */
859 if (matches_changelists
862 /* Finally, add the committable item. */
863 SVN_ERR(add_committable(committables, local_abspath,
868 : status->repos_relpath,
876 baton->lock_tokens, status->lock,
877 result_pool, scratch_pool));
880 /* Fetch lock tokens for descendants of deleted BASE nodes. */
881 if (matches_changelists
882 && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
884 && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */
885 && baton->lock_tokens)
887 apr_hash_t *local_relpath_tokens;
888 apr_hash_index_t *hi;
890 SVN_ERR(svn_wc__node_get_lock_tokens_recursive(
891 &local_relpath_tokens, wc_ctx, local_abspath,
892 result_pool, scratch_pool));
894 /* Add tokens to existing hash. */
895 for (hi = apr_hash_first(scratch_pool, local_relpath_tokens);
897 hi = apr_hash_next(hi))
903 apr_hash_this(hi, &key, &klen, &val);
905 apr_hash_set(baton->lock_tokens, key, klen, val);
909 /* Make sure we check for dangling children on additions
911 We perform this operation on the harvest root, and on roots caused by
912 changelist filtering.
914 if (matches_changelists
915 && (is_harvest_root || baton->changelists)
917 && (is_added || (is_deleted && is_op_root && status->copied))
920 /* If a node is added, its parent must exist in the repository at the
921 time of committing */
922 apr_hash_t *danglers = baton->danglers;
923 svn_boolean_t parent_added;
924 const char *parent_abspath = svn_dirent_dirname(local_abspath,
927 /* First check if parent is already in the list of commits
928 (Common case for GUI clients that provide a list of commit targets) */
929 if (look_up_committable(committables, parent_abspath, scratch_pool))
930 parent_added = FALSE; /* Skip all expensive checks */
932 SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath,
937 const char *copy_root_abspath;
938 svn_boolean_t parent_is_copy;
940 /* The parent is added, so either it is a copy, or a locally added
941 * directory. In either case, we require the op-root of the parent
942 * to be part of the commit. See issue #4059. */
943 SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL,
944 NULL, NULL, ©_root_abspath,
945 wc_ctx, parent_abspath,
946 FALSE, scratch_pool, scratch_pool));
949 parent_abspath = copy_root_abspath;
951 if (!svn_hash_gets(danglers, parent_abspath))
953 svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath),
954 apr_pstrdup(result_pool, local_abspath));
959 if (is_deleted && !is_added)
961 /* Skip all descendants */
962 if (status->kind == svn_node_dir)
963 baton->skip_below_abspath = apr_pstrdup(baton->result_pool,
968 /* Recursively handle each node according to depth, except when the
969 node is only being deleted, or is in an added tree (as added trees
970 use the normal commit handling). */
971 if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir)
973 SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables,
974 repos_root_url, commit_relpath,
975 baton->check_url_func,
976 baton->check_url_baton,
977 result_pool, scratch_pool));
983 /* Baton for handle_descendants */
984 struct handle_descendants_baton
986 svn_wc_context_t *wc_ctx;
987 svn_cancel_func_t cancel_func;
989 svn_client__check_url_kind_t check_url_func;
990 void *check_url_baton;
991 svn_client__committables_t *committables;
994 /* Helper for the commit harvesters */
996 handle_descendants(void *baton,
997 const void *key, apr_ssize_t klen, void *val,
1000 struct handle_descendants_baton *hdb = baton;
1001 apr_array_header_t *commit_items = val;
1002 apr_pool_t *iterpool = svn_pool_create(pool);
1003 const char *repos_root_url = key;
1006 for (i = 0; i < commit_items->nelts; i++)
1008 svn_client_commit_item3_t *item =
1009 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1010 const apr_array_header_t *absent_descendants;
1013 /* Is this a copy operation? */
1014 if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1015 || ! item->copyfrom_url)
1018 if (hdb->cancel_func)
1019 SVN_ERR(hdb->cancel_func(hdb->cancel_baton));
1021 svn_pool_clear(iterpool);
1023 SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants,
1024 hdb->wc_ctx, item->path,
1025 iterpool, iterpool));
1027 for (j = 0; j < absent_descendants->nelts; j++)
1029 svn_node_kind_t kind;
1030 svn_client_commit_item3_t *desc_item;
1031 const char *relpath = APR_ARRAY_IDX(absent_descendants, j,
1033 const char *local_abspath = svn_dirent_join(item->path, relpath,
1036 /* ### Need a sub-iterpool? */
1039 /* We found a 'not present' descendant during a copy (at op_depth>0),
1040 this is most commonly caused by copying some mixed revision tree.
1042 In this case not present can imply that the node does not exist
1043 in the parent revision, or that the node does. But we want to copy
1044 the working copy state in which it does not exist, but might be
1047 desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath);
1049 /* If the path has a commit operation (possibly at an higher
1050 op_depth, we might want to turn an add in a replace. */
1054 svn_boolean_t found_intermediate = FALSE;
1056 if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1057 continue; /* We already have a delete or replace */
1058 else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1059 continue; /* Not a copy/add, just a modification */
1061 dir = svn_dirent_dirname(local_abspath, iterpool);
1063 while (strcmp(dir, item->path))
1065 svn_client_commit_item3_t *i_item;
1067 i_item = svn_hash_gets(hdb->committables->by_path, dir);
1071 if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1072 || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1074 found_intermediate = TRUE;
1078 dir = svn_dirent_dirname(dir, iterpool);
1081 if (found_intermediate)
1082 continue; /* Some intermediate ancestor is an add or delete */
1084 /* Fall through to detect if we need to turn the add in a
1088 if (hdb->check_url_func)
1090 const char *from_url = svn_path_url_add_component2(
1091 item->copyfrom_url, relpath,
1094 SVN_ERR(hdb->check_url_func(hdb->check_url_baton,
1095 &kind, from_url, item->copyfrom_rev,
1098 if (kind == svn_node_none)
1099 continue; /* This node is already deleted */
1102 kind = svn_node_unknown; /* 'Ok' for a delete of something */
1106 /* Extend the existing add/copy item to create a replace */
1107 desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
1111 /* Add a new commit item that describes the delete */
1113 SVN_ERR(add_committable(hdb->committables,
1114 svn_dirent_join(item->path, relpath,
1118 svn_uri_skip_ancestor(
1120 svn_path_url_add_component2(item->url,
1125 NULL /* copyfrom_relpath */,
1127 NULL /* moved_from_abspath */,
1128 SVN_CLIENT_COMMIT_ITEM_DELETE,
1129 NULL /* lock tokens */,
1136 svn_pool_destroy(iterpool);
1137 return SVN_NO_ERROR;
1140 /* Allocate and initialize the COMMITTABLES structure from POOL.
1143 create_committables(svn_client__committables_t **committables,
1146 *committables = apr_palloc(pool, sizeof(**committables));
1148 (*committables)->by_repository = apr_hash_make(pool);
1149 (*committables)->by_path = apr_hash_make(pool);
1153 svn_client__harvest_committables(svn_client__committables_t **committables,
1154 apr_hash_t **lock_tokens,
1155 const char *base_dir_abspath,
1156 const apr_array_header_t *targets,
1157 int depth_empty_start,
1159 svn_boolean_t just_locked,
1160 const apr_array_header_t *changelists,
1161 svn_client__check_url_kind_t check_url_func,
1162 void *check_url_baton,
1163 svn_client_ctx_t *ctx,
1164 apr_pool_t *result_pool,
1165 apr_pool_t *scratch_pool)
1168 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1169 apr_hash_t *changelist_hash = NULL;
1170 struct handle_descendants_baton hdb;
1171 apr_hash_index_t *hi;
1173 /* It's possible that one of the named targets has a parent that is
1174 * itself scheduled for addition or replacement -- that is, the
1175 * parent is not yet versioned in the repository. This is okay, as
1176 * long as the parent itself is part of this same commit, either
1177 * directly, or by virtue of a grandparent, great-grandparent, etc,
1178 * being part of the commit.
1180 * Since we don't know what's included in the commit until we've
1181 * harvested all the targets, we can't reliably check this as we
1182 * go. So in `danglers', we record named targets whose parents
1183 * do not yet exist in the repository. Then after harvesting the total
1184 * commit group, we check to make sure those parents are included.
1186 * Each key of danglers is a parent which does not exist in the
1187 * repository. The (const char *) value is one of that parent's
1188 * children which is named as part of the commit; the child is
1189 * included only to make a better error message.
1191 * (The reason we don't bother to check unnamed -- i.e, implicit --
1192 * targets is that they can only join the commit if their parents
1193 * did too, so this situation can't arise for them.)
1195 apr_hash_t *danglers = apr_hash_make(scratch_pool);
1197 SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath));
1199 /* Create the COMMITTABLES structure. */
1200 create_committables(committables, result_pool);
1202 /* And the LOCK_TOKENS dito. */
1203 *lock_tokens = apr_hash_make(result_pool);
1205 /* If we have a list of changelists, convert that into a hash with
1207 if (changelists && changelists->nelts)
1208 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists,
1211 for (i = 0; i < targets->nelts; ++i)
1213 const char *target_abspath;
1215 svn_pool_clear(iterpool);
1217 /* Add the relative portion to the base abspath. */
1218 target_abspath = svn_dirent_join(base_dir_abspath,
1219 APR_ARRAY_IDX(targets, i, const char *),
1222 /* Handle our TARGET. */
1223 /* Make sure this isn't inside a working copy subtree that is
1224 * marked as tree-conflicted. */
1225 SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath,
1230 /* Are the remaining items externals with depth empty? */
1231 if (i == depth_empty_start)
1232 depth = svn_depth_empty;
1234 SVN_ERR(harvest_committables(target_abspath,
1235 *committables, *lock_tokens,
1236 NULL /* COPY_MODE_RELPATH */,
1237 depth, just_locked, changelist_hash,
1239 check_url_func, check_url_baton,
1240 ctx->cancel_func, ctx->cancel_baton,
1241 ctx->notify_func2, ctx->notify_baton2,
1242 ctx->wc_ctx, result_pool, iterpool));
1245 hdb.wc_ctx = ctx->wc_ctx;
1246 hdb.cancel_func = ctx->cancel_func;
1247 hdb.cancel_baton = ctx->cancel_baton;
1248 hdb.check_url_func = check_url_func;
1249 hdb.check_url_baton = check_url_baton;
1250 hdb.committables = *committables;
1252 SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository,
1253 handle_descendants, &hdb, iterpool));
1255 /* Make sure that every path in danglers is part of the commit. */
1256 for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi))
1258 const char *dangling_parent = apr_hash_this_key(hi);
1260 svn_pool_clear(iterpool);
1262 if (! look_up_committable(*committables, dangling_parent, iterpool))
1264 const char *dangling_child = apr_hash_this_val(hi);
1266 if (ctx->notify_func2 != NULL)
1268 svn_wc_notify_t *notify;
1270 notify = svn_wc_create_notify(dangling_child,
1271 svn_wc_notify_failed_no_parent,
1274 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1277 return svn_error_createf(
1278 SVN_ERR_ILLEGAL_TARGET, NULL,
1279 _("'%s' is not known to exist in the repository "
1280 "and is not part of the commit, "
1281 "yet its child '%s' is part of the commit"),
1282 /* Probably one or both of these is an entry, but
1283 safest to local_stylize just in case. */
1284 svn_dirent_local_style(dangling_parent, iterpool),
1285 svn_dirent_local_style(dangling_child, iterpool));
1289 svn_pool_destroy(iterpool);
1291 return SVN_NO_ERROR;
1294 struct copy_committables_baton
1296 svn_client_ctx_t *ctx;
1297 svn_client__committables_t *committables;
1298 apr_pool_t *result_pool;
1299 svn_client__check_url_kind_t check_url_func;
1300 void *check_url_baton;
1303 static svn_error_t *
1304 harvest_copy_committables(void *baton, void *item, apr_pool_t *pool)
1306 struct copy_committables_baton *btn = baton;
1307 svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item;
1308 const char *repos_root_url;
1309 const char *commit_relpath;
1310 struct handle_descendants_baton hdb;
1312 /* Read the entry for this SRC. */
1313 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
1315 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL,
1317 pair->src_abspath_or_url,
1320 commit_relpath = svn_uri_skip_ancestor(repos_root_url,
1321 pair->dst_abspath_or_url, pool);
1323 /* Handle this SRC. */
1324 SVN_ERR(harvest_committables(pair->src_abspath_or_url,
1325 btn->committables, NULL,
1328 FALSE, /* JUST_LOCKED */
1329 NULL /* changelists */,
1331 btn->check_url_func,
1332 btn->check_url_baton,
1333 btn->ctx->cancel_func,
1334 btn->ctx->cancel_baton,
1335 btn->ctx->notify_func2,
1336 btn->ctx->notify_baton2,
1337 btn->ctx->wc_ctx, btn->result_pool, pool));
1339 hdb.wc_ctx = btn->ctx->wc_ctx;
1340 hdb.cancel_func = btn->ctx->cancel_func;
1341 hdb.cancel_baton = btn->ctx->cancel_baton;
1342 hdb.check_url_func = btn->check_url_func;
1343 hdb.check_url_baton = btn->check_url_baton;
1344 hdb.committables = btn->committables;
1346 SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository,
1347 handle_descendants, &hdb, pool));
1349 return SVN_NO_ERROR;
1355 svn_client__get_copy_committables(svn_client__committables_t **committables,
1356 const apr_array_header_t *copy_pairs,
1357 svn_client__check_url_kind_t check_url_func,
1358 void *check_url_baton,
1359 svn_client_ctx_t *ctx,
1360 apr_pool_t *result_pool,
1361 apr_pool_t *scratch_pool)
1363 struct copy_committables_baton btn;
1365 /* Create the COMMITTABLES structure. */
1366 create_committables(committables, result_pool);
1369 btn.committables = *committables;
1370 btn.result_pool = result_pool;
1372 btn.check_url_func = check_url_func;
1373 btn.check_url_baton = check_url_baton;
1375 /* For each copy pair, harvest the committables for that pair into the
1376 committables hash. */
1377 return svn_iter_apr_array(NULL, copy_pairs,
1378 harvest_copy_committables, &btn, scratch_pool);
1382 /* A svn_sort__array()/qsort()-compatible sort routine for sorting
1383 an array of svn_client_commit_item_t *'s by their URL member. */
1385 sort_commit_item_urls(const void *a, const void *b)
1387 const svn_client_commit_item3_t *item1
1388 = *((const svn_client_commit_item3_t * const *) a);
1389 const svn_client_commit_item3_t *item2
1390 = *((const svn_client_commit_item3_t * const *) b);
1391 return svn_path_compare_paths(item1->url, item2->url);
1396 svn_client__condense_commit_items2(const char *base_url,
1397 apr_array_header_t *commit_items,
1400 apr_array_header_t *ci = commit_items; /* convenience */
1403 /* Sort our commit items by their URLs. */
1404 svn_sort__array(ci, sort_commit_item_urls);
1406 /* Hack BASE_URL off each URL; store the result as session_relpath. */
1407 for (i = 0; i < ci->nelts; i++)
1409 svn_client_commit_item3_t *this_item
1410 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1412 this_item->session_relpath = svn_uri_skip_ancestor(base_url,
1413 this_item->url, pool);
1416 return SVN_NO_ERROR;
1420 svn_client__condense_commit_items(const char **base_url,
1421 apr_array_header_t *commit_items,
1424 apr_array_header_t *ci = commit_items; /* convenience */
1426 svn_client_commit_item3_t *item, *last_item = NULL;
1429 SVN_ERR_ASSERT(ci && ci->nelts);
1431 /* Sort our commit items by their URLs. */
1432 svn_sort__array(ci, sort_commit_item_urls);
1434 /* Loop through the URLs, finding the longest usable ancestor common
1435 to all of them, and making sure there are no duplicate URLs. */
1436 for (i = 0; i < ci->nelts; i++)
1438 item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1441 if ((last_item) && (strcmp(last_item->url, url) == 0))
1442 return svn_error_createf
1443 (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
1444 _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
1445 svn_dirent_local_style(item->path, pool),
1446 svn_dirent_local_style(last_item->path, pool));
1448 /* In the first iteration, our BASE_URL is just our only
1449 encountered commit URL to date. After that, we find the
1450 longest ancestor between the current BASE_URL and the current
1453 *base_url = apr_pstrdup(pool, url);
1455 *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
1457 /* If our BASE_URL is itself a to-be-committed item, and it is
1458 anything other than an already-versioned directory with
1459 property mods, we'll call its parent directory URL the
1460 BASE_URL. Why? Because we can't have a file URL as our base
1461 -- period -- and all other directory operations (removal,
1462 addition, etc.) require that we open that directory's parent
1464 /* ### I don't understand the strlen()s here, hmmm. -kff */
1465 if ((strlen(*base_url) == strlen(url))
1466 && (! ((item->kind == svn_node_dir)
1467 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1468 *base_url = svn_uri_dirname(*base_url, pool);
1470 /* Stash our item here for the next iteration. */
1474 /* Now that we've settled on a *BASE_URL, go hack that base off
1475 of all of our URLs and store it as session_relpath. */
1476 for (i = 0; i < ci->nelts; i++)
1478 svn_client_commit_item3_t *this_item
1479 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1481 this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
1482 this_item->url, pool);
1484 #ifdef SVN_CLIENT_COMMIT_DEBUG
1485 /* ### TEMPORARY CODE ### */
1486 SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
1487 SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n"));
1488 for (i = 0; i < ci->nelts; i++)
1490 svn_client_commit_item3_t *this_item
1491 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1493 flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1495 flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1497 flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1499 flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1501 flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1504 SVN_DBG((" %s %6ld '%s' (%s)\n",
1506 this_item->revision,
1507 this_item->url ? this_item->url : "",
1508 this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
1510 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1512 return SVN_NO_ERROR;
1518 const svn_client_commit_item3_t *item;
1520 apr_pool_t *file_pool;
1524 /* A baton for use while driving a path-based editor driver for commit */
1525 struct item_commit_baton
1527 apr_hash_t *file_mods; /* hash: path->file_mod_t */
1528 const char *notify_path_prefix; /* notification path prefix
1529 (NULL is okay, else abs path) */
1530 svn_client_ctx_t *ctx; /* client context baton */
1531 apr_hash_t *commit_items; /* the committables */
1532 const char *base_url; /* The session url for the commit */
1536 /* Drive CALLBACK_BATON->editor with the change described by the item in
1537 * CALLBACK_BATON->commit_items that is keyed by PATH. If the change
1538 * includes a text mod, however, call the editor's file_open() function
1539 * but do not send the text mod to the editor; instead, add a mapping of
1540 * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
1542 * Before driving the editor, call the cancellation and notification
1543 * callbacks in CALLBACK_BATON->ctx, if present.
1545 * This implements svn_delta_path_driver_cb_func_t. */
1546 static svn_error_t *
1547 do_item_commit(void **dir_baton,
1548 const svn_delta_editor_t *editor,
1551 void *callback_baton,
1555 struct item_commit_baton *icb = callback_baton;
1556 const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
1558 svn_node_kind_t kind = item->kind;
1559 void *file_baton = NULL;
1560 apr_pool_t *file_pool = NULL;
1561 apr_hash_t *file_mods = icb->file_mods;
1562 svn_client_ctx_t *ctx = icb->ctx;
1564 const char *local_abspath = NULL;
1566 /* Do some initializations. */
1568 if (item->kind != svn_node_none && item->path)
1570 /* We always get an absolute path, see svn_client_commit_item3_t. */
1571 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
1572 local_abspath = item->path;
1575 /* If this is a file with textual mods, we'll be keeping its baton
1576 around until the end of the commit. So just lump its memory into
1577 a single, big, all-the-file-batons-in-here pool. Otherwise, we
1578 can just use POOL, and trust our caller to clean that mess up. */
1579 if ((kind == svn_node_file)
1580 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1581 file_pool = apr_hash_pool_get(file_mods);
1585 /* Subpools are cheap, but memory isn't */
1586 file_pool = svn_pool_create(file_pool);
1588 /* Call the cancellation function. */
1589 if (ctx->cancel_func)
1590 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1593 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1595 if (! item->copyfrom_url)
1596 return svn_error_createf
1597 (SVN_ERR_BAD_URL, NULL,
1598 _("Commit item '%s' has copy flag but no copyfrom URL"),
1599 svn_dirent_local_style(path, pool));
1600 if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1601 return svn_error_createf
1602 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1603 _("Commit item '%s' has copy flag but an invalid revision"),
1604 svn_dirent_local_style(path, pool));
1607 /* If a feedback table was supplied by the application layer,
1608 describe what we're about to do to this item. */
1609 if (ctx->notify_func2 && item->path)
1611 const char *npath = item->path;
1612 svn_wc_notify_t *notify;
1614 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1615 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1617 /* We don't print the "(bin)" notice for binary files when
1618 replacing, only when adding. So we don't bother to get
1619 the mime-type here. */
1620 if (item->copyfrom_url)
1621 notify = svn_wc_create_notify(npath,
1622 svn_wc_notify_commit_copied_replaced,
1625 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1629 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1631 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1634 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1636 if (item->copyfrom_url)
1637 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
1640 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1643 if (item->kind == svn_node_file)
1645 const svn_string_t *propval;
1647 SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
1648 SVN_PROP_MIME_TYPE, pool, pool));
1651 notify->mime_type = propval->data;
1654 else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1655 || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1657 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1659 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1660 notify->content_state = svn_wc_notify_state_changed;
1662 notify->content_state = svn_wc_notify_state_unchanged;
1663 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1664 notify->prop_state = svn_wc_notify_state_changed;
1666 notify->prop_state = svn_wc_notify_state_unchanged;
1674 notify->kind = item->kind;
1675 notify->path_prefix = icb->notify_path_prefix;
1676 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1680 /* If this item is supposed to be deleted, do so. */
1681 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1683 SVN_ERR_ASSERT(parent_baton);
1684 err = editor->delete_entry(path, item->revision,
1685 parent_baton, pool);
1691 /* If this item is supposed to be added, do so. */
1692 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1694 if (kind == svn_node_file)
1696 SVN_ERR_ASSERT(parent_baton);
1697 err = editor->add_file(
1698 path, parent_baton, item->copyfrom_url,
1699 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1700 file_pool, &file_baton);
1702 else /* May be svn_node_none when adding parent dirs for a copy. */
1704 SVN_ERR_ASSERT(parent_baton);
1705 err = editor->add_directory(
1706 path, parent_baton, item->copyfrom_url,
1707 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1714 /* Set other prop-changes, if available in the baton */
1715 if (item->outgoing_prop_changes)
1718 apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1720 for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1722 prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1723 if (kind == svn_node_file)
1725 err = editor->change_file_prop(file_baton, prop->name,
1730 err = editor->change_dir_prop(*dir_baton, prop->name,
1740 /* Now handle property mods. */
1741 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1743 if (kind == svn_node_file)
1747 SVN_ERR_ASSERT(parent_baton);
1748 err = editor->open_file(path, parent_baton,
1750 file_pool, &file_baton);
1762 err = editor->open_root(edit_baton, item->revision,
1767 err = editor->open_directory(path, parent_baton,
1777 /* When committing a directory that no longer exists in the
1778 repository, a "not found" error does not occur immediately
1779 upon opening the directory. It appears here during the delta
1781 err = svn_wc_transmit_prop_deltas2(
1782 ctx->wc_ctx, local_abspath, editor,
1783 (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
1788 /* Make any additional client -> repository prop changes. */
1789 if (item->outgoing_prop_changes)
1794 for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1796 prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
1798 if (kind == svn_node_file)
1800 err = editor->change_file_prop(file_baton, prop->name,
1805 err = editor->change_dir_prop(*dir_baton, prop->name,
1815 /* Finally, handle text mods (in that we need to open a file if it
1816 hasn't already been opened, and we need to put the file baton in
1818 if ((kind == svn_node_file)
1819 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1821 struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1825 SVN_ERR_ASSERT(parent_baton);
1826 err = editor->open_file(path, parent_baton,
1828 file_pool, &file_baton);
1834 /* Add this file mod to the FILE_MODS hash. */
1836 mod->file_baton = file_baton;
1837 mod->file_pool = file_pool;
1838 svn_hash_sets(file_mods, item->session_relpath, mod);
1840 else if (file_baton)
1842 /* Close any outstanding file batons that didn't get caught by
1843 the "has local mods" conditional above. */
1844 err = editor->close_file(file_baton, NULL, file_pool);
1845 svn_pool_destroy(file_pool);
1850 return SVN_NO_ERROR;
1853 return svn_error_trace(fixup_commit_error(local_abspath,
1860 svn_client__do_commit(const char *base_url,
1861 const apr_array_header_t *commit_items,
1862 const svn_delta_editor_t *editor,
1864 const char *notify_path_prefix,
1865 apr_hash_t **sha1_checksums,
1866 svn_client_ctx_t *ctx,
1867 apr_pool_t *result_pool,
1868 apr_pool_t *scratch_pool)
1870 apr_hash_t *file_mods = apr_hash_make(scratch_pool);
1871 apr_hash_t *items_hash = apr_hash_make(scratch_pool);
1872 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1873 apr_hash_index_t *hi;
1875 struct item_commit_baton cb_baton;
1876 apr_array_header_t *paths =
1877 apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
1879 /* Ditto for the checksums. */
1881 *sha1_checksums = apr_hash_make(result_pool);
1883 /* Build a hash from our COMMIT_ITEMS array, keyed on the
1884 relative paths (which come from the item URLs). And
1885 keep an array of those decoded paths, too. */
1886 for (i = 0; i < commit_items->nelts; i++)
1888 svn_client_commit_item3_t *item =
1889 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1890 const char *path = item->session_relpath;
1891 svn_hash_sets(items_hash, path, item);
1892 APR_ARRAY_PUSH(paths, const char *) = path;
1895 /* Setup the callback baton. */
1896 cb_baton.file_mods = file_mods;
1897 cb_baton.notify_path_prefix = notify_path_prefix;
1899 cb_baton.commit_items = items_hash;
1900 cb_baton.base_url = base_url;
1902 /* Drive the commit editor! */
1903 SVN_ERR(svn_delta_path_driver3(editor, edit_baton, paths, TRUE,
1904 do_item_commit, &cb_baton, scratch_pool));
1906 /* Transmit outstanding text deltas. */
1907 for (hi = apr_hash_first(scratch_pool, file_mods);
1909 hi = apr_hash_next(hi))
1911 struct file_mod_t *mod = apr_hash_this_val(hi);
1912 const svn_client_commit_item3_t *item = mod->item;
1913 const svn_checksum_t *new_text_base_md5_checksum;
1914 const svn_checksum_t *new_text_base_sha1_checksum;
1915 svn_boolean_t fulltext = FALSE;
1918 svn_pool_clear(iterpool);
1920 /* Transmit the entry. */
1921 if (ctx->cancel_func)
1922 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1924 if (ctx->notify_func2)
1926 svn_wc_notify_t *notify;
1927 notify = svn_wc_create_notify(item->path,
1928 svn_wc_notify_commit_postfix_txdelta,
1930 notify->kind = svn_node_file;
1931 notify->path_prefix = notify_path_prefix;
1932 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1935 /* If the node has no history, transmit full text */
1936 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1937 && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
1940 err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
1941 &new_text_base_sha1_checksum,
1942 ctx->wc_ctx, item->path,
1943 fulltext, editor, mod->file_baton,
1944 result_pool, iterpool);
1948 svn_pool_destroy(iterpool); /* Close tempfiles */
1949 return svn_error_trace(fixup_commit_error(item->path,
1951 item->session_relpath,
1953 err, ctx, scratch_pool));
1957 svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
1959 svn_pool_destroy(mod->file_pool);
1962 if (ctx->notify_func2)
1964 svn_wc_notify_t *notify;
1965 notify = svn_wc_create_notify_url(base_url,
1966 svn_wc_notify_commit_finalizing,
1968 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1971 svn_pool_destroy(iterpool);
1973 /* Close the edit. */
1974 return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
1979 svn_client__get_log_msg(const char **log_msg,
1980 const char **tmp_file,
1981 const apr_array_header_t *commit_items,
1982 svn_client_ctx_t *ctx,
1985 if (ctx->log_msg_func3)
1987 /* The client provided a callback function for the current API.
1988 Forward the call to it directly. */
1989 return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1990 ctx->log_msg_baton3, pool);
1992 else if (ctx->log_msg_func2 || ctx->log_msg_func)
1994 /* The client provided a pre-1.5 (or pre-1.3) API callback
1995 function. Convert the commit_items list to the appropriate
1996 type, and forward call to it. */
1998 apr_pool_t *scratch_pool = svn_pool_create(pool);
1999 apr_array_header_t *old_commit_items =
2000 apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
2003 for (i = 0; i < commit_items->nelts; i++)
2005 svn_client_commit_item3_t *item =
2006 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
2008 if (ctx->log_msg_func2)
2010 svn_client_commit_item2_t *old_item =
2011 apr_pcalloc(scratch_pool, sizeof(*old_item));
2013 old_item->path = item->path;
2014 old_item->kind = item->kind;
2015 old_item->url = item->url;
2016 old_item->revision = item->revision;
2017 old_item->copyfrom_url = item->copyfrom_url;
2018 old_item->copyfrom_rev = item->copyfrom_rev;
2019 old_item->state_flags = item->state_flags;
2020 old_item->wcprop_changes = item->incoming_prop_changes;
2022 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
2025 else /* ctx->log_msg_func */
2027 svn_client_commit_item_t *old_item =
2028 apr_pcalloc(scratch_pool, sizeof(*old_item));
2030 old_item->path = item->path;
2031 old_item->kind = item->kind;
2032 old_item->url = item->url;
2033 /* The pre-1.3 API used the revision field for copyfrom_rev
2034 and revision depeding of copyfrom_url. */
2035 old_item->revision = item->copyfrom_url ?
2036 item->copyfrom_rev : item->revision;
2037 old_item->copyfrom_url = item->copyfrom_url;
2038 old_item->state_flags = item->state_flags;
2039 old_item->wcprop_changes = item->incoming_prop_changes;
2041 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
2046 if (ctx->log_msg_func2)
2047 err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
2048 ctx->log_msg_baton2, pool);
2050 err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
2051 ctx->log_msg_baton, pool);
2052 svn_pool_destroy(scratch_pool);
2057 /* No log message callback was provided by the client. */
2060 return SVN_NO_ERROR;
2065 svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
2066 const apr_hash_t *revprop_table_in,
2067 const char *log_msg,
2068 svn_client_ctx_t *ctx,
2071 apr_hash_t *new_revprop_table;
2072 if (revprop_table_in)
2074 if (svn_prop_has_svn_prop(revprop_table_in, pool))
2075 return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2076 _("Standard properties can't be set "
2077 "explicitly as revision properties"));
2078 new_revprop_table = apr_hash_copy(pool, revprop_table_in);
2082 new_revprop_table = apr_hash_make(pool);
2084 svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
2085 svn_string_create(log_msg, pool));
2086 *revprop_table_out = new_revprop_table;
2087 return SVN_NO_ERROR;