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)
895 && (is_added || (is_deleted && is_op_root && status->copied))
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;
969 svn_client__committables_t *committables;
972 /* Helper for the commit harvesters */
974 handle_descendants(void *baton,
975 const void *key, apr_ssize_t klen, void *val,
978 struct handle_descendants_baton *hdb = baton;
979 apr_array_header_t *commit_items = val;
980 apr_pool_t *iterpool = svn_pool_create(pool);
981 const char *repos_root_url = key;
984 for (i = 0; i < commit_items->nelts; i++)
986 svn_client_commit_item3_t *item =
987 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
988 const apr_array_header_t *absent_descendants;
991 /* Is this a copy operation? */
992 if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
993 || ! item->copyfrom_url)
996 if (hdb->cancel_func)
997 SVN_ERR(hdb->cancel_func(hdb->cancel_baton));
999 svn_pool_clear(iterpool);
1001 SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants,
1002 hdb->wc_ctx, item->path,
1003 iterpool, iterpool));
1005 for (j = 0; j < absent_descendants->nelts; j++)
1007 svn_node_kind_t kind;
1008 svn_client_commit_item3_t *desc_item;
1009 const char *relpath = APR_ARRAY_IDX(absent_descendants, j,
1011 const char *local_abspath = svn_dirent_join(item->path, relpath,
1014 /* ### Need a sub-iterpool? */
1017 /* We found a 'not present' descendant during a copy (at op_depth>0),
1018 this is most commonly caused by copying some mixed revision tree.
1020 In this case not present can imply that the node does not exist
1021 in the parent revision, or that the node does. But we want to copy
1022 the working copy state in which it does not exist, but might be
1025 desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath);
1027 /* If the path has a commit operation (possibly at an higher
1028 op_depth, we might want to turn an add in a replace. */
1032 svn_boolean_t found_intermediate = FALSE;
1034 if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1035 continue; /* We already have a delete or replace */
1036 else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1037 continue; /* Not a copy/add, just a modification */
1039 dir = svn_dirent_dirname(local_abspath, iterpool);
1041 while (strcmp(dir, item->path))
1043 svn_client_commit_item3_t *i_item;
1045 i_item = svn_hash_gets(hdb->committables->by_path, dir);
1049 if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1050 || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1052 found_intermediate = TRUE;
1056 dir = svn_dirent_dirname(dir, iterpool);
1059 if (found_intermediate)
1060 continue; /* Some intermediate ancestor is an add or delete */
1062 /* Fall through to detect if we need to turn the add in a
1066 if (hdb->check_url_func)
1068 const char *from_url = svn_path_url_add_component2(
1069 item->copyfrom_url, relpath,
1072 SVN_ERR(hdb->check_url_func(hdb->check_url_baton,
1073 &kind, from_url, item->copyfrom_rev,
1076 if (kind == svn_node_none)
1077 continue; /* This node is already deleted */
1080 kind = svn_node_unknown; /* 'Ok' for a delete of something */
1084 /* Extend the existing add/copy item to create a replace */
1085 desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
1089 /* Add a new commit item that describes the delete */
1091 SVN_ERR(add_committable(hdb->committables,
1092 svn_dirent_join(item->path, relpath,
1096 svn_uri_skip_ancestor(
1098 svn_path_url_add_component2(item->url,
1103 NULL /* copyfrom_relpath */,
1105 NULL /* moved_from_abspath */,
1106 SVN_CLIENT_COMMIT_ITEM_DELETE,
1107 NULL /* lock tokens */,
1114 svn_pool_destroy(iterpool);
1115 return SVN_NO_ERROR;
1118 /* Allocate and initialize the COMMITTABLES structure from POOL.
1121 create_committables(svn_client__committables_t **committables,
1124 *committables = apr_palloc(pool, sizeof(**committables));
1126 (*committables)->by_repository = apr_hash_make(pool);
1127 (*committables)->by_path = apr_hash_make(pool);
1131 svn_client__harvest_committables(svn_client__committables_t **committables,
1132 apr_hash_t **lock_tokens,
1133 const char *base_dir_abspath,
1134 const apr_array_header_t *targets,
1135 int depth_empty_start,
1137 svn_boolean_t just_locked,
1138 const apr_array_header_t *changelists,
1139 svn_client__check_url_kind_t check_url_func,
1140 void *check_url_baton,
1141 svn_client_ctx_t *ctx,
1142 apr_pool_t *result_pool,
1143 apr_pool_t *scratch_pool)
1146 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1147 apr_hash_t *changelist_hash = NULL;
1148 struct handle_descendants_baton hdb;
1149 apr_hash_index_t *hi;
1151 /* It's possible that one of the named targets has a parent that is
1152 * itself scheduled for addition or replacement -- that is, the
1153 * parent is not yet versioned in the repository. This is okay, as
1154 * long as the parent itself is part of this same commit, either
1155 * directly, or by virtue of a grandparent, great-grandparent, etc,
1156 * being part of the commit.
1158 * Since we don't know what's included in the commit until we've
1159 * harvested all the targets, we can't reliably check this as we
1160 * go. So in `danglers', we record named targets whose parents
1161 * do not yet exist in the repository. Then after harvesting the total
1162 * commit group, we check to make sure those parents are included.
1164 * Each key of danglers is a parent which does not exist in the
1165 * repository. The (const char *) value is one of that parent's
1166 * children which is named as part of the commit; the child is
1167 * included only to make a better error message.
1169 * (The reason we don't bother to check unnamed -- i.e, implicit --
1170 * targets is that they can only join the commit if their parents
1171 * did too, so this situation can't arise for them.)
1173 apr_hash_t *danglers = apr_hash_make(scratch_pool);
1175 SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath));
1177 /* Create the COMMITTABLES structure. */
1178 create_committables(committables, result_pool);
1180 /* And the LOCK_TOKENS dito. */
1181 *lock_tokens = apr_hash_make(result_pool);
1183 /* If we have a list of changelists, convert that into a hash with
1185 if (changelists && changelists->nelts)
1186 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists,
1189 for (i = 0; i < targets->nelts; ++i)
1191 const char *target_abspath;
1193 svn_pool_clear(iterpool);
1195 /* Add the relative portion to the base abspath. */
1196 target_abspath = svn_dirent_join(base_dir_abspath,
1197 APR_ARRAY_IDX(targets, i, const char *),
1200 /* Handle our TARGET. */
1201 /* Make sure this isn't inside a working copy subtree that is
1202 * marked as tree-conflicted. */
1203 SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath,
1208 /* Are the remaining items externals with depth empty? */
1209 if (i == depth_empty_start)
1210 depth = svn_depth_empty;
1212 SVN_ERR(harvest_committables(target_abspath,
1213 *committables, *lock_tokens,
1214 NULL /* COPY_MODE_RELPATH */,
1215 depth, just_locked, changelist_hash,
1217 check_url_func, check_url_baton,
1218 ctx->cancel_func, ctx->cancel_baton,
1219 ctx->notify_func2, ctx->notify_baton2,
1220 ctx->wc_ctx, result_pool, iterpool));
1223 hdb.wc_ctx = ctx->wc_ctx;
1224 hdb.cancel_func = ctx->cancel_func;
1225 hdb.cancel_baton = ctx->cancel_baton;
1226 hdb.check_url_func = check_url_func;
1227 hdb.check_url_baton = check_url_baton;
1228 hdb.committables = *committables;
1230 SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository,
1231 handle_descendants, &hdb, iterpool));
1233 /* Make sure that every path in danglers is part of the commit. */
1234 for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi))
1236 const char *dangling_parent = svn__apr_hash_index_key(hi);
1238 svn_pool_clear(iterpool);
1240 if (! look_up_committable(*committables, dangling_parent, iterpool))
1242 const char *dangling_child = svn__apr_hash_index_val(hi);
1244 if (ctx->notify_func2 != NULL)
1246 svn_wc_notify_t *notify;
1248 notify = svn_wc_create_notify(dangling_child,
1249 svn_wc_notify_failed_no_parent,
1252 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1255 return svn_error_createf(
1256 SVN_ERR_ILLEGAL_TARGET, NULL,
1257 _("'%s' is not known to exist in the repository "
1258 "and is not part of the commit, "
1259 "yet its child '%s' is part of the commit"),
1260 /* Probably one or both of these is an entry, but
1261 safest to local_stylize just in case. */
1262 svn_dirent_local_style(dangling_parent, iterpool),
1263 svn_dirent_local_style(dangling_child, iterpool));
1267 svn_pool_destroy(iterpool);
1269 return SVN_NO_ERROR;
1272 struct copy_committables_baton
1274 svn_client_ctx_t *ctx;
1275 svn_client__committables_t *committables;
1276 apr_pool_t *result_pool;
1277 svn_client__check_url_kind_t check_url_func;
1278 void *check_url_baton;
1281 static svn_error_t *
1282 harvest_copy_committables(void *baton, void *item, apr_pool_t *pool)
1284 struct copy_committables_baton *btn = baton;
1285 svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item;
1286 const char *repos_root_url;
1287 const char *commit_relpath;
1288 struct handle_descendants_baton hdb;
1290 /* Read the entry for this SRC. */
1291 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
1293 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL,
1295 pair->src_abspath_or_url,
1298 commit_relpath = svn_uri_skip_ancestor(repos_root_url,
1299 pair->dst_abspath_or_url, pool);
1301 /* Handle this SRC. */
1302 SVN_ERR(harvest_committables(pair->src_abspath_or_url,
1303 btn->committables, NULL,
1306 FALSE, /* JUST_LOCKED */
1307 NULL /* changelists */,
1309 btn->check_url_func,
1310 btn->check_url_baton,
1311 btn->ctx->cancel_func,
1312 btn->ctx->cancel_baton,
1313 btn->ctx->notify_func2,
1314 btn->ctx->notify_baton2,
1315 btn->ctx->wc_ctx, btn->result_pool, pool));
1317 hdb.wc_ctx = btn->ctx->wc_ctx;
1318 hdb.cancel_func = btn->ctx->cancel_func;
1319 hdb.cancel_baton = btn->ctx->cancel_baton;
1320 hdb.check_url_func = btn->check_url_func;
1321 hdb.check_url_baton = btn->check_url_baton;
1322 hdb.committables = btn->committables;
1324 SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository,
1325 handle_descendants, &hdb, pool));
1327 return SVN_NO_ERROR;
1333 svn_client__get_copy_committables(svn_client__committables_t **committables,
1334 const apr_array_header_t *copy_pairs,
1335 svn_client__check_url_kind_t check_url_func,
1336 void *check_url_baton,
1337 svn_client_ctx_t *ctx,
1338 apr_pool_t *result_pool,
1339 apr_pool_t *scratch_pool)
1341 struct copy_committables_baton btn;
1343 /* Create the COMMITTABLES structure. */
1344 create_committables(committables, result_pool);
1347 btn.committables = *committables;
1348 btn.result_pool = result_pool;
1350 btn.check_url_func = check_url_func;
1351 btn.check_url_baton = check_url_baton;
1353 /* For each copy pair, harvest the committables for that pair into the
1354 committables hash. */
1355 return svn_iter_apr_array(NULL, copy_pairs,
1356 harvest_copy_committables, &btn, scratch_pool);
1360 int svn_client__sort_commit_item_urls(const void *a, const void *b)
1362 const svn_client_commit_item3_t *item1
1363 = *((const svn_client_commit_item3_t * const *) a);
1364 const svn_client_commit_item3_t *item2
1365 = *((const svn_client_commit_item3_t * const *) b);
1366 return svn_path_compare_paths(item1->url, item2->url);
1372 svn_client__condense_commit_items(const char **base_url,
1373 apr_array_header_t *commit_items,
1376 apr_array_header_t *ci = commit_items; /* convenience */
1378 svn_client_commit_item3_t *item, *last_item = NULL;
1381 SVN_ERR_ASSERT(ci && ci->nelts);
1383 /* Sort our commit items by their URLs. */
1384 qsort(ci->elts, ci->nelts,
1385 ci->elt_size, svn_client__sort_commit_item_urls);
1387 /* Loop through the URLs, finding the longest usable ancestor common
1388 to all of them, and making sure there are no duplicate URLs. */
1389 for (i = 0; i < ci->nelts; i++)
1391 item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1394 if ((last_item) && (strcmp(last_item->url, url) == 0))
1395 return svn_error_createf
1396 (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
1397 _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
1398 svn_dirent_local_style(item->path, pool),
1399 svn_dirent_local_style(last_item->path, pool));
1401 /* In the first iteration, our BASE_URL is just our only
1402 encountered commit URL to date. After that, we find the
1403 longest ancestor between the current BASE_URL and the current
1406 *base_url = apr_pstrdup(pool, url);
1408 *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
1410 /* If our BASE_URL is itself a to-be-committed item, and it is
1411 anything other than an already-versioned directory with
1412 property mods, we'll call its parent directory URL the
1413 BASE_URL. Why? Because we can't have a file URL as our base
1414 -- period -- and all other directory operations (removal,
1415 addition, etc.) require that we open that directory's parent
1417 /* ### I don't understand the strlen()s here, hmmm. -kff */
1418 if ((strlen(*base_url) == strlen(url))
1419 && (! ((item->kind == svn_node_dir)
1420 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1421 *base_url = svn_uri_dirname(*base_url, pool);
1423 /* Stash our item here for the next iteration. */
1427 /* Now that we've settled on a *BASE_URL, go hack that base off
1428 of all of our URLs and store it as session_relpath. */
1429 for (i = 0; i < ci->nelts; i++)
1431 svn_client_commit_item3_t *this_item
1432 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1434 this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
1435 this_item->url, pool);
1437 #ifdef SVN_CLIENT_COMMIT_DEBUG
1438 /* ### TEMPORARY CODE ### */
1439 SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
1440 SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n"));
1441 for (i = 0; i < ci->nelts; i++)
1443 svn_client_commit_item3_t *this_item
1444 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1446 flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1448 flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1450 flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1452 flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1454 flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1457 SVN_DBG((" %s %6ld '%s' (%s)\n",
1459 this_item->revision,
1460 this_item->url ? this_item->url : "",
1461 this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
1463 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1465 return SVN_NO_ERROR;
1471 const svn_client_commit_item3_t *item;
1476 /* A baton for use while driving a path-based editor driver for commit */
1477 struct item_commit_baton
1479 const svn_delta_editor_t *editor; /* commit editor */
1480 void *edit_baton; /* commit editor's baton */
1481 apr_hash_t *file_mods; /* hash: path->file_mod_t */
1482 const char *notify_path_prefix; /* notification path prefix
1483 (NULL is okay, else abs path) */
1484 svn_client_ctx_t *ctx; /* client context baton */
1485 apr_hash_t *commit_items; /* the committables */
1486 const char *base_url; /* The session url for the commit */
1490 /* Drive CALLBACK_BATON->editor with the change described by the item in
1491 * CALLBACK_BATON->commit_items that is keyed by PATH. If the change
1492 * includes a text mod, however, call the editor's file_open() function
1493 * but do not send the text mod to the editor; instead, add a mapping of
1494 * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
1496 * Before driving the editor, call the cancellation and notification
1497 * callbacks in CALLBACK_BATON->ctx, if present.
1499 * This implements svn_delta_path_driver_cb_func_t. */
1500 static svn_error_t *
1501 do_item_commit(void **dir_baton,
1503 void *callback_baton,
1507 struct item_commit_baton *icb = callback_baton;
1508 const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
1510 svn_node_kind_t kind = item->kind;
1511 void *file_baton = NULL;
1512 apr_pool_t *file_pool = NULL;
1513 const svn_delta_editor_t *editor = icb->editor;
1514 apr_hash_t *file_mods = icb->file_mods;
1515 svn_client_ctx_t *ctx = icb->ctx;
1517 const char *local_abspath = NULL;
1519 /* Do some initializations. */
1521 if (item->kind != svn_node_none && item->path)
1523 /* We always get an absolute path, see svn_client_commit_item3_t. */
1524 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
1525 local_abspath = item->path;
1528 /* If this is a file with textual mods, we'll be keeping its baton
1529 around until the end of the commit. So just lump its memory into
1530 a single, big, all-the-file-batons-in-here pool. Otherwise, we
1531 can just use POOL, and trust our caller to clean that mess up. */
1532 if ((kind == svn_node_file)
1533 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1534 file_pool = apr_hash_pool_get(file_mods);
1538 /* Call the cancellation function. */
1539 if (ctx->cancel_func)
1540 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1543 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1545 if (! item->copyfrom_url)
1546 return svn_error_createf
1547 (SVN_ERR_BAD_URL, NULL,
1548 _("Commit item '%s' has copy flag but no copyfrom URL"),
1549 svn_dirent_local_style(path, pool));
1550 if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1551 return svn_error_createf
1552 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1553 _("Commit item '%s' has copy flag but an invalid revision"),
1554 svn_dirent_local_style(path, pool));
1557 /* If a feedback table was supplied by the application layer,
1558 describe what we're about to do to this item. */
1559 if (ctx->notify_func2 && item->path)
1561 const char *npath = item->path;
1562 svn_wc_notify_t *notify;
1564 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1565 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1567 /* We don't print the "(bin)" notice for binary files when
1568 replacing, only when adding. So we don't bother to get
1569 the mime-type here. */
1570 if (item->copyfrom_url)
1571 notify = svn_wc_create_notify(npath,
1572 svn_wc_notify_commit_copied_replaced,
1575 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1579 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1581 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1584 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1586 if (item->copyfrom_url)
1587 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
1590 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1593 if (item->kind == svn_node_file)
1595 const svn_string_t *propval;
1597 SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
1598 SVN_PROP_MIME_TYPE, pool, pool));
1601 notify->mime_type = propval->data;
1604 else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1605 || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1607 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1609 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1610 notify->content_state = svn_wc_notify_state_changed;
1612 notify->content_state = svn_wc_notify_state_unchanged;
1613 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1614 notify->prop_state = svn_wc_notify_state_changed;
1616 notify->prop_state = svn_wc_notify_state_unchanged;
1623 notify->kind = item->kind;
1624 notify->path_prefix = icb->notify_path_prefix;
1625 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1629 /* If this item is supposed to be deleted, do so. */
1630 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1632 SVN_ERR_ASSERT(parent_baton);
1633 err = editor->delete_entry(path, item->revision,
1634 parent_baton, pool);
1640 /* If this item is supposed to be added, do so. */
1641 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1643 if (kind == svn_node_file)
1645 SVN_ERR_ASSERT(parent_baton);
1646 err = editor->add_file(
1647 path, parent_baton, item->copyfrom_url,
1648 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1649 file_pool, &file_baton);
1651 else /* May be svn_node_none when adding parent dirs for a copy. */
1653 SVN_ERR_ASSERT(parent_baton);
1654 err = editor->add_directory(
1655 path, parent_baton, item->copyfrom_url,
1656 item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1663 /* Set other prop-changes, if available in the baton */
1664 if (item->outgoing_prop_changes)
1667 apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1669 for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1671 prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1672 if (kind == svn_node_file)
1674 err = editor->change_file_prop(file_baton, prop->name,
1679 err = editor->change_dir_prop(*dir_baton, prop->name,
1689 /* Now handle property mods. */
1690 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1692 if (kind == svn_node_file)
1696 SVN_ERR_ASSERT(parent_baton);
1697 err = editor->open_file(path, parent_baton,
1699 file_pool, &file_baton);
1711 err = editor->open_root(icb->edit_baton, item->revision,
1716 err = editor->open_directory(path, parent_baton,
1726 /* When committing a directory that no longer exists in the
1727 repository, a "not found" error does not occur immediately
1728 upon opening the directory. It appears here during the delta
1730 err = svn_wc_transmit_prop_deltas2(
1731 ctx->wc_ctx, local_abspath, editor,
1732 (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
1737 /* Make any additional client -> repository prop changes. */
1738 if (item->outgoing_prop_changes)
1743 for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1745 prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
1747 if (kind == svn_node_file)
1749 err = editor->change_file_prop(file_baton, prop->name,
1754 err = editor->change_dir_prop(*dir_baton, prop->name,
1764 /* Finally, handle text mods (in that we need to open a file if it
1765 hasn't already been opened, and we need to put the file baton in
1767 if ((kind == svn_node_file)
1768 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1770 struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1774 SVN_ERR_ASSERT(parent_baton);
1775 err = editor->open_file(path, parent_baton,
1777 file_pool, &file_baton);
1783 /* Add this file mod to the FILE_MODS hash. */
1785 mod->file_baton = file_baton;
1786 svn_hash_sets(file_mods, item->session_relpath, mod);
1788 else if (file_baton)
1790 /* Close any outstanding file batons that didn't get caught by
1791 the "has local mods" conditional above. */
1792 err = editor->close_file(file_baton, NULL, file_pool);
1798 return SVN_NO_ERROR;
1801 return svn_error_trace(fixup_commit_error(local_abspath,
1808 svn_client__do_commit(const char *base_url,
1809 const apr_array_header_t *commit_items,
1810 const svn_delta_editor_t *editor,
1812 const char *notify_path_prefix,
1813 apr_hash_t **sha1_checksums,
1814 svn_client_ctx_t *ctx,
1815 apr_pool_t *result_pool,
1816 apr_pool_t *scratch_pool)
1818 apr_hash_t *file_mods = apr_hash_make(scratch_pool);
1819 apr_hash_t *items_hash = apr_hash_make(scratch_pool);
1820 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1821 apr_hash_index_t *hi;
1823 struct item_commit_baton cb_baton;
1824 apr_array_header_t *paths =
1825 apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
1827 /* Ditto for the checksums. */
1829 *sha1_checksums = apr_hash_make(result_pool);
1831 /* Build a hash from our COMMIT_ITEMS array, keyed on the
1832 relative paths (which come from the item URLs). And
1833 keep an array of those decoded paths, too. */
1834 for (i = 0; i < commit_items->nelts; i++)
1836 svn_client_commit_item3_t *item =
1837 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1838 const char *path = item->session_relpath;
1839 svn_hash_sets(items_hash, path, item);
1840 APR_ARRAY_PUSH(paths, const char *) = path;
1843 /* Setup the callback baton. */
1844 cb_baton.editor = editor;
1845 cb_baton.edit_baton = edit_baton;
1846 cb_baton.file_mods = file_mods;
1847 cb_baton.notify_path_prefix = notify_path_prefix;
1849 cb_baton.commit_items = items_hash;
1850 cb_baton.base_url = base_url;
1852 /* Drive the commit editor! */
1853 SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1854 do_item_commit, &cb_baton, scratch_pool));
1856 /* Transmit outstanding text deltas. */
1857 for (hi = apr_hash_first(scratch_pool, file_mods);
1859 hi = apr_hash_next(hi))
1861 struct file_mod_t *mod = svn__apr_hash_index_val(hi);
1862 const svn_client_commit_item3_t *item = mod->item;
1863 const svn_checksum_t *new_text_base_md5_checksum;
1864 const svn_checksum_t *new_text_base_sha1_checksum;
1865 svn_boolean_t fulltext = FALSE;
1868 svn_pool_clear(iterpool);
1870 /* Transmit the entry. */
1871 if (ctx->cancel_func)
1872 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1874 if (ctx->notify_func2)
1876 svn_wc_notify_t *notify;
1877 notify = svn_wc_create_notify(item->path,
1878 svn_wc_notify_commit_postfix_txdelta,
1880 notify->kind = svn_node_file;
1881 notify->path_prefix = notify_path_prefix;
1882 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1885 /* If the node has no history, transmit full text */
1886 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1887 && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
1890 err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
1891 &new_text_base_sha1_checksum,
1892 ctx->wc_ctx, item->path,
1893 fulltext, editor, mod->file_baton,
1894 result_pool, iterpool);
1898 svn_pool_destroy(iterpool); /* Close tempfiles */
1899 return svn_error_trace(fixup_commit_error(item->path,
1901 item->session_relpath,
1903 err, ctx, scratch_pool));
1907 svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
1910 svn_pool_destroy(iterpool);
1912 /* Close the edit. */
1913 return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
1918 svn_client__get_log_msg(const char **log_msg,
1919 const char **tmp_file,
1920 const apr_array_header_t *commit_items,
1921 svn_client_ctx_t *ctx,
1924 if (ctx->log_msg_func3)
1926 /* The client provided a callback function for the current API.
1927 Forward the call to it directly. */
1928 return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1929 ctx->log_msg_baton3, pool);
1931 else if (ctx->log_msg_func2 || ctx->log_msg_func)
1933 /* The client provided a pre-1.5 (or pre-1.3) API callback
1934 function. Convert the commit_items list to the appropriate
1935 type, and forward call to it. */
1937 apr_pool_t *scratch_pool = svn_pool_create(pool);
1938 apr_array_header_t *old_commit_items =
1939 apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
1942 for (i = 0; i < commit_items->nelts; i++)
1944 svn_client_commit_item3_t *item =
1945 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1947 if (ctx->log_msg_func2)
1949 svn_client_commit_item2_t *old_item =
1950 apr_pcalloc(scratch_pool, sizeof(*old_item));
1952 old_item->path = item->path;
1953 old_item->kind = item->kind;
1954 old_item->url = item->url;
1955 old_item->revision = item->revision;
1956 old_item->copyfrom_url = item->copyfrom_url;
1957 old_item->copyfrom_rev = item->copyfrom_rev;
1958 old_item->state_flags = item->state_flags;
1959 old_item->wcprop_changes = item->incoming_prop_changes;
1961 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
1964 else /* ctx->log_msg_func */
1966 svn_client_commit_item_t *old_item =
1967 apr_pcalloc(scratch_pool, sizeof(*old_item));
1969 old_item->path = item->path;
1970 old_item->kind = item->kind;
1971 old_item->url = item->url;
1972 /* The pre-1.3 API used the revision field for copyfrom_rev
1973 and revision depeding of copyfrom_url. */
1974 old_item->revision = item->copyfrom_url ?
1975 item->copyfrom_rev : item->revision;
1976 old_item->copyfrom_url = item->copyfrom_url;
1977 old_item->state_flags = item->state_flags;
1978 old_item->wcprop_changes = item->incoming_prop_changes;
1980 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
1985 if (ctx->log_msg_func2)
1986 err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
1987 ctx->log_msg_baton2, pool);
1989 err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
1990 ctx->log_msg_baton, pool);
1991 svn_pool_destroy(scratch_pool);
1996 /* No log message callback was provided by the client. */
1999 return SVN_NO_ERROR;
2004 svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
2005 const apr_hash_t *revprop_table_in,
2006 const char *log_msg,
2007 svn_client_ctx_t *ctx,
2010 apr_hash_t *new_revprop_table;
2011 if (revprop_table_in)
2013 if (svn_prop_has_svn_prop(revprop_table_in, pool))
2014 return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2015 _("Standard properties can't be set "
2016 "explicitly as revision properties"));
2017 new_revprop_table = apr_hash_copy(pool, revprop_table_in);
2021 new_revprop_table = apr_hash_make(pool);
2023 svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
2024 svn_string_create(log_msg, pool));
2025 *revprop_table_out = new_revprop_table;
2026 return SVN_NO_ERROR;