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"
43 #include <stdlib.h> /* for qsort() */
45 #include "svn_private_config.h"
46 #include "private/svn_wc_private.h"
47 #include "private/svn_client_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_ALREADY_EXISTS
66 || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE
67 || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND
68 || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS
69 || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE))
71 if (ctx->notify_func2)
73 svn_wc_notify_t *notify;
76 notify = svn_wc_create_notify(local_abspath,
77 svn_wc_notify_failed_out_of_date,
80 notify = svn_wc_create_notify_url(
81 svn_path_url_add_component2(base_url, path,
83 svn_wc_notify_failed_out_of_date,
89 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
92 return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err,
94 ? _("Directory '%s' is out of date")
95 : _("File '%s' is out of date")),
97 ? svn_dirent_local_style(local_abspath,
99 : svn_path_url_add_component2(base_url,
103 else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN)
104 || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH
105 || err->apr_err == SVN_ERR_RA_NOT_LOCKED)
107 if (ctx->notify_func2)
109 svn_wc_notify_t *notify;
112 notify = svn_wc_create_notify(local_abspath,
113 svn_wc_notify_failed_locked,
116 notify = svn_wc_create_notify_url(
117 svn_path_url_add_component2(base_url, path,
119 svn_wc_notify_failed_locked,
125 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
128 return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err,
129 (kind == svn_node_dir
130 ? _("Directory '%s' is locked in another working copy")
131 : _("File '%s' is locked in another working copy")),
133 ? svn_dirent_local_style(local_abspath,
135 : svn_path_url_add_component2(base_url,
139 else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)
140 || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE)
142 if (ctx->notify_func2)
144 svn_wc_notify_t *notify;
147 notify = svn_wc_create_notify(
149 svn_wc_notify_failed_forbidden_by_server,
152 notify = svn_wc_create_notify_url(
153 svn_path_url_add_component2(base_url, path,
155 svn_wc_notify_failed_forbidden_by_server,
161 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
164 return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err,
165 (kind == svn_node_dir
166 ? _("Changing directory '%s' is forbidden by the server")
167 : _("Changing file '%s' is forbidden by the server")),
169 ? svn_dirent_local_style(local_abspath,
171 : svn_path_url_add_component2(base_url,
180 /*** Harvesting Commit Candidates ***/
183 /* Add a new commit candidate (described by all parameters except
184 `COMMITTABLES') to the COMMITTABLES hash. All of the commit item's
185 members are allocated out of RESULT_POOL.
187 If the state flag specifies that a lock must be used, store the token in LOCK
191 add_committable(svn_client__committables_t *committables,
192 const char *local_abspath,
193 svn_node_kind_t kind,
194 const char *repos_root_url,
195 const char *repos_relpath,
196 svn_revnum_t revision,
197 const char *copyfrom_relpath,
198 svn_revnum_t copyfrom_rev,
199 const char *moved_from_abspath,
200 apr_byte_t state_flags,
201 apr_hash_t *lock_tokens,
202 const svn_lock_t *lock,
203 apr_pool_t *result_pool,
204 apr_pool_t *scratch_pool)
206 apr_array_header_t *array;
207 svn_client_commit_item3_t *new_item;
210 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
211 SVN_ERR_ASSERT(repos_root_url && repos_relpath);
213 /* ### todo: Get the canonical repository for this item, which will
214 be the real key for the COMMITTABLES hash, instead of the above
216 array = svn_hash_gets(committables->by_repository, repos_root_url);
218 /* E-gads! There is no array for this repository yet! Oh, no
219 problem, we'll just create (and add to the hash) one. */
222 array = apr_array_make(result_pool, 1, sizeof(new_item));
223 svn_hash_sets(committables->by_repository,
224 apr_pstrdup(result_pool, repos_root_url), array);
227 /* Now update pointer values, ensuring that their allocations live
229 new_item = svn_client_commit_item3_create(result_pool);
230 new_item->path = apr_pstrdup(result_pool, local_abspath);
231 new_item->kind = kind;
232 new_item->url = svn_path_url_add_component2(repos_root_url,
235 new_item->revision = revision;
236 new_item->copyfrom_url = copyfrom_relpath
237 ? svn_path_url_add_component2(repos_root_url,
241 new_item->copyfrom_rev = copyfrom_rev;
242 new_item->state_flags = state_flags;
243 new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
244 sizeof(svn_prop_t *));
246 if (moved_from_abspath)
247 new_item->moved_from_abspath = apr_pstrdup(result_pool,
250 /* Now, add the commit item to the array. */
251 APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item;
253 /* ... and to the hash. */
254 svn_hash_sets(committables->by_path, new_item->path, new_item);
258 && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN))
260 svn_hash_sets(lock_tokens, new_item->url,
261 apr_pstrdup(result_pool, lock->token));
267 /* If there is a commit item for PATH in COMMITTABLES, return it, else
268 return NULL. Use POOL for temporary allocation only. */
269 static svn_client_commit_item3_t *
270 look_up_committable(svn_client__committables_t *committables,
274 return (svn_client_commit_item3_t *)
275 svn_hash_gets(committables->by_path, path);
278 /* Helper function for svn_client__harvest_committables().
279 * Determine whether we are within a tree-conflicted subtree of the
280 * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */
282 bail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx,
283 const char *local_abspath,
284 svn_wc_notify_func2_t notify_func,
286 apr_pool_t *scratch_pool)
288 const char *wcroot_abspath;
290 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath,
291 scratch_pool, scratch_pool));
293 local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
295 while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath))
297 svn_boolean_t tree_conflicted;
299 /* Check if the parent has tree conflicts */
300 SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
301 wc_ctx, local_abspath, scratch_pool));
304 if (notify_func != NULL)
306 notify_func(notify_baton,
307 svn_wc_create_notify(local_abspath,
308 svn_wc_notify_failed_conflict,
313 return svn_error_createf(
314 SVN_ERR_WC_FOUND_CONFLICT, NULL,
315 _("Aborting commit: '%s' remains in tree-conflict"),
316 svn_dirent_local_style(local_abspath, scratch_pool));
320 if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
323 local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
330 /* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using
331 WC_CTX and add those candidates to COMMITTABLES. If in ADDS_ONLY modes,
332 only new additions are recognized.
334 DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH
335 when LOCAL_ABSPATH is itself a directory; see
336 svn_client__harvest_committables() for its behavior.
338 Lock tokens of candidates will be added to LOCK_TOKENS, if
339 non-NULL. JUST_LOCKED indicates whether to treat non-modified items with
340 lock tokens as commit candidates.
342 If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to
343 be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as
344 items to delete in the copy destination. COPY_MODE_ROOT should be set TRUE
345 for the first call for which COPY_MODE is TRUE, i.e. not for the
346 recursive calls, and FALSE otherwise.
348 If CHANGELISTS is non-NULL, it is a hash whose keys are const char *
349 changelist names used as a restrictive filter
350 when harvesting committables; that is, don't add a path to
351 COMMITTABLES unless it's a member of one of those changelists.
353 IS_EXPLICIT_TARGET should always be passed as TRUE, except when
354 harvest_committables() calls itself in recursion. This provides a way to
355 tell whether LOCAL_ABSPATH was an original target or whether it was reached
356 by recursing deeper into a dir target. (This is used to skip all file
357 externals that aren't explicit commit targets.)
359 DANGLERS is a hash table mapping const char* absolute paths of a parent
360 to a const char * absolute path of a child. See the comment about
361 danglers at the top of svn_client__harvest_committables().
363 If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see
364 if the user has cancelled the operation.
366 Any items added to COMMITTABLES are allocated from the COMITTABLES
367 hash pool, not POOL. SCRATCH_POOL is used for temporary allocations. */
372 const char *root_abspath;
373 svn_client__committables_t *committables;
374 apr_hash_t *lock_tokens;
375 const char *commit_relpath; /* Valid for the harvest root */
377 svn_boolean_t just_locked;
378 apr_hash_t *changelists;
379 apr_hash_t *danglers;
380 svn_client__check_url_kind_t check_url_func;
381 void *check_url_baton;
382 svn_wc_notify_func2_t notify_func;
384 svn_wc_context_t *wc_ctx;
385 apr_pool_t *result_pool;
387 /* Harvester state */
388 const char *skip_below_abspath; /* If non-NULL, skip everything below */
392 harvest_status_callback(void *status_baton,
393 const char *local_abspath,
394 const svn_wc_status3_t *status,
395 apr_pool_t *scratch_pool);
398 harvest_committables(const char *local_abspath,
399 svn_client__committables_t *committables,
400 apr_hash_t *lock_tokens,
401 const char *copy_mode_relpath,
403 svn_boolean_t just_locked,
404 apr_hash_t *changelists,
405 apr_hash_t *danglers,
406 svn_client__check_url_kind_t check_url_func,
407 void *check_url_baton,
408 svn_cancel_func_t cancel_func,
410 svn_wc_notify_func2_t notify_func,
412 svn_wc_context_t *wc_ctx,
413 apr_pool_t *result_pool,
414 apr_pool_t *scratch_pool)
416 struct harvest_baton baton;
418 SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked);
420 baton.root_abspath = local_abspath;
421 baton.committables = committables;
422 baton.lock_tokens = lock_tokens;
423 baton.commit_relpath = copy_mode_relpath;
425 baton.just_locked = just_locked;
426 baton.changelists = changelists;
427 baton.danglers = danglers;
428 baton.check_url_func = check_url_func;
429 baton.check_url_baton = check_url_baton;
430 baton.notify_func = notify_func;
431 baton.notify_baton = notify_baton;
432 baton.wc_ctx = wc_ctx;
433 baton.result_pool = result_pool;
435 baton.skip_below_abspath = NULL;
437 SVN_ERR(svn_wc_walk_status(wc_ctx,
440 (copy_mode_relpath != NULL) /* get_all */,
441 FALSE /* no_ignore */,
442 FALSE /* ignore_text_mods */,
443 NULL /* ignore_patterns */,
444 harvest_status_callback,
446 cancel_func, cancel_baton,
453 harvest_not_present_for_copy(svn_wc_context_t *wc_ctx,
454 const char *local_abspath,
455 svn_client__committables_t *committables,
456 const char *repos_root_url,
457 const char *commit_relpath,
458 svn_client__check_url_kind_t check_url_func,
459 void *check_url_baton,
460 apr_pool_t *result_pool,
461 apr_pool_t *scratch_pool)
463 const apr_array_header_t *children;
464 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
467 /* A function to retrieve not present children would be nice to have */
468 SVN_ERR(svn_wc__node_get_children_of_working_node(
469 &children, wc_ctx, local_abspath, TRUE,
470 scratch_pool, iterpool));
472 for (i = 0; i < children->nelts; i++)
474 const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
475 const char *name = svn_dirent_basename(this_abspath, NULL);
476 const char *this_commit_relpath;
477 svn_boolean_t not_present;
478 svn_node_kind_t kind;
480 svn_pool_clear(iterpool);
482 SVN_ERR(svn_wc__node_is_not_present(¬_present, NULL, NULL, wc_ctx,
483 this_abspath, FALSE, scratch_pool));
488 if (commit_relpath == NULL)
489 this_commit_relpath = NULL;
491 this_commit_relpath = svn_relpath_join(commit_relpath, name,
494 /* We should check if we should really add a delete operation */
497 svn_revnum_t parent_rev;
498 const char *parent_repos_relpath;
499 const char *parent_repos_root_url;
500 const char *node_url;
502 /* Determine from what parent we would be the deleted child */
503 SVN_ERR(svn_wc__node_get_origin(
504 NULL, &parent_rev, &parent_repos_relpath,
505 &parent_repos_root_url, NULL, NULL,
507 svn_dirent_dirname(this_abspath,
509 FALSE, scratch_pool, scratch_pool));
511 node_url = svn_path_url_add_component2(
512 svn_path_url_add_component2(parent_repos_root_url,
513 parent_repos_relpath,
515 svn_dirent_basename(this_abspath, NULL),
518 SVN_ERR(check_url_func(check_url_baton, &kind,
519 node_url, parent_rev, iterpool));
521 if (kind == svn_node_none)
522 continue; /* This node can't be deleted */
525 SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath,
526 TRUE, TRUE, scratch_pool));
528 SVN_ERR(add_committable(committables, this_abspath, kind,
532 NULL /* copyfrom_relpath */,
533 SVN_INVALID_REVNUM /* copyfrom_rev */,
534 NULL /* moved_from_abspath */,
535 SVN_CLIENT_COMMIT_ITEM_DELETE,
537 result_pool, scratch_pool));
540 svn_pool_destroy(iterpool);
544 /* Implements svn_wc_status_func4_t */
546 harvest_status_callback(void *status_baton,
547 const char *local_abspath,
548 const svn_wc_status3_t *status,
549 apr_pool_t *scratch_pool)
551 apr_byte_t state_flags = 0;
552 svn_revnum_t node_rev;
553 const char *cf_relpath = NULL;
554 svn_revnum_t cf_rev = SVN_INVALID_REVNUM;
555 svn_boolean_t matches_changelists;
556 svn_boolean_t is_added;
557 svn_boolean_t is_deleted;
558 svn_boolean_t is_replaced;
559 svn_boolean_t is_op_root;
560 svn_revnum_t original_rev;
561 const char *original_relpath;
562 svn_boolean_t copy_mode;
564 struct harvest_baton *baton = status_baton;
565 svn_boolean_t is_harvest_root =
566 (strcmp(baton->root_abspath, local_abspath) == 0);
567 svn_client__committables_t *committables = baton->committables;
568 const char *repos_root_url = status->repos_root_url;
569 const char *commit_relpath = NULL;
570 svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root);
571 svn_boolean_t just_locked = baton->just_locked;
572 apr_hash_t *changelists = baton->changelists;
573 svn_wc_notify_func2_t notify_func = baton->notify_func;
574 void *notify_baton = baton->notify_baton;
575 svn_wc_context_t *wc_ctx = baton->wc_ctx;
576 apr_pool_t *result_pool = baton->result_pool;
577 const char *moved_from_abspath = NULL;
579 if (baton->commit_relpath)
580 commit_relpath = svn_relpath_join(
581 baton->commit_relpath,
582 svn_dirent_skip_ancestor(baton->root_abspath,
586 copy_mode = (commit_relpath != NULL);
588 if (baton->skip_below_abspath
589 && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath))
594 baton->skip_below_abspath = NULL; /* We have left the skip tree */
596 /* Return early for nodes that don't have a committable status */
597 switch (status->node_status)
599 case svn_wc_status_unversioned:
600 case svn_wc_status_ignored:
601 case svn_wc_status_external:
602 case svn_wc_status_none:
603 /* Unversioned nodes aren't committable, but are reported by the status
605 But if the unversioned node is the root of the walk, we have a user
608 return svn_error_createf(
609 SVN_ERR_ILLEGAL_TARGET, NULL,
610 _("'%s' is not under version control"),
611 svn_dirent_local_style(local_abspath, scratch_pool));
613 case svn_wc_status_normal:
614 /* Status normal nodes aren't modified, so we don't have to commit them
615 when we perform a normal commit. But if a node is conflicted we want
616 to stop the commit and if we are collecting lock tokens we want to
619 When in copy mode we need to compare the revision of the node against
620 the parent node to copy mixed-revision base nodes properly */
621 if (!copy_mode && !status->conflicted
622 && !(just_locked && status->lock))
630 /* Early out if the item is already marked as committable. */
631 if (look_up_committable(committables, local_abspath, scratch_pool))
634 SVN_ERR_ASSERT((copy_mode && commit_relpath)
635 || (! copy_mode && ! commit_relpath));
636 SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root);
638 /* Save the result for reuse. */
639 matches_changelists = ((changelists == NULL)
640 || (status->changelist != NULL
641 && svn_hash_gets(changelists, status->changelist)
645 if (status->kind != svn_node_dir && ! matches_changelists)
650 /* If NODE is in our changelist, then examine it for conflicts. We
651 need to bail out if any conflicts exist.
652 The status walker checked for conflict marker removal. */
653 if (status->conflicted && matches_changelists)
655 if (notify_func != NULL)
657 notify_func(notify_baton,
658 svn_wc_create_notify(local_abspath,
659 svn_wc_notify_failed_conflict,
664 return svn_error_createf(
665 SVN_ERR_WC_FOUND_CONFLICT, NULL,
666 _("Aborting commit: '%s' remains in conflict"),
667 svn_dirent_local_style(local_abspath, scratch_pool));
669 else if (status->node_status == svn_wc_status_obstructed)
671 /* A node's type has changed before attempting to commit.
672 This also catches symlink vs non symlink changes */
674 if (notify_func != NULL)
676 notify_func(notify_baton,
677 svn_wc_create_notify(local_abspath,
678 svn_wc_notify_failed_obstruction,
683 return svn_error_createf(
684 SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
685 _("Node '%s' has unexpectedly changed kind"),
686 svn_dirent_local_style(local_abspath, scratch_pool));
689 if (status->conflicted && status->kind == svn_node_unknown)
690 return SVN_NO_ERROR; /* Ignore delete-delete conflict */
692 /* Return error on unknown path kinds. We check both the entry and
693 the node itself, since a path might have changed kind since its
694 entry was written. */
695 SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted,
699 &original_rev, &original_relpath,
700 wc_ctx, local_abspath,
701 scratch_pool, scratch_pool));
703 /* Hande file externals only when passed as explicit target. Note that
704 * svn_client_commit6() passes all committable externals in as explicit
705 * targets iff they count. */
706 if (status->file_external && !is_harvest_root)
711 if (status->node_status == svn_wc_status_missing && matches_changelists)
713 /* Added files and directories must exist. See issue #3198. */
714 if (is_added && is_op_root)
716 if (notify_func != NULL)
718 notify_func(notify_baton,
719 svn_wc_create_notify(local_abspath,
720 svn_wc_notify_failed_missing,
724 return svn_error_createf(
725 SVN_ERR_WC_PATH_NOT_FOUND, NULL,
726 _("'%s' is scheduled for addition, but is missing"),
727 svn_dirent_local_style(local_abspath, scratch_pool));
733 if (is_deleted && !is_op_root /* && !is_added */)
734 return SVN_NO_ERROR; /* Not an operational delete and not an add. */
736 /* Check for the deletion case.
737 * We delete explicitly deleted nodes (duh!)
738 * We delete not-present children of copies
739 * We delete nodes that directly replace a node in its ancestor
742 if (is_deleted || is_replaced)
743 state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
745 /* Check for adds and copies */
746 if (is_added && is_op_root)
748 /* Root of local add or copy */
749 state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
751 if (original_relpath)
754 state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
755 cf_relpath = original_relpath;
756 cf_rev = original_rev;
758 if (status->moved_from_abspath && !copy_mode)
760 state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE;
761 moved_from_abspath = status->moved_from_abspath;
766 /* Further copies may occur in copy mode. */
768 && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
770 svn_revnum_t dir_rev = SVN_INVALID_REVNUM;
772 if (!copy_mode_root && !status->switched && !is_added)
773 SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, NULL, NULL, NULL, NULL,
774 wc_ctx, svn_dirent_dirname(local_abspath,
776 FALSE /* ignore_enoent */,
777 FALSE /* show_hidden */,
778 scratch_pool, scratch_pool));
780 if (copy_mode_root || status->switched || node_rev != dir_rev)
782 state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD
783 | SVN_CLIENT_COMMIT_ITEM_IS_COPY);
787 /* Copy from original location */
788 cf_rev = original_rev;
789 cf_relpath = original_relpath;
793 /* Copy BASE location, to represent a mixed-rev or switch copy */
794 cf_rev = status->revision;
795 cf_relpath = status->repos_relpath;
800 if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
801 || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
803 svn_boolean_t text_mod = FALSE;
804 svn_boolean_t prop_mod = FALSE;
806 if (status->kind == svn_node_file)
808 /* Check for text modifications on files */
809 if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
810 && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
812 text_mod = TRUE; /* Local added files are always modified */
815 text_mod = (status->text_status != svn_wc_status_normal);
818 prop_mod = (status->prop_status != svn_wc_status_normal
819 && status->prop_status != svn_wc_status_none);
821 /* Set text/prop modification flags accordingly. */
823 state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
825 state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
828 /* If the entry has a lock token and it is already a commit candidate,
829 or the caller wants unmodified locked items to be treated as
830 such, note this fact. */
831 if (status->lock && baton->lock_tokens && (state_flags || just_locked))
833 state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN;
836 /* Now, if this is something to commit, add it to our list. */
837 if (matches_changelists
840 /* Finally, add the committable item. */
841 SVN_ERR(add_committable(committables, local_abspath,
846 : status->repos_relpath,
854 baton->lock_tokens, status->lock,
855 result_pool, scratch_pool));
858 /* Fetch lock tokens for descendants of deleted BASE nodes. */
859 if (matches_changelists
860 && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
862 && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */
863 && baton->lock_tokens)
865 apr_hash_t *local_relpath_tokens;
866 apr_hash_index_t *hi;
868 SVN_ERR(svn_wc__node_get_lock_tokens_recursive(
869 &local_relpath_tokens, wc_ctx, local_abspath,
870 result_pool, scratch_pool));
872 /* Add tokens to existing hash. */
873 for (hi = apr_hash_first(scratch_pool, local_relpath_tokens);
875 hi = apr_hash_next(hi))
881 apr_hash_this(hi, &key, &klen, &val);
883 apr_hash_set(baton->lock_tokens, key, klen, val);
887 /* Make sure we check for dangling children on additions
889 We perform this operation on the harvest root, and on roots caused by
890 changelist filtering.
892 if (matches_changelists
893 && (is_harvest_root || baton->changelists)
898 /* If a node is added, its parent must exist in the repository at the
899 time of committing */
900 apr_hash_t *danglers = baton->danglers;
901 svn_boolean_t parent_added;
902 const char *parent_abspath = svn_dirent_dirname(local_abspath,
905 /* First check if parent is already in the list of commits
906 (Common case for GUI clients that provide a list of commit targets) */
907 if (look_up_committable(committables, parent_abspath, scratch_pool))
908 parent_added = FALSE; /* Skip all expensive checks */
910 SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath,
915 const char *copy_root_abspath;
916 svn_boolean_t parent_is_copy;
918 /* The parent is added, so either it is a copy, or a locally added
919 * directory. In either case, we require the op-root of the parent
920 * to be part of the commit. See issue #4059. */
921 SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL,
922 NULL, ©_root_abspath,
923 wc_ctx, parent_abspath,
924 FALSE, scratch_pool, scratch_pool));
927 parent_abspath = copy_root_abspath;
929 if (!svn_hash_gets(danglers, parent_abspath))
931 svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath),
932 apr_pstrdup(result_pool, local_abspath));
937 if (is_deleted && !is_added)
939 /* Skip all descendants */
940 if (status->kind == svn_node_dir)
941 baton->skip_below_abspath = apr_pstrdup(baton->result_pool,
946 /* Recursively handle each node according to depth, except when the
947 node is only being deleted, or is in an added tree (as added trees
948 use the normal commit handling). */
949 if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir)
951 SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables,
952 repos_root_url, commit_relpath,
953 baton->check_url_func,
954 baton->check_url_baton,
955 result_pool, scratch_pool));
961 /* Baton for handle_descendants */
962 struct handle_descendants_baton
964 svn_wc_context_t *wc_ctx;
965 svn_cancel_func_t cancel_func;
967 svn_client__check_url_kind_t check_url_func;
968 void *check_url_baton;
971 /* Helper for the commit harvesters */
973 handle_descendants(void *baton,
974 const void *key, apr_ssize_t klen, void *val,
977 struct handle_descendants_baton *hdb = baton;
978 apr_array_header_t *commit_items = val;
979 apr_pool_t *iterpool = svn_pool_create(pool);
982 for (i = 0; i < commit_items->nelts; i++)
984 svn_client_commit_item3_t *item =
985 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
986 const apr_array_header_t *absent_descendants;
989 /* Is this a copy operation? */
990 if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
991 || ! item->copyfrom_url)
994 if (hdb->cancel_func)
995 SVN_ERR(hdb->cancel_func(hdb->cancel_baton));
997 svn_pool_clear(iterpool);
999 SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants,
1000 hdb->wc_ctx, item->path,
1001 iterpool, iterpool));
1003 for (j = 0; j < absent_descendants->nelts; j++)
1006 svn_boolean_t found_item = FALSE;
1007 svn_node_kind_t kind;
1008 const char *relpath = APR_ARRAY_IDX(absent_descendants, j,
1010 const char *local_abspath = svn_dirent_join(item->path, relpath,
1013 /* If the path has a commit operation, we do nothing.
1014 (It will be deleted by the operation) */
1015 for (k = 0; k < commit_items->nelts; k++)
1017 svn_client_commit_item3_t *cmt_item =
1018 APR_ARRAY_IDX(commit_items, k, svn_client_commit_item3_t *);
1020 if (! strcmp(cmt_item->path, local_abspath))
1028 continue; /* We have an explicit delete or replace for this path */
1030 /* ### Need a sub-iterpool? */
1032 if (hdb->check_url_func)
1034 const char *from_url = svn_path_url_add_component2(
1035 item->copyfrom_url, relpath,
1038 SVN_ERR(hdb->check_url_func(hdb->check_url_baton,
1039 &kind, from_url, item->copyfrom_rev,
1042 if (kind == svn_node_none)
1043 continue; /* This node is already deleted */
1046 kind = svn_node_unknown; /* 'Ok' for a delete of something */
1049 /* Add a new commit item that describes the delete */
1050 apr_pool_t *result_pool = commit_items->pool;
1051 svn_client_commit_item3_t *new_item
1052 = svn_client_commit_item3_create(result_pool);
1054 new_item->path = svn_dirent_join(item->path, relpath,
1056 new_item->kind = kind;
1057 new_item->url = svn_path_url_add_component2(item->url, relpath,
1059 new_item->revision = SVN_INVALID_REVNUM;
1060 new_item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1061 new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
1062 sizeof(svn_prop_t *));
1064 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *)
1070 svn_pool_destroy(iterpool);
1071 return SVN_NO_ERROR;
1074 /* Allocate and initialize the COMMITTABLES structure from POOL.
1077 create_committables(svn_client__committables_t **committables,
1080 *committables = apr_palloc(pool, sizeof(**committables));
1082 (*committables)->by_repository = apr_hash_make(pool);
1083 (*committables)->by_path = apr_hash_make(pool);
1087 svn_client__harvest_committables(svn_client__committables_t **committables,
1088 apr_hash_t **lock_tokens,
1089 const char *base_dir_abspath,
1090 const apr_array_header_t *targets,
1091 int depth_empty_start,
1093 svn_boolean_t just_locked,
1094 const apr_array_header_t *changelists,
1095 svn_client__check_url_kind_t check_url_func,
1096 void *check_url_baton,
1097 svn_client_ctx_t *ctx,
1098 apr_pool_t *result_pool,
1099 apr_pool_t *scratch_pool)
1102 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1103 apr_hash_t *changelist_hash = NULL;
1104 struct handle_descendants_baton hdb;
1105 apr_hash_index_t *hi;
1107 /* It's possible that one of the named targets has a parent that is
1108 * itself scheduled for addition or replacement -- that is, the
1109 * parent is not yet versioned in the repository. This is okay, as
1110 * long as the parent itself is part of this same commit, either
1111 * directly, or by virtue of a grandparent, great-grandparent, etc,
1112 * being part of the commit.
1114 * Since we don't know what's included in the commit until we've
1115 * harvested all the targets, we can't reliably check this as we
1116 * go. So in `danglers', we record named targets whose parents
1117 * do not yet exist in the repository. Then after harvesting the total
1118 * commit group, we check to make sure those parents are included.
1120 * Each key of danglers is a parent which does not exist in the
1121 * repository. The (const char *) value is one of that parent's
1122 * children which is named as part of the commit; the child is
1123 * included only to make a better error message.
1125 * (The reason we don't bother to check unnamed -- i.e, implicit --
1126 * targets is that they can only join the commit if their parents
1127 * did too, so this situation can't arise for them.)
1129 apr_hash_t *danglers = apr_hash_make(scratch_pool);
1131 SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath));
1133 /* Create the COMMITTABLES structure. */
1134 create_committables(committables, result_pool);
1136 /* And the LOCK_TOKENS dito. */
1137 *lock_tokens = apr_hash_make(result_pool);
1139 /* If we have a list of changelists, convert that into a hash with
1141 if (changelists && changelists->nelts)
1142 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists,
1145 for (i = 0; i < targets->nelts; ++i)
1147 const char *target_abspath;
1149 svn_pool_clear(iterpool);
1151 /* Add the relative portion to the base abspath. */
1152 target_abspath = svn_dirent_join(base_dir_abspath,
1153 APR_ARRAY_IDX(targets, i, const char *),
1156 /* Handle our TARGET. */
1157 /* Make sure this isn't inside a working copy subtree that is
1158 * marked as tree-conflicted. */
1159 SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath,
1164 /* Are the remaining items externals with depth empty? */
1165 if (i == depth_empty_start)
1166 depth = svn_depth_empty;
1168 SVN_ERR(harvest_committables(target_abspath,
1169 *committables, *lock_tokens,
1170 NULL /* COPY_MODE_RELPATH */,
1171 depth, just_locked, changelist_hash,
1173 check_url_func, check_url_baton,
1174 ctx->cancel_func, ctx->cancel_baton,
1175 ctx->notify_func2, ctx->notify_baton2,
1176 ctx->wc_ctx, result_pool, iterpool));
1179 hdb.wc_ctx = ctx->wc_ctx;
1180 hdb.cancel_func = ctx->cancel_func;
1181 hdb.cancel_baton = ctx->cancel_baton;
1182 hdb.check_url_func = check_url_func;
1183 hdb.check_url_baton = check_url_baton;
1185 SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository,
1186 handle_descendants, &hdb, iterpool));
1188 /* Make sure that every path in danglers is part of the commit. */
1189 for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi))
1191 const char *dangling_parent = svn__apr_hash_index_key(hi);
1193 svn_pool_clear(iterpool);
1195 if (! look_up_committable(*committables, dangling_parent, iterpool))
1197 const char *dangling_child = svn__apr_hash_index_val(hi);
1199 if (ctx->notify_func2 != NULL)
1201 svn_wc_notify_t *notify;
1203 notify = svn_wc_create_notify(dangling_child,
1204 svn_wc_notify_failed_no_parent,
1207 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1210 return svn_error_createf(
1211 SVN_ERR_ILLEGAL_TARGET, NULL,
1212 _("'%s' is not known to exist in the repository "
1213 "and is not part of the commit, "
1214 "yet its child '%s' is part of the commit"),
1215 /* Probably one or both of these is an entry, but
1216 safest to local_stylize just in case. */
1217 svn_dirent_local_style(dangling_parent, iterpool),
1218 svn_dirent_local_style(dangling_child, iterpool));
1222 svn_pool_destroy(iterpool);
1224 return SVN_NO_ERROR;
1227 struct copy_committables_baton
1229 svn_client_ctx_t *ctx;
1230 svn_client__committables_t *committables;
1231 apr_pool_t *result_pool;
1232 svn_client__check_url_kind_t check_url_func;
1233 void *check_url_baton;
1236 static svn_error_t *
1237 harvest_copy_committables(void *baton, void *item, apr_pool_t *pool)
1239 struct copy_committables_baton *btn = baton;
1240 svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item;
1241 const char *repos_root_url;
1242 const char *commit_relpath;
1243 struct handle_descendants_baton hdb;
1245 /* Read the entry for this SRC. */
1246 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
1248 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL,
1250 pair->src_abspath_or_url,
1253 commit_relpath = svn_uri_skip_ancestor(repos_root_url,
1254 pair->dst_abspath_or_url, pool);
1256 /* Handle this SRC. */
1257 SVN_ERR(harvest_committables(pair->src_abspath_or_url,
1258 btn->committables, NULL,
1261 FALSE, /* JUST_LOCKED */
1262 NULL /* changelists */,
1264 btn->check_url_func,
1265 btn->check_url_baton,
1266 btn->ctx->cancel_func,
1267 btn->ctx->cancel_baton,
1268 btn->ctx->notify_func2,
1269 btn->ctx->notify_baton2,
1270 btn->ctx->wc_ctx, btn->result_pool, pool));
1272 hdb.wc_ctx = btn->ctx->wc_ctx;
1273 hdb.cancel_func = btn->ctx->cancel_func;
1274 hdb.cancel_baton = btn->ctx->cancel_baton;
1275 hdb.check_url_func = btn->check_url_func;
1276 hdb.check_url_baton = btn->check_url_baton;
1278 SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository,
1279 handle_descendants, &hdb, pool));
1281 return SVN_NO_ERROR;
1287 svn_client__get_copy_committables(svn_client__committables_t **committables,
1288 const apr_array_header_t *copy_pairs,
1289 svn_client__check_url_kind_t check_url_func,
1290 void *check_url_baton,
1291 svn_client_ctx_t *ctx,
1292 apr_pool_t *result_pool,
1293 apr_pool_t *scratch_pool)
1295 struct copy_committables_baton btn;
1297 /* Create the COMMITTABLES structure. */
1298 create_committables(committables, result_pool);
1301 btn.committables = *committables;
1302 btn.result_pool = result_pool;
1304 btn.check_url_func = check_url_func;
1305 btn.check_url_baton = check_url_baton;
1307 /* For each copy pair, harvest the committables for that pair into the
1308 committables hash. */
1309 return svn_iter_apr_array(NULL, copy_pairs,
1310 harvest_copy_committables, &btn, scratch_pool);
1314 int svn_client__sort_commit_item_urls(const void *a, const void *b)
1316 const svn_client_commit_item3_t *item1
1317 = *((const svn_client_commit_item3_t * const *) a);
1318 const svn_client_commit_item3_t *item2
1319 = *((const svn_client_commit_item3_t * const *) b);
1320 return svn_path_compare_paths(item1->url, item2->url);
1326 svn_client__condense_commit_items(const char **base_url,
1327 apr_array_header_t *commit_items,
1330 apr_array_header_t *ci = commit_items; /* convenience */
1332 svn_client_commit_item3_t *item, *last_item = NULL;
1335 SVN_ERR_ASSERT(ci && ci->nelts);
1337 /* Sort our commit items by their URLs. */
1338 qsort(ci->elts, ci->nelts,
1339 ci->elt_size, svn_client__sort_commit_item_urls);
1341 /* Loop through the URLs, finding the longest usable ancestor common
1342 to all of them, and making sure there are no duplicate URLs. */
1343 for (i = 0; i < ci->nelts; i++)
1345 item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1348 if ((last_item) && (strcmp(last_item->url, url) == 0))
1349 return svn_error_createf
1350 (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
1351 _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
1352 svn_dirent_local_style(item->path, pool),
1353 svn_dirent_local_style(last_item->path, pool));
1355 /* In the first iteration, our BASE_URL is just our only
1356 encountered commit URL to date. After that, we find the
1357 longest ancestor between the current BASE_URL and the current
1360 *base_url = apr_pstrdup(pool, url);
1362 *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
1364 /* If our BASE_URL is itself a to-be-committed item, and it is
1365 anything other than an already-versioned directory with
1366 property mods, we'll call its parent directory URL the
1367 BASE_URL. Why? Because we can't have a file URL as our base
1368 -- period -- and all other directory operations (removal,
1369 addition, etc.) require that we open that directory's parent
1371 /* ### I don't understand the strlen()s here, hmmm. -kff */
1372 if ((strlen(*base_url) == strlen(url))
1373 && (! ((item->kind == svn_node_dir)
1374 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1375 *base_url = svn_uri_dirname(*base_url, pool);
1377 /* Stash our item here for the next iteration. */
1381 /* Now that we've settled on a *BASE_URL, go hack that base off
1382 of all of our URLs and store it as session_relpath. */
1383 for (i = 0; i < ci->nelts; i++)
1385 svn_client_commit_item3_t *this_item
1386 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1388 this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
1389 this_item->url, pool);
1391 #ifdef SVN_CLIENT_COMMIT_DEBUG
1392 /* ### TEMPORARY CODE ### */
1393 SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
1394 SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n"));
1395 for (i = 0; i < ci->nelts; i++)
1397 svn_client_commit_item3_t *this_item
1398 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1400 flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1402 flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1404 flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1406 flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1408 flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1411 SVN_DBG((" %s %6ld '%s' (%s)\n",
1413 this_item->revision,
1414 this_item->url ? this_item->url : "",
1415 this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
1417 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1419 return SVN_NO_ERROR;
1425 const svn_client_commit_item3_t *item;
1430 /* A baton for use while driving a path-based editor driver for commit */
1431 struct item_commit_baton
1433 const svn_delta_editor_t *editor; /* commit editor */
1434 void *edit_baton; /* commit editor's baton */
1435 apr_hash_t *file_mods; /* hash: path->file_mod_t */
1436 const char *notify_path_prefix; /* notification path prefix
1437 (NULL is okay, else abs path) */
1438 svn_client_ctx_t *ctx; /* client context baton */
1439 apr_hash_t *commit_items; /* the committables */
1440 const char *base_url; /* The session url for the commit */
1444 /* Drive CALLBACK_BATON->editor with the change described by the item in
1445 * CALLBACK_BATON->commit_items that is keyed by PATH. If the change
1446 * includes a text mod, however, call the editor's file_open() function
1447 * but do not send the text mod to the editor; instead, add a mapping of
1448 * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
1450 * Before driving the editor, call the cancellation and notification
1451 * callbacks in CALLBACK_BATON->ctx, if present.
1453 * This implements svn_delta_path_driver_cb_func_t. */
1454 static svn_error_t *
1455 do_item_commit(void **dir_baton,
1457 void *callback_baton,
1461 struct item_commit_baton *icb = callback_baton;
1462 const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
1464 svn_node_kind_t kind = item->kind;
1465 void *file_baton = NULL;
1466 apr_pool_t *file_pool = NULL;
1467 const svn_delta_editor_t *editor = icb->editor;
1468 apr_hash_t *file_mods = icb->file_mods;
1469 svn_client_ctx_t *ctx = icb->ctx;
1471 const char *local_abspath = NULL;
1473 /* Do some initializations. */
1475 if (item->kind != svn_node_none && item->path)
1477 /* We always get an absolute path, see svn_client_commit_item3_t. */
1478 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
1479 local_abspath = item->path;
1482 /* If this is a file with textual mods, we'll be keeping its baton
1483 around until the end of the commit. So just lump its memory into
1484 a single, big, all-the-file-batons-in-here pool. Otherwise, we
1485 can just use POOL, and trust our caller to clean that mess up. */
1486 if ((kind == svn_node_file)
1487 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1488 file_pool = apr_hash_pool_get(file_mods);
1492 /* Call the cancellation function. */
1493 if (ctx->cancel_func)
1494 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1497 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1499 if (! item->copyfrom_url)
1500 return svn_error_createf
1501 (SVN_ERR_BAD_URL, NULL,
1502 _("Commit item '%s' has copy flag but no copyfrom URL"),
1503 svn_dirent_local_style(path, pool));
1504 if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1505 return svn_error_createf
1506 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1507 _("Commit item '%s' has copy flag but an invalid revision"),
1508 svn_dirent_local_style(path, pool));
1511 /* If a feedback table was supplied by the application layer,
1512 describe what we're about to do to this item. */
1513 if (ctx->notify_func2 && item->path)
1515 const char *npath = item->path;
1516 svn_wc_notify_t *notify;
1518 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1519 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1521 /* We don't print the "(bin)" notice for binary files when
1522 replacing, only when adding. So we don't bother to get
1523 the mime-type here. */
1524 if (item->copyfrom_url)
1525 notify = svn_wc_create_notify(npath,
1526 svn_wc_notify_commit_copied_replaced,
1529 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1533 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1535 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1538 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1540 if (item->copyfrom_url)
1541 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
1544 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1547 if (item->kind == svn_node_file)
1549 const svn_string_t *propval;
1551 SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
1552 SVN_PROP_MIME_TYPE, pool, pool));
1555 notify->mime_type = propval->data;
1558 else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1559 || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1561 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1563 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1564 notify->content_state = svn_wc_notify_state_changed;
1566 notify->content_state = svn_wc_notify_state_unchanged;
1567 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1568 notify->prop_state = svn_wc_notify_state_changed;
1570 notify->prop_state = svn_wc_notify_state_unchanged;
1577 notify->kind = item->kind;
1578 notify->path_prefix = icb->notify_path_prefix;
1579 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1583 /* If this item is supposed to be deleted, do so. */
1584 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1586 SVN_ERR_ASSERT(parent_baton);
1587 err = editor->delete_entry(path, item->revision,
1588 parent_baton, pool);
1594 /* If this item is supposed to be added, do so. */
1595 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1597 if (kind == svn_node_file)
1599 SVN_ERR_ASSERT(parent_baton);
1600 err = editor->add_file(
1601 path, parent_baton, item->copyfrom_url,
1602 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1603 file_pool, &file_baton);
1605 else /* May be svn_node_none when adding parent dirs for a copy. */
1607 SVN_ERR_ASSERT(parent_baton);
1608 err = editor->add_directory(
1609 path, parent_baton, item->copyfrom_url,
1610 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1617 /* Set other prop-changes, if available in the baton */
1618 if (item->outgoing_prop_changes)
1621 apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1623 for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1625 prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1626 if (kind == svn_node_file)
1628 err = editor->change_file_prop(file_baton, prop->name,
1633 err = editor->change_dir_prop(*dir_baton, prop->name,
1643 /* Now handle property mods. */
1644 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1646 if (kind == svn_node_file)
1650 SVN_ERR_ASSERT(parent_baton);
1651 err = editor->open_file(path, parent_baton,
1653 file_pool, &file_baton);
1665 err = editor->open_root(icb->edit_baton, item->revision,
1670 err = editor->open_directory(path, parent_baton,
1680 /* When committing a directory that no longer exists in the
1681 repository, a "not found" error does not occur immediately
1682 upon opening the directory. It appears here during the delta
1684 err = svn_wc_transmit_prop_deltas2(
1685 ctx->wc_ctx, local_abspath, editor,
1686 (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
1691 /* Make any additional client -> repository prop changes. */
1692 if (item->outgoing_prop_changes)
1697 for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1699 prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
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 /* Finally, handle text mods (in that we need to open a file if it
1719 hasn't already been opened, and we need to put the file baton in
1721 if ((kind == svn_node_file)
1722 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1724 struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1728 SVN_ERR_ASSERT(parent_baton);
1729 err = editor->open_file(path, parent_baton,
1731 file_pool, &file_baton);
1737 /* Add this file mod to the FILE_MODS hash. */
1739 mod->file_baton = file_baton;
1740 svn_hash_sets(file_mods, item->session_relpath, mod);
1742 else if (file_baton)
1744 /* Close any outstanding file batons that didn't get caught by
1745 the "has local mods" conditional above. */
1746 err = editor->close_file(file_baton, NULL, file_pool);
1752 return SVN_NO_ERROR;
1755 return svn_error_trace(fixup_commit_error(local_abspath,
1762 svn_client__do_commit(const char *base_url,
1763 const apr_array_header_t *commit_items,
1764 const svn_delta_editor_t *editor,
1766 const char *notify_path_prefix,
1767 apr_hash_t **sha1_checksums,
1768 svn_client_ctx_t *ctx,
1769 apr_pool_t *result_pool,
1770 apr_pool_t *scratch_pool)
1772 apr_hash_t *file_mods = apr_hash_make(scratch_pool);
1773 apr_hash_t *items_hash = apr_hash_make(scratch_pool);
1774 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1775 apr_hash_index_t *hi;
1777 struct item_commit_baton cb_baton;
1778 apr_array_header_t *paths =
1779 apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
1781 /* Ditto for the checksums. */
1783 *sha1_checksums = apr_hash_make(result_pool);
1785 /* Build a hash from our COMMIT_ITEMS array, keyed on the
1786 relative paths (which come from the item URLs). And
1787 keep an array of those decoded paths, too. */
1788 for (i = 0; i < commit_items->nelts; i++)
1790 svn_client_commit_item3_t *item =
1791 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1792 const char *path = item->session_relpath;
1793 svn_hash_sets(items_hash, path, item);
1794 APR_ARRAY_PUSH(paths, const char *) = path;
1797 /* Setup the callback baton. */
1798 cb_baton.editor = editor;
1799 cb_baton.edit_baton = edit_baton;
1800 cb_baton.file_mods = file_mods;
1801 cb_baton.notify_path_prefix = notify_path_prefix;
1803 cb_baton.commit_items = items_hash;
1804 cb_baton.base_url = base_url;
1806 /* Drive the commit editor! */
1807 SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1808 do_item_commit, &cb_baton, scratch_pool));
1810 /* Transmit outstanding text deltas. */
1811 for (hi = apr_hash_first(scratch_pool, file_mods);
1813 hi = apr_hash_next(hi))
1815 struct file_mod_t *mod = svn__apr_hash_index_val(hi);
1816 const svn_client_commit_item3_t *item = mod->item;
1817 const svn_checksum_t *new_text_base_md5_checksum;
1818 const svn_checksum_t *new_text_base_sha1_checksum;
1819 svn_boolean_t fulltext = FALSE;
1822 svn_pool_clear(iterpool);
1824 /* Transmit the entry. */
1825 if (ctx->cancel_func)
1826 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1828 if (ctx->notify_func2)
1830 svn_wc_notify_t *notify;
1831 notify = svn_wc_create_notify(item->path,
1832 svn_wc_notify_commit_postfix_txdelta,
1834 notify->kind = svn_node_file;
1835 notify->path_prefix = notify_path_prefix;
1836 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1839 /* If the node has no history, transmit full text */
1840 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1841 && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
1844 err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
1845 &new_text_base_sha1_checksum,
1846 ctx->wc_ctx, item->path,
1847 fulltext, editor, mod->file_baton,
1848 result_pool, iterpool);
1852 svn_pool_destroy(iterpool); /* Close tempfiles */
1853 return svn_error_trace(fixup_commit_error(item->path,
1855 item->session_relpath,
1857 err, ctx, scratch_pool));
1861 svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
1864 svn_pool_destroy(iterpool);
1866 /* Close the edit. */
1867 return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
1872 svn_client__get_log_msg(const char **log_msg,
1873 const char **tmp_file,
1874 const apr_array_header_t *commit_items,
1875 svn_client_ctx_t *ctx,
1878 if (ctx->log_msg_func3)
1880 /* The client provided a callback function for the current API.
1881 Forward the call to it directly. */
1882 return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1883 ctx->log_msg_baton3, pool);
1885 else if (ctx->log_msg_func2 || ctx->log_msg_func)
1887 /* The client provided a pre-1.5 (or pre-1.3) API callback
1888 function. Convert the commit_items list to the appropriate
1889 type, and forward call to it. */
1891 apr_pool_t *scratch_pool = svn_pool_create(pool);
1892 apr_array_header_t *old_commit_items =
1893 apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
1896 for (i = 0; i < commit_items->nelts; i++)
1898 svn_client_commit_item3_t *item =
1899 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1901 if (ctx->log_msg_func2)
1903 svn_client_commit_item2_t *old_item =
1904 apr_pcalloc(scratch_pool, sizeof(*old_item));
1906 old_item->path = item->path;
1907 old_item->kind = item->kind;
1908 old_item->url = item->url;
1909 old_item->revision = item->revision;
1910 old_item->copyfrom_url = item->copyfrom_url;
1911 old_item->copyfrom_rev = item->copyfrom_rev;
1912 old_item->state_flags = item->state_flags;
1913 old_item->wcprop_changes = item->incoming_prop_changes;
1915 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
1918 else /* ctx->log_msg_func */
1920 svn_client_commit_item_t *old_item =
1921 apr_pcalloc(scratch_pool, sizeof(*old_item));
1923 old_item->path = item->path;
1924 old_item->kind = item->kind;
1925 old_item->url = item->url;
1926 /* The pre-1.3 API used the revision field for copyfrom_rev
1927 and revision depeding of copyfrom_url. */
1928 old_item->revision = item->copyfrom_url ?
1929 item->copyfrom_rev : item->revision;
1930 old_item->copyfrom_url = item->copyfrom_url;
1931 old_item->state_flags = item->state_flags;
1932 old_item->wcprop_changes = item->incoming_prop_changes;
1934 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
1939 if (ctx->log_msg_func2)
1940 err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
1941 ctx->log_msg_baton2, pool);
1943 err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
1944 ctx->log_msg_baton, pool);
1945 svn_pool_destroy(scratch_pool);
1950 /* No log message callback was provided by the client. */
1953 return SVN_NO_ERROR;
1958 svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
1959 const apr_hash_t *revprop_table_in,
1960 const char *log_msg,
1961 svn_client_ctx_t *ctx,
1964 apr_hash_t *new_revprop_table;
1965 if (revprop_table_in)
1967 if (svn_prop_has_svn_prop(revprop_table_in, pool))
1968 return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
1969 _("Standard properties can't be set "
1970 "explicitly as revision properties"));
1971 new_revprop_table = apr_hash_copy(pool, revprop_table_in);
1975 new_revprop_table = apr_hash_make(pool);
1977 svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
1978 svn_string_create(log_msg, pool));
1979 *revprop_table_out = new_revprop_table;
1980 return SVN_NO_ERROR;