2 * mtcc.c -- Multi Command Context implementation. This allows
3 * performing many operations without a working copy.
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
22 * ====================================================================
25 #include "svn_dirent_uri.h"
28 #include "svn_props.h"
29 #include "svn_pools.h"
30 #include "svn_subst.h"
32 #include "private/svn_client_mtcc.h"
35 #include "svn_private_config.h"
41 #define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
43 /* The kind of operation to perform in an mtcc_op_t */
44 typedef enum mtcc_kind_t
53 typedef struct mtcc_op_t
55 const char *name; /* basename of operation */
56 mtcc_kind_t kind; /* editor operation */
58 apr_array_header_t *children; /* List of mtcc_op_t * */
60 const char *src_relpath; /* For ADD_DIR, ADD_FILE */
61 svn_revnum_t src_rev; /* For ADD_DIR, ADD_FILE */
62 svn_stream_t *src_stream; /* For ADD_FILE, OPEN_FILE */
63 svn_checksum_t *src_checksum; /* For ADD_FILE, OPEN_FILE */
64 svn_stream_t *base_stream; /* For ADD_FILE, OPEN_FILE */
65 const svn_checksum_t *base_checksum; /* For ADD_FILE, OPEN_FILE */
67 apr_array_header_t *prop_mods; /* For all except DELETE
70 svn_boolean_t performed_stat; /* Verified kind with repository */
73 /* Check if the mtcc doesn't contain any modifications yet */
74 #define MTCC_UNMODIFIED(mtcc) \
75 ((mtcc->root_op->kind == OP_OPEN_DIR \
76 || mtcc->root_op->kind == OP_OPEN_FILE) \
77 && (mtcc->root_op->prop_mods == NULL \
78 || !mtcc->root_op->prop_mods->nelts) \
79 && (mtcc->root_op->children == NULL \
80 || !mtcc->root_op->children->nelts))
82 struct svn_client__mtcc_t
85 svn_revnum_t head_revision;
86 svn_revnum_t base_revision;
88 svn_ra_session_t *ra_session;
89 svn_client_ctx_t *ctx;
95 mtcc_op_create(const char *name,
97 svn_boolean_t directory,
98 apr_pool_t *result_pool)
102 op = apr_pcalloc(result_pool, sizeof(*op));
103 op->name = name ? apr_pstrdup(result_pool, name) : "";
106 op->kind = directory ? OP_ADD_DIR : OP_ADD_FILE;
108 op->kind = directory ? OP_OPEN_DIR : OP_OPEN_FILE;
111 op->children = apr_array_make(result_pool, 4, sizeof(mtcc_op_t *));
113 op->src_rev = SVN_INVALID_REVNUM;
119 mtcc_op_find(mtcc_op_t **op,
120 svn_boolean_t *created,
123 svn_boolean_t find_existing,
124 svn_boolean_t find_deletes,
125 svn_boolean_t create_file,
126 apr_pool_t *result_pool,
127 apr_pool_t *scratch_pool)
133 assert(svn_relpath_is_canonical(relpath));
137 if (SVN_PATH_IS_EMPTY(relpath))
147 child = strchr(relpath, '/');
151 name = apr_pstrmemdup(scratch_pool, relpath, (child-relpath));
152 child++; /* Skip '/' */
157 if (!base_op->children)
165 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
166 _("Can't operate on '%s' because '%s' is not a "
168 name, base_op->name);
171 for (i = base_op->children->nelts-1; i >= 0 ; i--)
175 cop = APR_ARRAY_IDX(base_op->children, i, mtcc_op_t *);
177 if (! strcmp(cop->name, name)
178 && (find_deletes || cop->kind != OP_DELETE))
180 return svn_error_trace(
181 mtcc_op_find(op, created, child ? child : "", cop,
182 find_existing, find_deletes, create_file,
183 result_pool, scratch_pool));
196 cop = mtcc_op_create(name, FALSE, child || !create_file, result_pool);
198 APR_ARRAY_PUSH(base_op->children, mtcc_op_t *) = cop;
207 return svn_error_trace(
208 mtcc_op_find(op, created, child, cop, find_existing,
209 find_deletes, create_file,
210 result_pool, scratch_pool));
214 /* Gets the original repository location of RELPATH, checking things
215 like copies, moves, etc. */
217 get_origin(svn_boolean_t *done,
218 const char **origin_relpath,
222 apr_pool_t *result_pool,
223 apr_pool_t *scratch_pool)
227 if (SVN_PATH_IS_EMPTY(relpath))
229 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
231 *origin_relpath = op->src_relpath
232 ? apr_pstrdup(result_pool, op->src_relpath)
238 child = strchr(relpath, '/');
241 name = apr_pstrmemdup(scratch_pool, relpath, child-relpath);
242 child++; /* Skip '/' */
247 if (op->children && op->children->nelts)
251 for (i = op->children->nelts-1; i >= 0; i--)
255 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
257 if (! strcmp(cop->name, name))
259 if (cop->kind == OP_DELETE)
265 SVN_ERR(get_origin(done, origin_relpath, rev,
266 cop, child ? child : "",
267 result_pool, scratch_pool));
269 if (*origin_relpath || *done)
277 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
282 *origin_relpath = svn_relpath_join(op->src_relpath, relpath,
291 /* Obtains the original repository location for an mtcc relpath as
292 *ORIGIN_RELPATH @ *REV, if it has one. If it has not and IGNORE_ENOENT
293 is TRUE report *ORIGIN_RELPATH as NULL, otherwise return an error */
295 mtcc_get_origin(const char **origin_relpath,
298 svn_boolean_t ignore_enoent,
299 svn_client__mtcc_t *mtcc,
300 apr_pool_t *result_pool,
301 apr_pool_t *scratch_pool)
303 svn_boolean_t done = FALSE;
305 *origin_relpath = NULL;
306 *rev = SVN_INVALID_REVNUM;
308 SVN_ERR(get_origin(&done, origin_relpath, rev, mtcc->root_op, relpath,
309 result_pool, scratch_pool));
311 if (!*origin_relpath && !done)
313 *origin_relpath = apr_pstrdup(result_pool, relpath);
314 *rev = mtcc->base_revision;
316 else if (!ignore_enoent)
318 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
319 _("No origin found for node at '%s'"),
327 svn_client__mtcc_create(svn_client__mtcc_t **mtcc,
328 const char *anchor_url,
329 svn_revnum_t base_revision,
330 svn_client_ctx_t *ctx,
331 apr_pool_t *result_pool,
332 apr_pool_t *scratch_pool)
334 apr_pool_t *mtcc_pool;
336 mtcc_pool = svn_pool_create(result_pool);
338 *mtcc = apr_pcalloc(mtcc_pool, sizeof(**mtcc));
339 (*mtcc)->pool = mtcc_pool;
341 (*mtcc)->root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc_pool);
345 SVN_ERR(svn_client_open_ra_session2(&(*mtcc)->ra_session, anchor_url,
346 NULL /* wri_abspath */, ctx,
347 mtcc_pool, scratch_pool));
349 SVN_ERR(svn_ra_get_latest_revnum((*mtcc)->ra_session, &(*mtcc)->head_revision,
352 if (SVN_IS_VALID_REVNUM(base_revision))
353 (*mtcc)->base_revision = base_revision;
355 (*mtcc)->base_revision = (*mtcc)->head_revision;
357 if ((*mtcc)->base_revision > (*mtcc)->head_revision)
358 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
359 _("No such revision %ld (HEAD is %ld)"),
360 base_revision, (*mtcc)->head_revision);
366 update_copy_src(mtcc_op_t *op,
367 const char *add_relpath,
368 apr_pool_t *result_pool)
373 op->src_relpath = svn_relpath_join(add_relpath, op->src_relpath,
379 for (i = 0; i < op->children->nelts; i++)
383 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
385 SVN_ERR(update_copy_src(cop, add_relpath, result_pool));
392 mtcc_reparent(const char *new_anchor_url,
393 svn_client__mtcc_t *mtcc,
394 apr_pool_t *scratch_pool)
396 const char *session_url;
399 SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url,
402 up = svn_uri_skip_ancestor(new_anchor_url, session_url, scratch_pool);
406 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
407 _("'%s' is not an ancestor of '%s'"),
408 new_anchor_url, session_url);
412 return SVN_NO_ERROR; /* Same url */
415 /* Update copy origins recursively...:( */
416 SVN_ERR(update_copy_src(mtcc->root_op, up, mtcc->pool));
418 SVN_ERR(svn_ra_reparent(mtcc->ra_session, new_anchor_url, scratch_pool));
420 /* Create directory open operations for new ancestors */
425 mtcc->root_op->name = svn_relpath_basename(up, mtcc->pool);
426 up = svn_relpath_dirname(up, scratch_pool);
428 root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc->pool);
430 APR_ARRAY_PUSH(root_op->children, mtcc_op_t *) = mtcc->root_op;
432 mtcc->root_op = root_op;
438 /* Check if it is safe to create a new node at NEW_RELPATH. Return a proper
439 error if it is not */
441 mtcc_verify_create(svn_client__mtcc_t *mtcc,
442 const char *new_relpath,
443 apr_pool_t *scratch_pool)
445 svn_node_kind_t kind;
447 if (*new_relpath || !MTCC_UNMODIFIED(mtcc))
451 SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, FALSE,
452 FALSE, mtcc->pool, scratch_pool));
455 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
456 _("Path '%s' already exists"),
459 SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE,
460 FALSE, mtcc->pool, scratch_pool));
463 return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */
466 /* mod_dav_svn used to allow overwriting existing directories. Let's hide
467 that for users of this api */
468 SVN_ERR(svn_client__mtcc_check_path(&kind, new_relpath, FALSE,
469 mtcc, scratch_pool));
471 if (kind != svn_node_none)
472 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
473 _("Path '%s' already exists"),
481 svn_client__mtcc_add_add_file(const char *relpath,
482 svn_stream_t *src_stream,
483 const svn_checksum_t *src_checksum,
484 svn_client__mtcc_t *mtcc,
485 apr_pool_t *scratch_pool)
488 svn_boolean_t created;
489 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
491 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
493 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
495 /* Turn the root operation into a file addition */
500 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
501 TRUE, mtcc->pool, scratch_pool));
505 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
506 _("Can't add file at '%s'"),
511 op->kind = OP_ADD_FILE;
512 op->src_stream = src_stream;
513 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
520 svn_client__mtcc_add_copy(const char *src_relpath,
521 svn_revnum_t revision,
522 const char *dst_relpath,
523 svn_client__mtcc_t *mtcc,
524 apr_pool_t *scratch_pool)
527 svn_boolean_t created;
528 svn_node_kind_t kind;
530 SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath)
531 && svn_relpath_is_canonical(dst_relpath));
533 if (! SVN_IS_VALID_REVNUM(revision))
534 revision = mtcc->head_revision;
535 else if (revision > mtcc->head_revision)
537 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
538 _("No such revision %ld"), revision);
541 SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool));
543 /* Subversion requires the kind of a copy */
544 SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind,
547 if (kind != svn_node_dir && kind != svn_node_file)
549 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
550 _("Path '%s' not found in revision %ld"),
551 src_relpath, revision);
554 SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE,
555 (kind == svn_node_file), mtcc->pool, scratch_pool));
559 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
560 _("Can't add node at '%s'"),
564 op->kind = (kind == svn_node_file) ? OP_ADD_FILE : OP_ADD_DIR;
565 op->src_relpath = apr_pstrdup(mtcc->pool, src_relpath);
566 op->src_rev = revision;
572 svn_client__mtcc_add_delete(const char *relpath,
573 svn_client__mtcc_t *mtcc,
574 apr_pool_t *scratch_pool)
577 svn_boolean_t created;
578 svn_node_kind_t kind;
580 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
582 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
583 mtcc, scratch_pool));
585 if (kind == svn_node_none)
586 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
587 _("Can't delete node at '%s' as it "
591 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
593 /* Turn root operation into delete */
598 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE,
599 TRUE, mtcc->pool, scratch_pool));
603 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
604 _("Can't delete node at '%s'"),
609 op->kind = OP_DELETE;
611 op->prop_mods = NULL;
617 svn_client__mtcc_add_mkdir(const char *relpath,
618 svn_client__mtcc_t *mtcc,
619 apr_pool_t *scratch_pool)
622 svn_boolean_t created;
623 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
625 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
627 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
629 /* Turn the root of the operation in an MKDIR */
630 mtcc->root_op->kind = OP_ADD_DIR;
635 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
636 FALSE, mtcc->pool, scratch_pool));
640 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
641 _("Can't create directory at '%s'"),
645 op->kind = OP_ADD_DIR;
651 svn_client__mtcc_add_move(const char *src_relpath,
652 const char *dst_relpath,
653 svn_client__mtcc_t *mtcc,
654 apr_pool_t *scratch_pool)
656 const char *origin_relpath;
657 svn_revnum_t origin_rev;
659 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
660 src_relpath, FALSE, mtcc,
661 scratch_pool, scratch_pool));
663 SVN_ERR(svn_client__mtcc_add_copy(src_relpath, mtcc->base_revision,
664 dst_relpath, mtcc, scratch_pool));
665 SVN_ERR(svn_client__mtcc_add_delete(src_relpath, mtcc, scratch_pool));
670 /* Baton for mtcc_prop_getter */
671 struct mtcc_prop_get_baton
673 svn_client__mtcc_t *mtcc;
675 svn_cancel_func_t cancel_func;
679 /* Implements svn_wc_canonicalize_svn_prop_get_file_t */
681 mtcc_prop_getter(const svn_string_t **mime_type,
682 svn_stream_t *stream,
686 struct mtcc_prop_get_baton *mpgb = baton;
687 const char *origin_relpath;
688 svn_revnum_t origin_rev;
689 apr_hash_t *props = NULL;
696 /* Check if we have the information locally */
697 SVN_ERR(mtcc_op_find(&op, NULL, mpgb->relpath, mpgb->mtcc->root_op, TRUE,
698 FALSE, FALSE, pool, pool));
706 for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++)
708 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i,
711 if (! strcmp(mod->name, SVN_PROP_MIME_TYPE))
713 *mime_type = svn_string_dup(mod->value, pool);
719 if (stream && op->src_stream)
721 svn_stream_mark_t *mark;
724 /* Is the source stream capable of being read multiple times? */
725 err = svn_stream_mark(op->src_stream, &mark, pool);
727 if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED)
728 return svn_error_trace(err);
729 svn_error_clear(err);
733 err = svn_stream_copy3(svn_stream_disown(op->src_stream, pool),
734 svn_stream_disown(stream, pool),
735 mpgb->cancel_func, mpgb->cancel_baton,
738 SVN_ERR(svn_error_compose_create(
740 svn_stream_seek(op->src_stream, mark)));
742 /* else: ### Create tempfile? */
744 stream = NULL; /* Stream is handled */
748 if (!stream && !mime_type)
751 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE,
752 mpgb->mtcc, pool, pool));
755 return SVN_NO_ERROR; /* Nothing to fetch at repository */
757 SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev,
758 stream, NULL, mime_type ? &props : NULL, pool));
760 if (mime_type && props)
761 *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
767 svn_client__mtcc_add_propset(const char *relpath,
768 const char *propname,
769 const svn_string_t *propval,
770 svn_boolean_t skip_checks,
771 svn_client__mtcc_t *mtcc,
772 apr_pool_t *scratch_pool)
775 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
777 if (! svn_prop_name_is_valid(propname))
778 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
779 _("Bad property name: '%s'"), propname);
781 if (svn_prop_is_known_svn_rev_prop(propname))
782 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
783 _("Revision property '%s' not allowed "
784 "in this context"), propname);
786 if (svn_property_kind2(propname) == svn_prop_wc_kind)
787 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
788 _("'%s' is a wcprop, thus not accessible "
789 "to clients"), propname);
791 if (!skip_checks && svn_prop_needs_translation(propname))
793 svn_string_t *translated_value;
794 SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
797 scratch_pool, scratch_pool),
798 _("Error normalizing property value"));
800 propval = translated_value;
803 if (propval && svn_prop_is_svn_prop(propname))
805 struct mtcc_prop_get_baton mpbg;
806 svn_node_kind_t kind;
807 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, mtcc,
811 mpbg.relpath = relpath;
812 mpbg.cancel_func = mtcc->ctx->cancel_func;
813 mpbg.cancel_baton = mtcc->ctx->cancel_baton;
815 SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval,
816 relpath, kind, skip_checks,
817 mtcc_prop_getter, &mpbg,
821 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
823 svn_node_kind_t kind;
825 /* Probing the node for an unmodified root will fix the node type to
826 a file if necessary */
828 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
829 mtcc, scratch_pool));
831 if (kind == svn_node_none)
832 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
833 _("Can't set properties at not existing '%s'"),
840 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
841 FALSE, mtcc->pool, scratch_pool));
845 svn_node_kind_t kind;
846 svn_boolean_t created;
848 /* ### TODO: Check if this node is within a newly copied directory,
849 and update origin values accordingly */
851 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
852 mtcc, scratch_pool));
854 if (kind == svn_node_none)
855 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
856 _("Can't set properties at not existing '%s'"),
859 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
860 (kind != svn_node_dir),
861 mtcc->pool, scratch_pool));
863 SVN_ERR_ASSERT(op != NULL);
868 op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t));
871 svn_prop_t propchange;
872 propchange.name = apr_pstrdup(mtcc->pool, propname);
875 propchange.value = svn_string_dup(propval, mtcc->pool);
877 propchange.value = NULL;
879 APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange;
886 svn_client__mtcc_add_update_file(const char *relpath,
887 svn_stream_t *src_stream,
888 const svn_checksum_t *src_checksum,
889 svn_stream_t *base_stream,
890 const svn_checksum_t *base_checksum,
891 svn_client__mtcc_t *mtcc,
892 apr_pool_t *scratch_pool)
895 svn_boolean_t created;
896 svn_node_kind_t kind;
897 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
899 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
900 mtcc, scratch_pool));
902 if (kind != svn_node_file)
903 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
904 _("Can't update '%s' because it is not a file"),
907 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
908 TRUE, mtcc->pool, scratch_pool));
911 || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE)
912 || (op->src_stream != NULL))
914 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
915 _("Can't update file at '%s'"), relpath);
918 op->src_stream = src_stream;
919 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
922 op->base_stream = base_stream;
923 op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum,
931 svn_client__mtcc_check_path(svn_node_kind_t *kind,
933 svn_boolean_t check_repository,
934 svn_client__mtcc_t *mtcc,
935 apr_pool_t *scratch_pool)
937 const char *origin_relpath;
938 svn_revnum_t origin_rev;
941 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
943 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)
944 && !mtcc->root_op->performed_stat)
946 /* We know nothing about the root. Perhaps it is a file? */
947 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision,
948 kind, scratch_pool));
950 mtcc->root_op->performed_stat = TRUE;
951 if (*kind == svn_node_file)
953 mtcc->root_op->kind = OP_OPEN_FILE;
954 mtcc->root_op->children = NULL;
959 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
960 FALSE, mtcc->pool, scratch_pool));
962 if (!op || (check_repository && !op->performed_stat))
964 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
966 scratch_pool, scratch_pool));
969 *kind = svn_node_none;
971 SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath,
972 origin_rev, kind, scratch_pool));
974 if (op && *kind == svn_node_dir)
976 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
977 op->performed_stat = TRUE;
978 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
979 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
980 _("Can't perform file operation "
981 "on '%s' as it is not a file"),
984 else if (op && *kind == svn_node_file)
986 if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
987 op->performed_stat = TRUE;
988 else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
989 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
990 _("Can't perform directory operation "
991 "on '%s' as it is not a directory"),
994 else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE))
996 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
997 _("Can't open '%s' as it does not exist"),
1001 return SVN_NO_ERROR;
1005 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1007 *kind = svn_node_dir;
1008 return SVN_NO_ERROR;
1010 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1012 *kind = svn_node_file;
1013 return SVN_NO_ERROR;
1015 SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */
1018 static svn_error_t *
1019 commit_properties(const svn_delta_editor_t *editor,
1020 const mtcc_op_t *op,
1022 apr_pool_t *scratch_pool)
1025 apr_pool_t *iterpool;
1027 if (!op->prop_mods || op->prop_mods->nelts == 0)
1028 return SVN_NO_ERROR;
1030 iterpool = svn_pool_create(scratch_pool);
1031 for (i = 0; i < op->prop_mods->nelts; i++)
1033 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t);
1035 svn_pool_clear(iterpool);
1037 if (op->kind == OP_ADD_DIR || op->kind == OP_OPEN_DIR)
1038 SVN_ERR(editor->change_dir_prop(node_baton, mod->name, mod->value,
1040 else if (op->kind == OP_ADD_FILE || op->kind == OP_OPEN_FILE)
1041 SVN_ERR(editor->change_file_prop(node_baton, mod->name, mod->value,
1045 svn_pool_destroy(iterpool);
1046 return SVN_NO_ERROR;
1049 /* Handles updating a file to a delta editor and then closes it */
1050 static svn_error_t *
1051 commit_file(const svn_delta_editor_t *editor,
1054 const char *session_url,
1055 const char *relpath,
1056 svn_client_ctx_t *ctx,
1057 apr_pool_t *scratch_pool)
1059 const char *text_checksum = NULL;
1060 svn_checksum_t *src_checksum = op->src_checksum;
1061 SVN_ERR(commit_properties(editor, op, file_baton, scratch_pool));
1065 const char *base_checksum = NULL;
1066 apr_pool_t *txdelta_pool = scratch_pool;
1067 svn_txdelta_window_handler_t window_handler;
1068 void *handler_baton;
1069 svn_stream_t *src_stream = op->src_stream;
1071 if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5)
1072 base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool);
1074 /* ### TODO: Future enhancement: Allocate in special pool and send
1075 files after the true edit operation, like a wc commit */
1076 SVN_ERR(editor->apply_textdelta(file_baton, base_checksum, txdelta_pool,
1077 &window_handler, &handler_baton));
1079 if (ctx->notify_func2)
1081 svn_wc_notify_t *notify;
1083 notify = svn_wc_create_notify_url(
1084 svn_path_url_add_component2(session_url, relpath,
1086 svn_wc_notify_commit_postfix_txdelta,
1089 notify->path = relpath;
1090 notify->kind = svn_node_file;
1092 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
1095 if (window_handler != svn_delta_noop_window_handler)
1097 if (!src_checksum || src_checksum->kind != svn_checksum_md5)
1098 src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL,
1100 TRUE, scratch_pool);
1102 if (!op->base_stream)
1103 SVN_ERR(svn_txdelta_send_stream(src_stream,
1104 window_handler, handler_baton, NULL,
1107 SVN_ERR(svn_txdelta_run(op->base_stream, src_stream,
1108 window_handler, handler_baton,
1109 svn_checksum_md5, NULL,
1110 ctx->cancel_func, ctx->cancel_baton,
1111 scratch_pool, scratch_pool));
1114 SVN_ERR(svn_stream_close(src_stream));
1115 if (op->base_stream)
1116 SVN_ERR(svn_stream_close(op->base_stream));
1119 if (src_checksum && src_checksum->kind == svn_checksum_md5)
1120 text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool);
1122 return svn_error_trace(editor->close_file(file_baton, text_checksum,
1126 /* Handles updating a directory to a delta editor and then closes it */
1127 static svn_error_t *
1128 commit_directory(const svn_delta_editor_t *editor,
1130 const char *relpath,
1131 svn_revnum_t base_rev,
1133 const char *session_url,
1134 svn_client_ctx_t *ctx,
1135 apr_pool_t *scratch_pool)
1137 SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool));
1139 if (op->children && op->children->nelts > 0)
1141 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1144 for (i = 0; i < op->children->nelts; i++)
1147 const char * child_relpath;
1150 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1152 svn_pool_clear(iterpool);
1154 if (ctx->cancel_func)
1155 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1157 child_relpath = svn_relpath_join(relpath, cop->name, iterpool);
1162 SVN_ERR(editor->delete_entry(child_relpath, base_rev,
1163 dir_baton, iterpool));
1167 SVN_ERR(editor->add_directory(child_relpath, dir_baton,
1169 ? svn_path_url_add_component2(
1175 iterpool, &child_baton));
1176 SVN_ERR(commit_directory(editor, cop, child_relpath,
1177 SVN_INVALID_REVNUM, child_baton,
1178 session_url, ctx, iterpool));
1181 SVN_ERR(editor->open_directory(child_relpath, dir_baton,
1182 base_rev, iterpool, &child_baton));
1183 SVN_ERR(commit_directory(editor, cop, child_relpath,
1184 base_rev, child_baton,
1185 session_url, ctx, iterpool));
1189 SVN_ERR(editor->add_file(child_relpath, dir_baton,
1191 ? svn_path_url_add_component2(
1197 iterpool, &child_baton));
1198 SVN_ERR(commit_file(editor, cop, child_baton,
1199 session_url, child_relpath, ctx, iterpool));
1202 SVN_ERR(editor->open_file(child_relpath, dir_baton, base_rev,
1203 iterpool, &child_baton));
1204 SVN_ERR(commit_file(editor, cop, child_baton,
1205 session_url, child_relpath, ctx, iterpool));
1209 SVN_ERR_MALFUNCTION();
1214 return svn_error_trace(editor->close_directory(dir_baton, scratch_pool));
1218 /* Helper function to recursively create svn_client_commit_item3_t items
1219 to provide to the log message callback */
1220 static svn_error_t *
1221 add_commit_items(mtcc_op_t *op,
1222 const char *session_url,
1224 apr_array_header_t *commit_items,
1225 apr_pool_t *result_pool,
1226 apr_pool_t *scratch_pool)
1228 if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE)
1229 || (op->prop_mods && op->prop_mods->nelts)
1230 || (op->src_stream))
1232 svn_client_commit_item3_t *item;
1234 item = svn_client_commit_item3_create(result_pool);
1237 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1238 item->kind = svn_node_dir;
1239 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1240 item->kind = svn_node_file;
1242 item->kind = svn_node_unknown;
1244 item->url = apr_pstrdup(result_pool, url);
1245 item->session_relpath = svn_uri_skip_ancestor(session_url, item->url,
1248 if (op->src_relpath)
1250 item->copyfrom_url = svn_path_url_add_component2(session_url,
1253 item->copyfrom_rev = op->src_rev;
1254 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1257 item->copyfrom_rev = SVN_INVALID_REVNUM;
1259 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
1260 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1261 else if (op->kind == OP_DELETE)
1262 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1263 /* else item->state_flags = 0; */
1265 if (op->prop_mods && op->prop_mods->nelts)
1266 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1269 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
1271 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1274 if (op->children && op->children->nelts)
1277 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1279 for (i = 0; i < op->children->nelts; i++)
1282 const char * child_url;
1284 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1286 svn_pool_clear(iterpool);
1288 child_url = svn_path_url_add_component2(url, cop->name, iterpool);
1290 SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items,
1291 result_pool, iterpool));
1294 svn_pool_destroy(iterpool);
1297 return SVN_NO_ERROR;
1301 svn_client__mtcc_commit(apr_hash_t *revprop_table,
1302 svn_commit_callback2_t commit_callback,
1304 svn_client__mtcc_t *mtcc,
1305 apr_pool_t *scratch_pool)
1307 const svn_delta_editor_t *editor;
1310 apr_hash_t *commit_revprops;
1311 svn_node_kind_t kind;
1313 const char *session_url;
1314 const char *log_msg;
1316 if (MTCC_UNMODIFIED(mtcc))
1318 /* No changes -> no revision. Easy out */
1319 svn_pool_destroy(mtcc->pool);
1320 return SVN_NO_ERROR;
1323 SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool));
1325 if (mtcc->root_op->kind != OP_OPEN_DIR)
1329 svn_uri_split(&session_url, &name, session_url, scratch_pool);
1333 SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool));
1335 SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool));
1339 /* Create new commit items and add them to the array. */
1340 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx))
1342 svn_client_commit_item3_t *item;
1343 const char *tmp_file;
1344 apr_array_header_t *commit_items
1345 = apr_array_make(scratch_pool, 32, sizeof(item));
1347 SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url,
1348 commit_items, scratch_pool, scratch_pool));
1350 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
1351 mtcc->ctx, scratch_pool));
1354 return SVN_NO_ERROR;
1359 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1360 log_msg, mtcc->ctx, scratch_pool));
1362 /* Ugly corner case: The ra session might have died while we were waiting
1365 err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind,
1370 svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session,
1378 svn_pool_destroy(mtcc->pool);
1379 return svn_error_trace(svn_error_compose_create(err, err2));
1381 svn_error_clear(err);
1383 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "",
1384 mtcc->base_revision, &kind, scratch_pool));
1387 if (kind != svn_node_dir)
1388 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1389 _("Can't commit to '%s' because it "
1390 "is not a directory"),
1393 /* Beware that the editor object must not live longer than the MTCC.
1394 Otherwise, txn objects etc. in EDITOR may live longer than their
1395 respective FS objects. So, we can't use SCRATCH_POOL here. */
1396 SVN_ERR(svn_ra_get_commit_editor3(mtcc->ra_session, &editor, &edit_baton,
1398 commit_callback, commit_baton,
1399 NULL /* lock_tokens */,
1400 FALSE /* keep_locks */,
1403 err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton);
1406 err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision,
1407 root_baton, session_url, mtcc->ctx, scratch_pool);
1411 if (mtcc->ctx->notify_func2)
1413 svn_wc_notify_t *notify;
1414 notify = svn_wc_create_notify_url(session_url,
1415 svn_wc_notify_commit_finalizing,
1417 mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify,
1420 SVN_ERR(editor->close_edit(edit_baton, scratch_pool));
1423 err = svn_error_compose_create(err,
1424 editor->abort_edit(edit_baton, scratch_pool));
1426 svn_pool_destroy(mtcc->pool);
1428 return svn_error_trace(err);