1 /*
2 * mtcc.c -- Multi Command Context implementation. This allows
3 * performing many operations without a working copy.
4 *
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
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
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
21 * under the License.
22 * ====================================================================
23 */
24
25 #include "svn_dirent_uri.h"
26 #include "svn_hash.h"
27 #include "svn_path.h"
28 #include "svn_props.h"
29 #include "svn_pools.h"
30 #include "svn_subst.h"
31
32 #include "private/svn_client_mtcc.h"
33
34
35 #include "svn_private_config.h"
36
37 #include "client.h"
38
39 #include <assert.h>
40
41 #define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
42
43 /* The kind of operation to perform in an mtcc_op_t */
44 typedef enum mtcc_kind_t
45 {
46 OP_OPEN_DIR,
47 OP_OPEN_FILE,
48 OP_ADD_DIR,
49 OP_ADD_FILE,
50 OP_DELETE
51 } mtcc_kind_t;
52
53 typedef struct mtcc_op_t
54 {
55 const char *name; /* basename of operation */
56 mtcc_kind_t kind; /* editor operation */
57
58 apr_array_header_t *children; /* List of mtcc_op_t * */
59
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 */
66
67 apr_array_header_t *prop_mods; /* For all except DELETE
68 List of svn_prop_t */
69
70 svn_boolean_t performed_stat; /* Verified kind with repository */
71 } mtcc_op_t;
72
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))
81
82 struct svn_client__mtcc_t
83 {
84 apr_pool_t *pool;
85 svn_revnum_t head_revision;
86 svn_revnum_t base_revision;
87
88 svn_ra_session_t *ra_session;
89 svn_client_ctx_t *ctx;
90
91 mtcc_op_t *root_op;
92 };
93
94 static mtcc_op_t *
mtcc_op_create(const char * name,svn_boolean_t add,svn_boolean_t directory,apr_pool_t * result_pool)95 mtcc_op_create(const char *name,
96 svn_boolean_t add,
97 svn_boolean_t directory,
98 apr_pool_t *result_pool)
99 {
100 mtcc_op_t *op;
101
102 op = apr_pcalloc(result_pool, sizeof(*op));
103 op->name = name ? apr_pstrdup(result_pool, name) : "";
104
105 if (add)
106 op->kind = directory ? OP_ADD_DIR : OP_ADD_FILE;
107 else
108 op->kind = directory ? OP_OPEN_DIR : OP_OPEN_FILE;
109
110 if (directory)
111 op->children = apr_array_make(result_pool, 4, sizeof(mtcc_op_t *));
112
113 op->src_rev = SVN_INVALID_REVNUM;
114
115 return op;
116 }
117
118 static svn_error_t *
mtcc_op_find(mtcc_op_t ** op,svn_boolean_t * created,const char * relpath,mtcc_op_t * base_op,svn_boolean_t find_existing,svn_boolean_t find_deletes,svn_boolean_t create_file,apr_pool_t * result_pool,apr_pool_t * scratch_pool)119 mtcc_op_find(mtcc_op_t **op,
120 svn_boolean_t *created,
121 const char *relpath,
122 mtcc_op_t *base_op,
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)
128 {
129 const char *name;
130 const char *child;
131 int i;
132
133 assert(svn_relpath_is_canonical(relpath));
134 if (created)
135 *created = FALSE;
136
137 if (SVN_PATH_IS_EMPTY(relpath))
138 {
139 if (find_existing)
140 *op = base_op;
141 else
142 *op = NULL;
143
144 return SVN_NO_ERROR;
145 }
146
147 child = strchr(relpath, '/');
148
149 if (child)
150 {
151 name = apr_pstrmemdup(scratch_pool, relpath, (child-relpath));
152 child++; /* Skip '/' */
153 }
154 else
155 name = relpath;
156
157 if (!base_op->children)
158 {
159 if (!created)
160 {
161 *op = NULL;
162 return SVN_NO_ERROR;
163 }
164 else
165 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
166 _("Can't operate on '%s' because '%s' is not a "
167 "directory"),
168 name, base_op->name);
169 }
170
171 for (i = base_op->children->nelts-1; i >= 0 ; i--)
172 {
173 mtcc_op_t *cop;
174
175 cop = APR_ARRAY_IDX(base_op->children, i, mtcc_op_t *);
176
177 if (! strcmp(cop->name, name)
178 && (find_deletes || cop->kind != OP_DELETE))
179 {
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));
184 }
185 }
186
187 if (!created)
188 {
189 *op = NULL;
190 return SVN_NO_ERROR;
191 }
192
193 {
194 mtcc_op_t *cop;
195
196 cop = mtcc_op_create(name, FALSE, child || !create_file, result_pool);
197
198 APR_ARRAY_PUSH(base_op->children, mtcc_op_t *) = cop;
199
200 if (!child)
201 {
202 *op = cop;
203 *created = TRUE;
204 return SVN_NO_ERROR;
205 }
206
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));
211 }
212 }
213
214 /* Gets the original repository location of RELPATH, checking things
215 like copies, moves, etc. */
216 static svn_error_t *
get_origin(svn_boolean_t * done,const char ** origin_relpath,svn_revnum_t * rev,mtcc_op_t * op,const char * relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)217 get_origin(svn_boolean_t *done,
218 const char **origin_relpath,
219 svn_revnum_t *rev,
220 mtcc_op_t *op,
221 const char *relpath,
222 apr_pool_t *result_pool,
223 apr_pool_t *scratch_pool)
224 {
225 const char *child;
226 const char *name;
227 if (SVN_PATH_IS_EMPTY(relpath))
228 {
229 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
230 *done = TRUE;
231 *origin_relpath = op->src_relpath
232 ? apr_pstrdup(result_pool, op->src_relpath)
233 : NULL;
234 *rev = op->src_rev;
235 return SVN_NO_ERROR;
236 }
237
238 child = strchr(relpath, '/');
239 if (child)
240 {
241 name = apr_pstrmemdup(scratch_pool, relpath, child-relpath);
242 child++; /* Skip '/' */
243 }
244 else
245 name = relpath;
246
247 if (op->children && op->children->nelts)
248 {
249 int i;
250
251 for (i = op->children->nelts-1; i >= 0; i--)
252 {
253 mtcc_op_t *cop;
254
255 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
256
257 if (! strcmp(cop->name, name))
258 {
259 if (cop->kind == OP_DELETE)
260 {
261 *done = TRUE;
262 return SVN_NO_ERROR;
263 }
264
265 SVN_ERR(get_origin(done, origin_relpath, rev,
266 cop, child ? child : "",
267 result_pool, scratch_pool));
268
269 if (*origin_relpath || *done)
270 return SVN_NO_ERROR;
271
272 break;
273 }
274 }
275 }
276
277 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
278 {
279 *done = TRUE;
280 if (op->src_relpath)
281 {
282 *origin_relpath = svn_relpath_join(op->src_relpath, relpath,
283 result_pool);
284 *rev = op->src_rev;
285 }
286 }
287
288 return SVN_NO_ERROR;
289 }
290
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 */
294 static svn_error_t *
mtcc_get_origin(const char ** origin_relpath,svn_revnum_t * rev,const char * relpath,svn_boolean_t ignore_enoent,svn_client__mtcc_t * mtcc,apr_pool_t * result_pool,apr_pool_t * scratch_pool)295 mtcc_get_origin(const char **origin_relpath,
296 svn_revnum_t *rev,
297 const char *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)
302 {
303 svn_boolean_t done = FALSE;
304
305 *origin_relpath = NULL;
306 *rev = SVN_INVALID_REVNUM;
307
308 SVN_ERR(get_origin(&done, origin_relpath, rev, mtcc->root_op, relpath,
309 result_pool, scratch_pool));
310
311 if (!*origin_relpath && !done)
312 {
313 *origin_relpath = apr_pstrdup(result_pool, relpath);
314 *rev = mtcc->base_revision;
315 }
316 else if (!ignore_enoent)
317 {
318 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
319 _("No origin found for node at '%s'"),
320 relpath);
321 }
322
323 return SVN_NO_ERROR;
324 }
325
326 svn_error_t *
svn_client__mtcc_create(svn_client__mtcc_t ** mtcc,const char * anchor_url,svn_revnum_t base_revision,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
333 {
334 apr_pool_t *mtcc_pool;
335
336 mtcc_pool = svn_pool_create(result_pool);
337
338 *mtcc = apr_pcalloc(mtcc_pool, sizeof(**mtcc));
339 (*mtcc)->pool = mtcc_pool;
340
341 (*mtcc)->root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc_pool);
342
343 (*mtcc)->ctx = ctx;
344
345 SVN_ERR(svn_client_open_ra_session2(&(*mtcc)->ra_session, anchor_url,
346 NULL /* wri_abspath */, ctx,
347 mtcc_pool, scratch_pool));
348
349 SVN_ERR(svn_ra_get_latest_revnum((*mtcc)->ra_session, &(*mtcc)->head_revision,
350 scratch_pool));
351
352 if (SVN_IS_VALID_REVNUM(base_revision))
353 (*mtcc)->base_revision = base_revision;
354 else
355 (*mtcc)->base_revision = (*mtcc)->head_revision;
356
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);
361
362 return SVN_NO_ERROR;
363 }
364
365 static svn_error_t *
update_copy_src(mtcc_op_t * op,const char * add_relpath,apr_pool_t * result_pool)366 update_copy_src(mtcc_op_t *op,
367 const char *add_relpath,
368 apr_pool_t *result_pool)
369 {
370 int i;
371
372 if (op->src_relpath)
373 op->src_relpath = svn_relpath_join(add_relpath, op->src_relpath,
374 result_pool);
375
376 if (!op->children)
377 return SVN_NO_ERROR;
378
379 for (i = 0; i < op->children->nelts; i++)
380 {
381 mtcc_op_t *cop;
382
383 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
384
385 SVN_ERR(update_copy_src(cop, add_relpath, result_pool));
386 }
387
388 return SVN_NO_ERROR;
389 }
390
391 static svn_error_t *
mtcc_reparent(const char * new_anchor_url,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)392 mtcc_reparent(const char *new_anchor_url,
393 svn_client__mtcc_t *mtcc,
394 apr_pool_t *scratch_pool)
395 {
396 const char *session_url;
397 const char *up;
398
399 SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url,
400 scratch_pool));
401
402 up = svn_uri_skip_ancestor(new_anchor_url, session_url, scratch_pool);
403
404 if (! up)
405 {
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);
409 }
410 else if (!*up)
411 {
412 return SVN_NO_ERROR; /* Same url */
413 }
414
415 /* Update copy origins recursively...:( */
416 SVN_ERR(update_copy_src(mtcc->root_op, up, mtcc->pool));
417
418 SVN_ERR(svn_ra_reparent(mtcc->ra_session, new_anchor_url, scratch_pool));
419
420 /* Create directory open operations for new ancestors */
421 while (*up)
422 {
423 mtcc_op_t *root_op;
424
425 mtcc->root_op->name = svn_relpath_basename(up, mtcc->pool);
426 up = svn_relpath_dirname(up, scratch_pool);
427
428 root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc->pool);
429
430 APR_ARRAY_PUSH(root_op->children, mtcc_op_t *) = mtcc->root_op;
431
432 mtcc->root_op = root_op;
433 }
434
435 return SVN_NO_ERROR;
436 }
437
438 /* Check if it is safe to create a new node at NEW_RELPATH. Return a proper
439 error if it is not */
440 static svn_error_t *
mtcc_verify_create(svn_client__mtcc_t * mtcc,const char * new_relpath,apr_pool_t * scratch_pool)441 mtcc_verify_create(svn_client__mtcc_t *mtcc,
442 const char *new_relpath,
443 apr_pool_t *scratch_pool)
444 {
445 svn_node_kind_t kind;
446
447 if (*new_relpath || !MTCC_UNMODIFIED(mtcc))
448 {
449 mtcc_op_t *op;
450
451 SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, FALSE,
452 FALSE, mtcc->pool, scratch_pool));
453
454 if (op)
455 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
456 _("Path '%s' already exists"),
457 new_relpath);
458
459 SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE,
460 FALSE, mtcc->pool, scratch_pool));
461
462 if (op)
463 return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */
464 }
465
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));
470
471 if (kind != svn_node_none)
472 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
473 _("Path '%s' already exists"),
474 new_relpath);
475
476 return SVN_NO_ERROR;
477 }
478
479
480 svn_error_t *
svn_client__mtcc_add_add_file(const char * relpath,svn_stream_t * src_stream,const svn_checksum_t * src_checksum,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)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)
486 {
487 mtcc_op_t *op;
488 svn_boolean_t created;
489 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
490
491 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
492
493 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
494 {
495 /* Turn the root operation into a file addition */
496 op = mtcc->root_op;
497 }
498 else
499 {
500 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
501 TRUE, mtcc->pool, scratch_pool));
502
503 if (!op || !created)
504 {
505 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
506 _("Can't add file at '%s'"),
507 relpath);
508 }
509 }
510
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)
514 : NULL;
515
516 return SVN_NO_ERROR;
517 }
518
519 svn_error_t *
svn_client__mtcc_add_copy(const char * src_relpath,svn_revnum_t revision,const char * dst_relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_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)
525 {
526 mtcc_op_t *op;
527 svn_boolean_t created;
528 svn_node_kind_t kind;
529
530 SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath)
531 && svn_relpath_is_canonical(dst_relpath));
532
533 if (! SVN_IS_VALID_REVNUM(revision))
534 revision = mtcc->head_revision;
535 else if (revision > mtcc->head_revision)
536 {
537 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
538 _("No such revision %ld"), revision);
539 }
540
541 SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool));
542
543 /* Subversion requires the kind of a copy */
544 SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind,
545 scratch_pool));
546
547 if (kind != svn_node_dir && kind != svn_node_file)
548 {
549 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
550 _("Path '%s' not found in revision %ld"),
551 src_relpath, revision);
552 }
553
554 SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE,
555 (kind == svn_node_file), mtcc->pool, scratch_pool));
556
557 if (!op || !created)
558 {
559 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
560 _("Can't add node at '%s'"),
561 dst_relpath);
562 }
563
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;
567
568 return SVN_NO_ERROR;
569 }
570
571 /* Check if this operation contains at least one change that is not a
572 plain delete */
573 static svn_boolean_t
mtcc_op_contains_non_delete(const mtcc_op_t * op)574 mtcc_op_contains_non_delete(const mtcc_op_t *op)
575 {
576 if (op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE
577 && op->kind != OP_DELETE)
578 {
579 return TRUE;
580 }
581
582 if (op->prop_mods && op->prop_mods->nelts)
583 return TRUE;
584
585 if (op->src_stream)
586 return TRUE;
587
588 if (op->children)
589 {
590 int i;
591
592 for (i = 0; i < op->children->nelts; i++)
593 {
594 const mtcc_op_t *c_op = APR_ARRAY_IDX(op->children, i,
595 const mtcc_op_t *);
596
597 if (mtcc_op_contains_non_delete(c_op))
598 return TRUE;
599 }
600 }
601 return FALSE;
602 }
603
604 static svn_error_t *
mtcc_add_delete(const char * relpath,svn_boolean_t for_move,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)605 mtcc_add_delete(const char *relpath,
606 svn_boolean_t for_move,
607 svn_client__mtcc_t *mtcc,
608 apr_pool_t *scratch_pool)
609 {
610 mtcc_op_t *op;
611 svn_boolean_t created;
612 svn_node_kind_t kind;
613
614 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
615
616 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
617 mtcc, scratch_pool));
618
619 if (kind == svn_node_none)
620 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
621 _("Can't delete node at '%s' as it "
622 "does not exist"),
623 relpath);
624
625 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
626 {
627 /* Turn root operation into delete */
628 op = mtcc->root_op;
629 }
630 else
631 {
632 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE,
633 TRUE, mtcc->pool, scratch_pool));
634
635 if (!for_move && !op && !created)
636 {
637 /* Allow deleting directories, that are unmodified except for
638 one or more deleted descendants */
639
640 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE,
641 FALSE, FALSE, mtcc->pool, scratch_pool));
642
643 if (op && mtcc_op_contains_non_delete(op))
644 op = NULL;
645 else
646 created = TRUE;
647 }
648
649 if (!op || !created)
650 {
651 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
652 _("Can't delete node at '%s'"),
653 relpath);
654 }
655 }
656
657 op->kind = OP_DELETE;
658 op->children = NULL;
659 op->prop_mods = NULL;
660
661 return SVN_NO_ERROR;
662 }
663
664 svn_error_t *
svn_client__mtcc_add_delete(const char * relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)665 svn_client__mtcc_add_delete(const char *relpath,
666 svn_client__mtcc_t *mtcc,
667 apr_pool_t *scratch_pool)
668 {
669 return svn_error_trace(mtcc_add_delete(relpath, FALSE, mtcc, scratch_pool));
670 }
671
672 svn_error_t *
svn_client__mtcc_add_mkdir(const char * relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)673 svn_client__mtcc_add_mkdir(const char *relpath,
674 svn_client__mtcc_t *mtcc,
675 apr_pool_t *scratch_pool)
676 {
677 mtcc_op_t *op;
678 svn_boolean_t created;
679 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
680
681 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
682
683 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
684 {
685 /* Turn the root of the operation in an MKDIR */
686 mtcc->root_op->kind = OP_ADD_DIR;
687
688 return SVN_NO_ERROR;
689 }
690
691 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
692 FALSE, mtcc->pool, scratch_pool));
693
694 if (!op || !created)
695 {
696 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
697 _("Can't create directory at '%s'"),
698 relpath);
699 }
700
701 op->kind = OP_ADD_DIR;
702
703 return SVN_NO_ERROR;
704 }
705
706 svn_error_t *
svn_client__mtcc_add_move(const char * src_relpath,const char * dst_relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)707 svn_client__mtcc_add_move(const char *src_relpath,
708 const char *dst_relpath,
709 svn_client__mtcc_t *mtcc,
710 apr_pool_t *scratch_pool)
711 {
712 const char *origin_relpath;
713 svn_revnum_t origin_rev;
714
715 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
716 src_relpath, FALSE, mtcc,
717 scratch_pool, scratch_pool));
718
719 SVN_ERR(svn_client__mtcc_add_copy(src_relpath, mtcc->base_revision,
720 dst_relpath, mtcc, scratch_pool));
721 SVN_ERR(mtcc_add_delete(src_relpath, TRUE, mtcc, scratch_pool));
722
723 return SVN_NO_ERROR;
724 }
725
726 /* Baton for mtcc_prop_getter */
727 struct mtcc_prop_get_baton
728 {
729 svn_client__mtcc_t *mtcc;
730 const char *relpath;
731 svn_cancel_func_t cancel_func;
732 void *cancel_baton;
733 };
734
735 /* Implements svn_wc_canonicalize_svn_prop_get_file_t */
736 static svn_error_t *
mtcc_prop_getter(const svn_string_t ** mime_type,svn_stream_t * stream,void * baton,apr_pool_t * pool)737 mtcc_prop_getter(const svn_string_t **mime_type,
738 svn_stream_t *stream,
739 void *baton,
740 apr_pool_t *pool)
741 {
742 struct mtcc_prop_get_baton *mpgb = baton;
743 const char *origin_relpath;
744 svn_revnum_t origin_rev;
745 apr_hash_t *props = NULL;
746
747 mtcc_op_t *op;
748
749 if (mime_type)
750 *mime_type = NULL;
751
752 /* Check if we have the information locally */
753 SVN_ERR(mtcc_op_find(&op, NULL, mpgb->relpath, mpgb->mtcc->root_op, TRUE,
754 FALSE, FALSE, pool, pool));
755
756 if (op)
757 {
758 if (mime_type)
759 {
760 int i;
761
762 for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++)
763 {
764 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i,
765 svn_prop_t);
766
767 if (! strcmp(mod->name, SVN_PROP_MIME_TYPE))
768 {
769 *mime_type = svn_string_dup(mod->value, pool);
770 mime_type = NULL;
771 break;
772 }
773 }
774 }
775
776 if (stream && op->src_stream)
777 {
778 svn_stream_mark_t *mark;
779 svn_error_t *err;
780
781 /* Is the source stream capable of being read multiple times? */
782 err = svn_stream_mark(op->src_stream, &mark, pool);
783
784 if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED)
785 return svn_error_trace(err);
786 svn_error_clear(err);
787
788 if (!err)
789 {
790 err = svn_stream_copy3(svn_stream_disown(op->src_stream, pool),
791 svn_stream_disown(stream, pool),
792 mpgb->cancel_func, mpgb->cancel_baton,
793 pool);
794
795 SVN_ERR(svn_error_compose_create(
796 err,
797 svn_stream_seek(op->src_stream, mark)));
798 }
799 /* else: ### Create tempfile? */
800
801 stream = NULL; /* Stream is handled */
802 }
803 }
804
805 if (!stream && !mime_type)
806 return SVN_NO_ERROR;
807
808 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE,
809 mpgb->mtcc, pool, pool));
810
811 if (!origin_relpath)
812 return SVN_NO_ERROR; /* Nothing to fetch at repository */
813
814 SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev,
815 stream, NULL, mime_type ? &props : NULL, pool));
816
817 if (mime_type && props)
818 *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
819
820 return SVN_NO_ERROR;
821 }
822
823 svn_error_t *
svn_client__mtcc_add_propset(const char * relpath,const char * propname,const svn_string_t * propval,svn_boolean_t skip_checks,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)824 svn_client__mtcc_add_propset(const char *relpath,
825 const char *propname,
826 const svn_string_t *propval,
827 svn_boolean_t skip_checks,
828 svn_client__mtcc_t *mtcc,
829 apr_pool_t *scratch_pool)
830 {
831 mtcc_op_t *op;
832 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
833
834 if (! svn_prop_name_is_valid(propname))
835 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
836 _("Bad property name: '%s'"), propname);
837
838 if (svn_prop_is_known_svn_rev_prop(propname))
839 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
840 _("Revision property '%s' not allowed "
841 "in this context"), propname);
842
843 if (svn_property_kind2(propname) == svn_prop_wc_kind)
844 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
845 _("'%s' is a wcprop, thus not accessible "
846 "to clients"), propname);
847
848 if (!skip_checks && svn_prop_needs_translation(propname))
849 {
850 svn_string_t *translated_value;
851 SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
852 NULL, propval,
853 NULL, FALSE,
854 scratch_pool, scratch_pool),
855 _("Error normalizing property value"));
856
857 propval = translated_value;
858 }
859
860 if (propval && svn_prop_is_svn_prop(propname))
861 {
862 struct mtcc_prop_get_baton mpbg;
863 svn_node_kind_t kind;
864 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, mtcc,
865 scratch_pool));
866
867 mpbg.mtcc = mtcc;
868 mpbg.relpath = relpath;
869 mpbg.cancel_func = mtcc->ctx->cancel_func;
870 mpbg.cancel_baton = mtcc->ctx->cancel_baton;
871
872 SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval,
873 relpath, kind, skip_checks,
874 mtcc_prop_getter, &mpbg,
875 scratch_pool));
876 }
877
878 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
879 {
880 svn_node_kind_t kind;
881
882 /* Probing the node for an unmodified root will fix the node type to
883 a file if necessary */
884
885 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
886 mtcc, scratch_pool));
887
888 if (kind == svn_node_none)
889 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
890 _("Can't set properties at not existing '%s'"),
891 relpath);
892
893 op = mtcc->root_op;
894 }
895 else
896 {
897 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
898 FALSE, mtcc->pool, scratch_pool));
899
900 if (!op)
901 {
902 svn_node_kind_t kind;
903 svn_boolean_t created;
904
905 /* ### TODO: Check if this node is within a newly copied directory,
906 and update origin values accordingly */
907
908 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
909 mtcc, scratch_pool));
910
911 if (kind == svn_node_none)
912 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
913 _("Can't set properties at not existing '%s'"),
914 relpath);
915
916 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
917 (kind != svn_node_dir),
918 mtcc->pool, scratch_pool));
919
920 SVN_ERR_ASSERT(op != NULL);
921 }
922 }
923
924 if (!op->prop_mods)
925 op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t));
926
927 {
928 svn_prop_t propchange;
929 propchange.name = apr_pstrdup(mtcc->pool, propname);
930
931 if (propval)
932 propchange.value = svn_string_dup(propval, mtcc->pool);
933 else
934 propchange.value = NULL;
935
936 APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange;
937 }
938
939 return SVN_NO_ERROR;
940 }
941
942 svn_error_t *
svn_client__mtcc_add_update_file(const char * relpath,svn_stream_t * src_stream,const svn_checksum_t * src_checksum,svn_stream_t * base_stream,const svn_checksum_t * base_checksum,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)943 svn_client__mtcc_add_update_file(const char *relpath,
944 svn_stream_t *src_stream,
945 const svn_checksum_t *src_checksum,
946 svn_stream_t *base_stream,
947 const svn_checksum_t *base_checksum,
948 svn_client__mtcc_t *mtcc,
949 apr_pool_t *scratch_pool)
950 {
951 mtcc_op_t *op;
952 svn_boolean_t created;
953 svn_node_kind_t kind;
954 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
955
956 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
957 mtcc, scratch_pool));
958
959 if (kind != svn_node_file)
960 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
961 _("Can't update '%s' because it is not a file"),
962 relpath);
963
964 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
965 TRUE, mtcc->pool, scratch_pool));
966
967 if (!op
968 || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE)
969 || (op->src_stream != NULL))
970 {
971 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
972 _("Can't update file at '%s'"), relpath);
973 }
974
975 op->src_stream = src_stream;
976 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
977 : NULL;
978
979 op->base_stream = base_stream;
980 op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum,
981 mtcc->pool)
982 : NULL;
983
984 return SVN_NO_ERROR;
985 }
986
987 svn_error_t *
svn_client__mtcc_check_path(svn_node_kind_t * kind,const char * relpath,svn_boolean_t check_repository,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)988 svn_client__mtcc_check_path(svn_node_kind_t *kind,
989 const char *relpath,
990 svn_boolean_t check_repository,
991 svn_client__mtcc_t *mtcc,
992 apr_pool_t *scratch_pool)
993 {
994 const char *origin_relpath;
995 svn_revnum_t origin_rev;
996 mtcc_op_t *op;
997
998 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
999
1000 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)
1001 && !mtcc->root_op->performed_stat)
1002 {
1003 /* We know nothing about the root. Perhaps it is a file? */
1004 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision,
1005 kind, scratch_pool));
1006
1007 mtcc->root_op->performed_stat = TRUE;
1008 if (*kind == svn_node_file)
1009 {
1010 mtcc->root_op->kind = OP_OPEN_FILE;
1011 mtcc->root_op->children = NULL;
1012 }
1013 return SVN_NO_ERROR;
1014 }
1015
1016 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
1017 FALSE, mtcc->pool, scratch_pool));
1018
1019 if (!op || (check_repository && !op->performed_stat))
1020 {
1021 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
1022 relpath, TRUE, mtcc,
1023 scratch_pool, scratch_pool));
1024
1025 if (!origin_relpath)
1026 *kind = svn_node_none;
1027 else
1028 SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath,
1029 origin_rev, kind, scratch_pool));
1030
1031 if (op && *kind == svn_node_dir)
1032 {
1033 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1034 op->performed_stat = TRUE;
1035 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1036 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
1037 _("Can't perform file operation "
1038 "on '%s' as it is not a file"),
1039 relpath);
1040 }
1041 else if (op && *kind == svn_node_file)
1042 {
1043 if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1044 op->performed_stat = TRUE;
1045 else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1046 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1047 _("Can't perform directory operation "
1048 "on '%s' as it is not a directory"),
1049 relpath);
1050 }
1051 else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE))
1052 {
1053 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1054 _("Can't open '%s' as it does not exist"),
1055 relpath);
1056 }
1057
1058 return SVN_NO_ERROR;
1059 }
1060
1061 /* op != NULL */
1062 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1063 {
1064 *kind = svn_node_dir;
1065 return SVN_NO_ERROR;
1066 }
1067 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1068 {
1069 *kind = svn_node_file;
1070 return SVN_NO_ERROR;
1071 }
1072 SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */
1073 }
1074
1075 static svn_error_t *
commit_properties(const svn_delta_editor_t * editor,const mtcc_op_t * op,void * node_baton,apr_pool_t * scratch_pool)1076 commit_properties(const svn_delta_editor_t *editor,
1077 const mtcc_op_t *op,
1078 void *node_baton,
1079 apr_pool_t *scratch_pool)
1080 {
1081 int i;
1082 apr_pool_t *iterpool;
1083
1084 if (!op->prop_mods || op->prop_mods->nelts == 0)
1085 return SVN_NO_ERROR;
1086
1087 iterpool = svn_pool_create(scratch_pool);
1088 for (i = 0; i < op->prop_mods->nelts; i++)
1089 {
1090 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t);
1091
1092 svn_pool_clear(iterpool);
1093
1094 if (op->kind == OP_ADD_DIR || op->kind == OP_OPEN_DIR)
1095 SVN_ERR(editor->change_dir_prop(node_baton, mod->name, mod->value,
1096 iterpool));
1097 else if (op->kind == OP_ADD_FILE || op->kind == OP_OPEN_FILE)
1098 SVN_ERR(editor->change_file_prop(node_baton, mod->name, mod->value,
1099 iterpool));
1100 }
1101
1102 svn_pool_destroy(iterpool);
1103 return SVN_NO_ERROR;
1104 }
1105
1106 /* Handles updating a file to a delta editor and then closes it */
1107 static svn_error_t *
commit_file(const svn_delta_editor_t * editor,mtcc_op_t * op,void * file_baton,const char * session_url,const char * relpath,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1108 commit_file(const svn_delta_editor_t *editor,
1109 mtcc_op_t *op,
1110 void *file_baton,
1111 const char *session_url,
1112 const char *relpath,
1113 svn_client_ctx_t *ctx,
1114 apr_pool_t *scratch_pool)
1115 {
1116 const char *text_checksum = NULL;
1117 svn_checksum_t *src_checksum = op->src_checksum;
1118 SVN_ERR(commit_properties(editor, op, file_baton, scratch_pool));
1119
1120 if (op->src_stream)
1121 {
1122 const char *base_checksum = NULL;
1123 apr_pool_t *txdelta_pool = scratch_pool;
1124 svn_txdelta_window_handler_t window_handler;
1125 void *handler_baton;
1126 svn_stream_t *src_stream = op->src_stream;
1127
1128 if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5)
1129 base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool);
1130
1131 /* ### TODO: Future enhancement: Allocate in special pool and send
1132 files after the true edit operation, like a wc commit */
1133 SVN_ERR(editor->apply_textdelta(file_baton, base_checksum, txdelta_pool,
1134 &window_handler, &handler_baton));
1135
1136 if (ctx->notify_func2)
1137 {
1138 svn_wc_notify_t *notify;
1139
1140 notify = svn_wc_create_notify_url(
1141 svn_path_url_add_component2(session_url, relpath,
1142 scratch_pool),
1143 svn_wc_notify_commit_postfix_txdelta,
1144 scratch_pool);
1145
1146 notify->path = relpath;
1147 notify->kind = svn_node_file;
1148
1149 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
1150 }
1151
1152 if (window_handler != svn_delta_noop_window_handler)
1153 {
1154 if (!src_checksum || src_checksum->kind != svn_checksum_md5)
1155 src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL,
1156 svn_checksum_md5,
1157 TRUE, scratch_pool);
1158
1159 if (!op->base_stream)
1160 SVN_ERR(svn_txdelta_send_stream(src_stream,
1161 window_handler, handler_baton, NULL,
1162 scratch_pool));
1163 else
1164 SVN_ERR(svn_txdelta_run(op->base_stream, src_stream,
1165 window_handler, handler_baton,
1166 svn_checksum_md5, NULL,
1167 ctx->cancel_func, ctx->cancel_baton,
1168 scratch_pool, scratch_pool));
1169 }
1170
1171 SVN_ERR(svn_stream_close(src_stream));
1172 if (op->base_stream)
1173 SVN_ERR(svn_stream_close(op->base_stream));
1174 }
1175
1176 if (src_checksum && src_checksum->kind == svn_checksum_md5)
1177 text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool);
1178
1179 return svn_error_trace(editor->close_file(file_baton, text_checksum,
1180 scratch_pool));
1181 }
1182
1183 /* Handles updating a directory to a delta editor and then closes it */
1184 static svn_error_t *
commit_directory(const svn_delta_editor_t * editor,mtcc_op_t * op,const char * relpath,svn_revnum_t base_rev,void * dir_baton,const char * session_url,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1185 commit_directory(const svn_delta_editor_t *editor,
1186 mtcc_op_t *op,
1187 const char *relpath,
1188 svn_revnum_t base_rev,
1189 void *dir_baton,
1190 const char *session_url,
1191 svn_client_ctx_t *ctx,
1192 apr_pool_t *scratch_pool)
1193 {
1194 SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool));
1195
1196 if (op->children && op->children->nelts > 0)
1197 {
1198 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1199 int i;
1200
1201 for (i = 0; i < op->children->nelts; i++)
1202 {
1203 mtcc_op_t *cop;
1204 const char * child_relpath;
1205 void *child_baton;
1206
1207 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1208
1209 svn_pool_clear(iterpool);
1210
1211 if (ctx->cancel_func)
1212 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1213
1214 child_relpath = svn_relpath_join(relpath, cop->name, iterpool);
1215
1216 switch (cop->kind)
1217 {
1218 case OP_DELETE:
1219 SVN_ERR(editor->delete_entry(child_relpath, base_rev,
1220 dir_baton, iterpool));
1221 break;
1222
1223 case OP_ADD_DIR:
1224 SVN_ERR(editor->add_directory(child_relpath, dir_baton,
1225 cop->src_relpath
1226 ? svn_path_url_add_component2(
1227 session_url,
1228 cop->src_relpath,
1229 iterpool)
1230 : NULL,
1231 cop->src_rev,
1232 iterpool, &child_baton));
1233 SVN_ERR(commit_directory(editor, cop, child_relpath,
1234 SVN_INVALID_REVNUM, child_baton,
1235 session_url, ctx, iterpool));
1236 break;
1237 case OP_OPEN_DIR:
1238 SVN_ERR(editor->open_directory(child_relpath, dir_baton,
1239 base_rev, iterpool, &child_baton));
1240 SVN_ERR(commit_directory(editor, cop, child_relpath,
1241 base_rev, child_baton,
1242 session_url, ctx, iterpool));
1243 break;
1244
1245 case OP_ADD_FILE:
1246 SVN_ERR(editor->add_file(child_relpath, dir_baton,
1247 cop->src_relpath
1248 ? svn_path_url_add_component2(
1249 session_url,
1250 cop->src_relpath,
1251 iterpool)
1252 : NULL,
1253 cop->src_rev,
1254 iterpool, &child_baton));
1255 SVN_ERR(commit_file(editor, cop, child_baton,
1256 session_url, child_relpath, ctx, iterpool));
1257 break;
1258 case OP_OPEN_FILE:
1259 SVN_ERR(editor->open_file(child_relpath, dir_baton, base_rev,
1260 iterpool, &child_baton));
1261 SVN_ERR(commit_file(editor, cop, child_baton,
1262 session_url, child_relpath, ctx, iterpool));
1263 break;
1264
1265 default:
1266 SVN_ERR_MALFUNCTION();
1267 }
1268 }
1269 }
1270
1271 return svn_error_trace(editor->close_directory(dir_baton, scratch_pool));
1272 }
1273
1274
1275 /* Helper function to recursively create svn_client_commit_item3_t items
1276 to provide to the log message callback */
1277 static svn_error_t *
add_commit_items(mtcc_op_t * op,const char * session_url,const char * url,apr_array_header_t * commit_items,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1278 add_commit_items(mtcc_op_t *op,
1279 const char *session_url,
1280 const char *url,
1281 apr_array_header_t *commit_items,
1282 apr_pool_t *result_pool,
1283 apr_pool_t *scratch_pool)
1284 {
1285 if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE)
1286 || (op->prop_mods && op->prop_mods->nelts)
1287 || (op->src_stream))
1288 {
1289 svn_client_commit_item3_t *item;
1290
1291 item = svn_client_commit_item3_create(result_pool);
1292
1293 item->path = NULL;
1294 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1295 item->kind = svn_node_dir;
1296 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1297 item->kind = svn_node_file;
1298 else
1299 item->kind = svn_node_unknown;
1300
1301 item->url = apr_pstrdup(result_pool, url);
1302 item->session_relpath = svn_uri_skip_ancestor(session_url, item->url,
1303 result_pool);
1304
1305 if (op->src_relpath)
1306 {
1307 item->copyfrom_url = svn_path_url_add_component2(session_url,
1308 op->src_relpath,
1309 result_pool);
1310 item->copyfrom_rev = op->src_rev;
1311 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1312 }
1313 else
1314 item->copyfrom_rev = SVN_INVALID_REVNUM;
1315
1316 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
1317 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1318 else if (op->kind == OP_DELETE)
1319 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1320 /* else item->state_flags = 0; */
1321
1322 if (op->prop_mods && op->prop_mods->nelts)
1323 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1324
1325 if (op->src_stream)
1326 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
1327
1328 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1329 }
1330
1331 if (op->children && op->children->nelts)
1332 {
1333 int i;
1334 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1335
1336 for (i = 0; i < op->children->nelts; i++)
1337 {
1338 mtcc_op_t *cop;
1339 const char * child_url;
1340
1341 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1342
1343 svn_pool_clear(iterpool);
1344
1345 child_url = svn_path_url_add_component2(url, cop->name, iterpool);
1346
1347 SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items,
1348 result_pool, iterpool));
1349 }
1350
1351 svn_pool_destroy(iterpool);
1352 }
1353
1354 return SVN_NO_ERROR;
1355 }
1356
1357 svn_error_t *
svn_client__mtcc_commit(apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)1358 svn_client__mtcc_commit(apr_hash_t *revprop_table,
1359 svn_commit_callback2_t commit_callback,
1360 void *commit_baton,
1361 svn_client__mtcc_t *mtcc,
1362 apr_pool_t *scratch_pool)
1363 {
1364 const svn_delta_editor_t *editor;
1365 void *edit_baton;
1366 void *root_baton;
1367 apr_hash_t *commit_revprops;
1368 svn_node_kind_t kind;
1369 svn_error_t *err;
1370 const char *session_url;
1371 const char *log_msg;
1372
1373 if (MTCC_UNMODIFIED(mtcc))
1374 {
1375 /* No changes -> no revision. Easy out */
1376 svn_pool_destroy(mtcc->pool);
1377 return SVN_NO_ERROR;
1378 }
1379
1380 SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool));
1381
1382 if (mtcc->root_op->kind != OP_OPEN_DIR)
1383 {
1384 const char *name;
1385
1386 svn_uri_split(&session_url, &name, session_url, scratch_pool);
1387
1388 if (*name)
1389 {
1390 SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool));
1391
1392 SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool));
1393 }
1394 }
1395
1396 /* Create new commit items and add them to the array. */
1397 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx))
1398 {
1399 svn_client_commit_item3_t *item;
1400 const char *tmp_file;
1401 apr_array_header_t *commit_items
1402 = apr_array_make(scratch_pool, 32, sizeof(item));
1403
1404 SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url,
1405 commit_items, scratch_pool, scratch_pool));
1406
1407 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
1408 mtcc->ctx, scratch_pool));
1409
1410 if (! log_msg)
1411 return SVN_NO_ERROR;
1412 }
1413 else
1414 log_msg = "";
1415
1416 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1417 log_msg, mtcc->ctx, scratch_pool));
1418
1419 /* Ugly corner case: The ra session might have died while we were waiting
1420 for the callback */
1421
1422 err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind,
1423 scratch_pool);
1424
1425 if (err)
1426 {
1427 svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session,
1428 session_url,
1429 NULL, mtcc->ctx,
1430 mtcc->pool,
1431 scratch_pool);
1432
1433 if (err2)
1434 {
1435 svn_pool_destroy(mtcc->pool);
1436 return svn_error_trace(svn_error_compose_create(err, err2));
1437 }
1438 svn_error_clear(err);
1439
1440 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "",
1441 mtcc->base_revision, &kind, scratch_pool));
1442 }
1443
1444 if (kind != svn_node_dir)
1445 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1446 _("Can't commit to '%s' because it "
1447 "is not a directory"),
1448 session_url);
1449
1450 /* Beware that the editor object must not live longer than the MTCC.
1451 Otherwise, txn objects etc. in EDITOR may live longer than their
1452 respective FS objects. So, we can't use SCRATCH_POOL here. */
1453 SVN_ERR(svn_ra_get_commit_editor3(mtcc->ra_session, &editor, &edit_baton,
1454 commit_revprops,
1455 commit_callback, commit_baton,
1456 NULL /* lock_tokens */,
1457 FALSE /* keep_locks */,
1458 mtcc->pool));
1459
1460 err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton);
1461
1462 if (!err)
1463 err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision,
1464 root_baton, session_url, mtcc->ctx, scratch_pool);
1465
1466 if (!err)
1467 {
1468 if (mtcc->ctx->notify_func2)
1469 {
1470 svn_wc_notify_t *notify;
1471 notify = svn_wc_create_notify_url(session_url,
1472 svn_wc_notify_commit_finalizing,
1473 scratch_pool);
1474 mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify,
1475 scratch_pool);
1476 }
1477 SVN_ERR(editor->close_edit(edit_baton, scratch_pool));
1478 }
1479 else
1480 err = svn_error_compose_create(err,
1481 editor->abort_edit(edit_baton, scratch_pool));
1482
1483 svn_pool_destroy(mtcc->pool);
1484
1485 return svn_error_trace(err);
1486 }
1487