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_DIRECTORY, NULL,
980
_("Can't perform directory operation "
981
"on '%s' as it is not a directory"),
984
else if (op && *kind == svn_node_dir)
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_FILE, NULL,
990
_("Can't perform file operation "
991
"on '%s' as it is not a file"),
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);