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);
1397 svn_client__condense_commit_items(const char **base_url,
1398 apr_array_header_t *commit_items,
1401 apr_array_header_t *ci = commit_items; /* convenience */
1403 svn_client_commit_item3_t *item, *last_item = NULL;
1406 SVN_ERR_ASSERT(ci && ci->nelts);
1408 /* Sort our commit items by their URLs. */
1409 svn_sort__array(ci, sort_commit_item_urls);
1411 /* Loop through the URLs, finding the longest usable ancestor common
1412 to all of them, and making sure there are no duplicate URLs. */
1413 for (i = 0; i < ci->nelts; i++)
1415 item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1418 if ((last_item) && (strcmp(last_item->url, url) == 0))
1419 return svn_error_createf
1420 (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
1421 _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
1422 svn_dirent_local_style(item->path, pool),
1423 svn_dirent_local_style(last_item->path, pool));
1425 /* In the first iteration, our BASE_URL is just our only
1426 encountered commit URL to date. After that, we find the
1427 longest ancestor between the current BASE_URL and the current
1430 *base_url = apr_pstrdup(pool, url);
1432 *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
1434 /* If our BASE_URL is itself a to-be-committed item, and it is
1435 anything other than an already-versioned directory with
1436 property mods, we'll call its parent directory URL the
1437 BASE_URL. Why? Because we can't have a file URL as our base
1438 -- period -- and all other directory operations (removal,
1439 addition, etc.) require that we open that directory's parent
1441 /* ### I don't understand the strlen()s here, hmmm. -kff */
1442 if ((strlen(*base_url) == strlen(url))
1443 && (! ((item->kind == svn_node_dir)
1444 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1445 *base_url = svn_uri_dirname(*base_url, pool);
1447 /* Stash our item here for the next iteration. */
1451 /* Now that we've settled on a *BASE_URL, go hack that base off
1452 of all of our URLs and store it as session_relpath. */
1453 for (i = 0; i < ci->nelts; i++)
1455 svn_client_commit_item3_t *this_item
1456 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1458 this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
1459 this_item->url, pool);
1461 #ifdef SVN_CLIENT_COMMIT_DEBUG
1462 /* ### TEMPORARY CODE ### */
1463 SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
1464 SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n"));
1465 for (i = 0; i < ci->nelts; i++)
1467 svn_client_commit_item3_t *this_item
1468 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1470 flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1472 flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1474 flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1476 flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1478 flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1481 SVN_DBG((" %s %6ld '%s' (%s)\n",
1483 this_item->revision,
1484 this_item->url ? this_item->url : "",
1485 this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
1487 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1489 return SVN_NO_ERROR;
1495 const svn_client_commit_item3_t *item;
1497 apr_pool_t *file_pool;
1501 /* A baton for use while driving a path-based editor driver for commit */
1502 struct item_commit_baton
1504 const svn_delta_editor_t *editor; /* commit editor */
1505 void *edit_baton; /* commit editor's baton */
1506 apr_hash_t *file_mods; /* hash: path->file_mod_t */
1507 const char *notify_path_prefix; /* notification path prefix
1508 (NULL is okay, else abs path) */
1509 svn_client_ctx_t *ctx; /* client context baton */
1510 apr_hash_t *commit_items; /* the committables */
1511 const char *base_url; /* The session url for the commit */
1515 /* Drive CALLBACK_BATON->editor with the change described by the item in
1516 * CALLBACK_BATON->commit_items that is keyed by PATH. If the change
1517 * includes a text mod, however, call the editor's file_open() function
1518 * but do not send the text mod to the editor; instead, add a mapping of
1519 * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
1521 * Before driving the editor, call the cancellation and notification
1522 * callbacks in CALLBACK_BATON->ctx, if present.
1524 * This implements svn_delta_path_driver_cb_func_t. */
1525 static svn_error_t *
1526 do_item_commit(void **dir_baton,
1528 void *callback_baton,
1532 struct item_commit_baton *icb = callback_baton;
1533 const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
1535 svn_node_kind_t kind = item->kind;
1536 void *file_baton = NULL;
1537 apr_pool_t *file_pool = NULL;
1538 const svn_delta_editor_t *editor = icb->editor;
1539 apr_hash_t *file_mods = icb->file_mods;
1540 svn_client_ctx_t *ctx = icb->ctx;
1542 const char *local_abspath = NULL;
1544 /* Do some initializations. */
1546 if (item->kind != svn_node_none && item->path)
1548 /* We always get an absolute path, see svn_client_commit_item3_t. */
1549 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
1550 local_abspath = item->path;
1553 /* If this is a file with textual mods, we'll be keeping its baton
1554 around until the end of the commit. So just lump its memory into
1555 a single, big, all-the-file-batons-in-here pool. Otherwise, we
1556 can just use POOL, and trust our caller to clean that mess up. */
1557 if ((kind == svn_node_file)
1558 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1559 file_pool = apr_hash_pool_get(file_mods);
1563 /* Subpools are cheap, but memory isn't */
1564 file_pool = svn_pool_create(file_pool);
1566 /* Call the cancellation function. */
1567 if (ctx->cancel_func)
1568 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1571 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1573 if (! item->copyfrom_url)
1574 return svn_error_createf
1575 (SVN_ERR_BAD_URL, NULL,
1576 _("Commit item '%s' has copy flag but no copyfrom URL"),
1577 svn_dirent_local_style(path, pool));
1578 if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1579 return svn_error_createf
1580 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1581 _("Commit item '%s' has copy flag but an invalid revision"),
1582 svn_dirent_local_style(path, pool));
1585 /* If a feedback table was supplied by the application layer,
1586 describe what we're about to do to this item. */
1587 if (ctx->notify_func2 && item->path)
1589 const char *npath = item->path;
1590 svn_wc_notify_t *notify;
1592 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1593 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1595 /* We don't print the "(bin)" notice for binary files when
1596 replacing, only when adding. So we don't bother to get
1597 the mime-type here. */
1598 if (item->copyfrom_url)
1599 notify = svn_wc_create_notify(npath,
1600 svn_wc_notify_commit_copied_replaced,
1603 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1607 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1609 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1612 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1614 if (item->copyfrom_url)
1615 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
1618 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1621 if (item->kind == svn_node_file)
1623 const svn_string_t *propval;
1625 SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
1626 SVN_PROP_MIME_TYPE, pool, pool));
1629 notify->mime_type = propval->data;
1632 else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1633 || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1635 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1637 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1638 notify->content_state = svn_wc_notify_state_changed;
1640 notify->content_state = svn_wc_notify_state_unchanged;
1641 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1642 notify->prop_state = svn_wc_notify_state_changed;
1644 notify->prop_state = svn_wc_notify_state_unchanged;
1652 notify->kind = item->kind;
1653 notify->path_prefix = icb->notify_path_prefix;
1654 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1658 /* If this item is supposed to be deleted, do so. */
1659 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1661 SVN_ERR_ASSERT(parent_baton);
1662 err = editor->delete_entry(path, item->revision,
1663 parent_baton, pool);
1669 /* If this item is supposed to be added, do so. */
1670 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1672 if (kind == svn_node_file)
1674 SVN_ERR_ASSERT(parent_baton);
1675 err = editor->add_file(
1676 path, parent_baton, item->copyfrom_url,
1677 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1678 file_pool, &file_baton);
1680 else /* May be svn_node_none when adding parent dirs for a copy. */
1682 SVN_ERR_ASSERT(parent_baton);
1683 err = editor->add_directory(
1684 path, parent_baton, item->copyfrom_url,
1685 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1692 /* Set other prop-changes, if available in the baton */
1693 if (item->outgoing_prop_changes)
1696 apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1698 for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1700 prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1701 if (kind == svn_node_file)
1703 err = editor->change_file_prop(file_baton, prop->name,
1708 err = editor->change_dir_prop(*dir_baton, prop->name,
1718 /* Now handle property mods. */
1719 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1721 if (kind == svn_node_file)
1725 SVN_ERR_ASSERT(parent_baton);
1726 err = editor->open_file(path, parent_baton,
1728 file_pool, &file_baton);
1740 err = editor->open_root(icb->edit_baton, item->revision,
1745 err = editor->open_directory(path, parent_baton,
1755 /* When committing a directory that no longer exists in the
1756 repository, a "not found" error does not occur immediately
1757 upon opening the directory. It appears here during the delta
1759 err = svn_wc_transmit_prop_deltas2(
1760 ctx->wc_ctx, local_abspath, editor,
1761 (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
1766 /* Make any additional client -> repository prop changes. */
1767 if (item->outgoing_prop_changes)
1772 for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1774 prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
1776 if (kind == svn_node_file)
1778 err = editor->change_file_prop(file_baton, prop->name,
1783 err = editor->change_dir_prop(*dir_baton, prop->name,
1793 /* Finally, handle text mods (in that we need to open a file if it
1794 hasn't already been opened, and we need to put the file baton in
1796 if ((kind == svn_node_file)
1797 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1799 struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1803 SVN_ERR_ASSERT(parent_baton);
1804 err = editor->open_file(path, parent_baton,
1806 file_pool, &file_baton);
1812 /* Add this file mod to the FILE_MODS hash. */
1814 mod->file_baton = file_baton;
1815 mod->file_pool = file_pool;
1816 svn_hash_sets(file_mods, item->session_relpath, mod);
1818 else if (file_baton)
1820 /* Close any outstanding file batons that didn't get caught by
1821 the "has local mods" conditional above. */
1822 err = editor->close_file(file_baton, NULL, file_pool);
1823 svn_pool_destroy(file_pool);
1828 return SVN_NO_ERROR;
1831 return svn_error_trace(fixup_commit_error(local_abspath,
1838 svn_client__do_commit(const char *base_url,
1839 const apr_array_header_t *commit_items,
1840 const svn_delta_editor_t *editor,
1842 const char *notify_path_prefix,
1843 apr_hash_t **sha1_checksums,
1844 svn_client_ctx_t *ctx,
1845 apr_pool_t *result_pool,
1846 apr_pool_t *scratch_pool)
1848 apr_hash_t *file_mods = apr_hash_make(scratch_pool);
1849 apr_hash_t *items_hash = apr_hash_make(scratch_pool);
1850 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1851 apr_hash_index_t *hi;
1853 struct item_commit_baton cb_baton;
1854 apr_array_header_t *paths =
1855 apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
1857 /* Ditto for the checksums. */
1859 *sha1_checksums = apr_hash_make(result_pool);
1861 /* Build a hash from our COMMIT_ITEMS array, keyed on the
1862 relative paths (which come from the item URLs). And
1863 keep an array of those decoded paths, too. */
1864 for (i = 0; i < commit_items->nelts; i++)
1866 svn_client_commit_item3_t *item =
1867 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1868 const char *path = item->session_relpath;
1869 svn_hash_sets(items_hash, path, item);
1870 APR_ARRAY_PUSH(paths, const char *) = path;
1873 /* Setup the callback baton. */
1874 cb_baton.editor = editor;
1875 cb_baton.edit_baton = edit_baton;
1876 cb_baton.file_mods = file_mods;
1877 cb_baton.notify_path_prefix = notify_path_prefix;
1879 cb_baton.commit_items = items_hash;
1880 cb_baton.base_url = base_url;
1882 /* Drive the commit editor! */
1883 SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1884 do_item_commit, &cb_baton, scratch_pool));
1886 /* Transmit outstanding text deltas. */
1887 for (hi = apr_hash_first(scratch_pool, file_mods);
1889 hi = apr_hash_next(hi))
1891 struct file_mod_t *mod = apr_hash_this_val(hi);
1892 const svn_client_commit_item3_t *item = mod->item;
1893 const svn_checksum_t *new_text_base_md5_checksum;
1894 const svn_checksum_t *new_text_base_sha1_checksum;
1895 svn_boolean_t fulltext = FALSE;
1898 svn_pool_clear(iterpool);
1900 /* Transmit the entry. */
1901 if (ctx->cancel_func)
1902 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1904 if (ctx->notify_func2)
1906 svn_wc_notify_t *notify;
1907 notify = svn_wc_create_notify(item->path,
1908 svn_wc_notify_commit_postfix_txdelta,
1910 notify->kind = svn_node_file;
1911 notify->path_prefix = notify_path_prefix;
1912 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1915 /* If the node has no history, transmit full text */
1916 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1917 && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
1920 err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
1921 &new_text_base_sha1_checksum,
1922 ctx->wc_ctx, item->path,
1923 fulltext, editor, mod->file_baton,
1924 result_pool, iterpool);
1928 svn_pool_destroy(iterpool); /* Close tempfiles */
1929 return svn_error_trace(fixup_commit_error(item->path,
1931 item->session_relpath,
1933 err, ctx, scratch_pool));
1937 svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
1939 svn_pool_destroy(mod->file_pool);
1942 if (ctx->notify_func2)
1944 svn_wc_notify_t *notify;
1945 notify = svn_wc_create_notify_url(base_url,
1946 svn_wc_notify_commit_finalizing,
1948 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1951 svn_pool_destroy(iterpool);
1953 /* Close the edit. */
1954 return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
1959 svn_client__get_log_msg(const char **log_msg,
1960 const char **tmp_file,
1961 const apr_array_header_t *commit_items,
1962 svn_client_ctx_t *ctx,
1965 if (ctx->log_msg_func3)
1967 /* The client provided a callback function for the current API.
1968 Forward the call to it directly. */
1969 return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1970 ctx->log_msg_baton3, pool);
1972 else if (ctx->log_msg_func2 || ctx->log_msg_func)
1974 /* The client provided a pre-1.5 (or pre-1.3) API callback
1975 function. Convert the commit_items list to the appropriate
1976 type, and forward call to it. */
1978 apr_pool_t *scratch_pool = svn_pool_create(pool);
1979 apr_array_header_t *old_commit_items =
1980 apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
1983 for (i = 0; i < commit_items->nelts; i++)
1985 svn_client_commit_item3_t *item =
1986 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1988 if (ctx->log_msg_func2)
1990 svn_client_commit_item2_t *old_item =
1991 apr_pcalloc(scratch_pool, sizeof(*old_item));
1993 old_item->path = item->path;
1994 old_item->kind = item->kind;
1995 old_item->url = item->url;
1996 old_item->revision = item->revision;
1997 old_item->copyfrom_url = item->copyfrom_url;
1998 old_item->copyfrom_rev = item->copyfrom_rev;
1999 old_item->state_flags = item->state_flags;
2000 old_item->wcprop_changes = item->incoming_prop_changes;
2002 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
2005 else /* ctx->log_msg_func */
2007 svn_client_commit_item_t *old_item =
2008 apr_pcalloc(scratch_pool, sizeof(*old_item));
2010 old_item->path = item->path;
2011 old_item->kind = item->kind;
2012 old_item->url = item->url;
2013 /* The pre-1.3 API used the revision field for copyfrom_rev
2014 and revision depeding of copyfrom_url. */
2015 old_item->revision = item->copyfrom_url ?
2016 item->copyfrom_rev : item->revision;
2017 old_item->copyfrom_url = item->copyfrom_url;
2018 old_item->state_flags = item->state_flags;
2019 old_item->wcprop_changes = item->incoming_prop_changes;
2021 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
2026 if (ctx->log_msg_func2)
2027 err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
2028 ctx->log_msg_baton2, pool);
2030 err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
2031 ctx->log_msg_baton, pool);
2032 svn_pool_destroy(scratch_pool);
2037 /* No log message callback was provided by the client. */
2040 return SVN_NO_ERROR;
2045 svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
2046 const apr_hash_t *revprop_table_in,
2047 const char *log_msg,
2048 svn_client_ctx_t *ctx,
2051 apr_hash_t *new_revprop_table;
2052 if (revprop_table_in)
2054 if (svn_prop_has_svn_prop(revprop_table_in, pool))
2055 return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2056 _("Standard properties can't be set "
2057 "explicitly as revision properties"));
2058 new_revprop_table = apr_hash_copy(pool, revprop_table_in);
2062 new_revprop_table = apr_hash_make(pool);
2064 svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
2065 svn_string_create(log_msg, pool));
2066 *revprop_table_out = new_revprop_table;
2067 return SVN_NO_ERROR;