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, or was created "
457 "by an earlier operation"),
458 new_relpath);
459
460 SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE,
461 FALSE, mtcc->pool, scratch_pool));
462
463 if (op)
464 return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */
465 }
466
467 /* mod_dav_svn used to allow overwriting existing directories. Let's hide
468 that for users of this api */
469 SVN_ERR(svn_client__mtcc_check_path(&kind, new_relpath, FALSE,
470 mtcc, scratch_pool));
471
472 if (kind != svn_node_none)
473 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
474 _("Path '%s' already exists"),
475 new_relpath);
476
477 return SVN_NO_ERROR;
478 }
479
480
481 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)482 svn_client__mtcc_add_add_file(const char *relpath,
483 svn_stream_t *src_stream,
484 const svn_checksum_t *src_checksum,
485 svn_client__mtcc_t *mtcc,
486 apr_pool_t *scratch_pool)
487 {
488 mtcc_op_t *op;
489 svn_boolean_t created;
490 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
491
492 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
493
494 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
495 {
496 /* Turn the root operation into a file addition */
497 op = mtcc->root_op;
498 }
499 else
500 {
501 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
502 TRUE, mtcc->pool, scratch_pool));
503
504 if (!op || !created)
505 {
506 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
507 _("Can't add file at '%s'"),
508 relpath);
509 }
510 }
511
512 op->kind = OP_ADD_FILE;
513 op->src_stream = src_stream;
514 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
515 : NULL;
516
517 return SVN_NO_ERROR;
518 }
519
520 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)521 svn_client__mtcc_add_copy(const char *src_relpath,
522 svn_revnum_t revision,
523 const char *dst_relpath,
524 svn_client__mtcc_t *mtcc,
525 apr_pool_t *scratch_pool)
526 {
527 mtcc_op_t *op;
528 svn_boolean_t created;
529 svn_node_kind_t kind;
530
531 SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath)
532 && svn_relpath_is_canonical(dst_relpath));
533
534 if (! SVN_IS_VALID_REVNUM(revision))
535 revision = mtcc->head_revision;
536 else if (revision > mtcc->head_revision)
537 {
538 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
539 _("No such revision %ld"), revision);
540 }
541
542 SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool));
543
544 /* Subversion requires the kind of a copy */
545 SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind,
546 scratch_pool));
547
548 if (kind != svn_node_dir && kind != svn_node_file)
549 {
550 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
551 _("Path '%s' not found in revision %ld"),
552 src_relpath, revision);
553 }
554
555 SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE,
556 (kind == svn_node_file), mtcc->pool, scratch_pool));
557
558 if (!op || !created)
559 {
560 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
561 _("Can't add node at '%s'"),
562 dst_relpath);
563 }
564
565 op->kind = (kind == svn_node_file) ? OP_ADD_FILE : OP_ADD_DIR;
566 op->src_relpath = apr_pstrdup(mtcc->pool, src_relpath);
567 op->src_rev = revision;
568
569 return SVN_NO_ERROR;
570 }
571
572 /* Check if this operation contains at least one change that is not a
573 plain delete */
574 static svn_boolean_t
mtcc_op_contains_non_delete(const mtcc_op_t * op)575 mtcc_op_contains_non_delete(const mtcc_op_t *op)
576 {
577 if (op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE
578 && op->kind != OP_DELETE)
579 {
580 return TRUE;
581 }
582
583 if (op->prop_mods && op->prop_mods->nelts)
584 return TRUE;
585
586 if (op->src_stream)
587 return TRUE;
588
589 if (op->children)
590 {
591 int i;
592
593 for (i = 0; i < op->children->nelts; i++)
594 {
595 const mtcc_op_t *c_op = APR_ARRAY_IDX(op->children, i,
596 const mtcc_op_t *);
597
598 if (mtcc_op_contains_non_delete(c_op))
599 return TRUE;
600 }
601 }
602 return FALSE;
603 }
604
605 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)606 mtcc_add_delete(const char *relpath,
607 svn_boolean_t for_move,
608 svn_client__mtcc_t *mtcc,
609 apr_pool_t *scratch_pool)
610 {
611 mtcc_op_t *op;
612 svn_boolean_t created;
613 svn_node_kind_t kind;
614
615 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
616
617 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
618 mtcc, scratch_pool));
619
620 if (kind == svn_node_none)
621 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
622 _("Can't delete node at '%s' as it "
623 "does not exist"),
624 relpath);
625
626 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
627 {
628 /* Turn root operation into delete */
629 op = mtcc->root_op;
630 }
631 else
632 {
633 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE,
634 TRUE, mtcc->pool, scratch_pool));
635
636 if (!for_move && !op && !created)
637 {
638 /* Allow deleting directories, that are unmodified except for
639 one or more deleted descendants */
640
641 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE,
642 FALSE, FALSE, mtcc->pool, scratch_pool));
643
644 if (op && mtcc_op_contains_non_delete(op))
645 op = NULL;
646 else
647 created = TRUE;
648 }
649
650 if (!op || !created)
651 {
652 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
653 _("Can't delete node at '%s'"),
654 relpath);
655 }
656 }
657
658 op->kind = OP_DELETE;
659 op->children = NULL;
660 op->prop_mods = NULL;
661
662 return SVN_NO_ERROR;
663 }
664
665 svn_error_t *
svn_client__mtcc_add_delete(const char * relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)666 svn_client__mtcc_add_delete(const char *relpath,
667 svn_client__mtcc_t *mtcc,
668 apr_pool_t *scratch_pool)
669 {
670 return svn_error_trace(mtcc_add_delete(relpath, FALSE, mtcc, scratch_pool));
671 }
672
673 svn_error_t *
svn_client__mtcc_add_mkdir(const char * relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)674 svn_client__mtcc_add_mkdir(const char *relpath,
675 svn_client__mtcc_t *mtcc,
676 apr_pool_t *scratch_pool)
677 {
678 mtcc_op_t *op;
679 svn_boolean_t created;
680 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
681
682 SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
683
684 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
685 {
686 /* Turn the root of the operation in an MKDIR */
687 mtcc->root_op->kind = OP_ADD_DIR;
688
689 return SVN_NO_ERROR;
690 }
691
692 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
693 FALSE, mtcc->pool, scratch_pool));
694
695 if (!op || !created)
696 {
697 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
698 _("Can't create directory at '%s'"),
699 relpath);
700 }
701
702 op->kind = OP_ADD_DIR;
703
704 return SVN_NO_ERROR;
705 }
706
707 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)708 svn_client__mtcc_add_move(const char *src_relpath,
709 const char *dst_relpath,
710 svn_client__mtcc_t *mtcc,
711 apr_pool_t *scratch_pool)
712 {
713 const char *origin_relpath;
714 svn_revnum_t origin_rev;
715
716 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
717 src_relpath, FALSE, mtcc,
718 scratch_pool, scratch_pool));
719
720 SVN_ERR(svn_client__mtcc_add_copy(src_relpath, mtcc->base_revision,
721 dst_relpath, mtcc, scratch_pool));
722 SVN_ERR(mtcc_add_delete(src_relpath, TRUE, mtcc, scratch_pool));
723
724 return SVN_NO_ERROR;
725 }
726
727 /* Baton for mtcc_prop_getter */
728 struct mtcc_prop_get_baton
729 {
730 svn_client__mtcc_t *mtcc;
731 const char *relpath;
732 svn_cancel_func_t cancel_func;
733 void *cancel_baton;
734 };
735
736 /* Implements svn_wc_canonicalize_svn_prop_get_file_t */
737 static svn_error_t *
mtcc_prop_getter(const svn_string_t ** mime_type,svn_stream_t * stream,void * baton,apr_pool_t * pool)738 mtcc_prop_getter(const svn_string_t **mime_type,
739 svn_stream_t *stream,
740 void *baton,
741 apr_pool_t *pool)
742 {
743 struct mtcc_prop_get_baton *mpgb = baton;
744 const char *origin_relpath;
745 svn_revnum_t origin_rev;
746 apr_hash_t *props = NULL;
747
748 mtcc_op_t *op;
749
750 if (mime_type)
751 *mime_type = NULL;
752
753 /* Check if we have the information locally */
754 SVN_ERR(mtcc_op_find(&op, NULL, mpgb->relpath, mpgb->mtcc->root_op, TRUE,
755 FALSE, FALSE, pool, pool));
756
757 if (op)
758 {
759 if (mime_type)
760 {
761 int i;
762
763 for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++)
764 {
765 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i,
766 svn_prop_t);
767
768 if (! strcmp(mod->name, SVN_PROP_MIME_TYPE))
769 {
770 *mime_type = svn_string_dup(mod->value, pool);
771 mime_type = NULL;
772 break;
773 }
774 }
775 }
776
777 if (stream && op->src_stream)
778 {
779 svn_stream_mark_t *mark;
780 svn_error_t *err;
781
782 /* Is the source stream capable of being read multiple times? */
783 err = svn_stream_mark(op->src_stream, &mark, pool);
784
785 if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED)
786 return svn_error_trace(err);
787 svn_error_clear(err);
788
789 if (!err)
790 {
791 err = svn_stream_copy3(svn_stream_disown(op->src_stream, pool),
792 svn_stream_disown(stream, pool),
793 mpgb->cancel_func, mpgb->cancel_baton,
794 pool);
795
796 SVN_ERR(svn_error_compose_create(
797 err,
798 svn_stream_seek(op->src_stream, mark)));
799 }
800 /* else: ### Create tempfile? */
801
802 stream = NULL; /* Stream is handled */
803 }
804 }
805
806 if (!stream && !mime_type)
807 return SVN_NO_ERROR;
808
809 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE,
810 mpgb->mtcc, pool, pool));
811
812 if (!origin_relpath)
813 return SVN_NO_ERROR; /* Nothing to fetch at repository */
814
815 SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev,
816 stream, NULL, mime_type ? &props : NULL, pool));
817
818 if (mime_type && props)
819 *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
820
821 return SVN_NO_ERROR;
822 }
823
824 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)825 svn_client__mtcc_add_propset(const char *relpath,
826 const char *propname,
827 const svn_string_t *propval,
828 svn_boolean_t skip_checks,
829 svn_client__mtcc_t *mtcc,
830 apr_pool_t *scratch_pool)
831 {
832 mtcc_op_t *op;
833 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
834
835 if (! svn_prop_name_is_valid(propname))
836 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
837 _("Bad property name: '%s'"), propname);
838
839 if (svn_prop_is_known_svn_rev_prop(propname))
840 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
841 _("Revision property '%s' not allowed "
842 "in this context"), propname);
843
844 if (svn_property_kind2(propname) == svn_prop_wc_kind)
845 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
846 _("'%s' is a wcprop, thus not accessible "
847 "to clients"), propname);
848
849 if (!skip_checks && svn_prop_needs_translation(propname))
850 {
851 svn_string_t *translated_value;
852 SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
853 NULL, propval,
854 NULL, FALSE,
855 scratch_pool, scratch_pool),
856 _("Error normalizing property value"));
857
858 propval = translated_value;
859 }
860
861 if (propval && svn_prop_is_svn_prop(propname))
862 {
863 struct mtcc_prop_get_baton mpbg;
864 svn_node_kind_t kind;
865 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, mtcc,
866 scratch_pool));
867
868 mpbg.mtcc = mtcc;
869 mpbg.relpath = relpath;
870 mpbg.cancel_func = mtcc->ctx->cancel_func;
871 mpbg.cancel_baton = mtcc->ctx->cancel_baton;
872
873 SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval,
874 relpath, kind, skip_checks,
875 mtcc_prop_getter, &mpbg,
876 scratch_pool));
877 }
878
879 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
880 {
881 svn_node_kind_t kind;
882
883 /* Probing the node for an unmodified root will fix the node type to
884 a file if necessary */
885
886 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
887 mtcc, scratch_pool));
888
889 if (kind == svn_node_none)
890 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
891 _("Can't set properties at not existing '%s'"),
892 relpath);
893
894 op = mtcc->root_op;
895 }
896 else
897 {
898 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
899 FALSE, mtcc->pool, scratch_pool));
900
901 if (!op)
902 {
903 svn_node_kind_t kind;
904 svn_boolean_t created;
905
906 /* ### TODO: Check if this node is within a newly copied directory,
907 and update origin values accordingly */
908
909 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
910 mtcc, scratch_pool));
911
912 if (kind == svn_node_none)
913 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
914 _("Can't set properties at not existing '%s'"),
915 relpath);
916
917 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
918 (kind != svn_node_dir),
919 mtcc->pool, scratch_pool));
920
921 SVN_ERR_ASSERT(op != NULL);
922 }
923 }
924
925 if (!op->prop_mods)
926 op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t));
927
928 {
929 svn_prop_t propchange;
930 propchange.name = apr_pstrdup(mtcc->pool, propname);
931
932 if (propval)
933 propchange.value = svn_string_dup(propval, mtcc->pool);
934 else
935 propchange.value = NULL;
936
937 APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange;
938 }
939
940 return SVN_NO_ERROR;
941 }
942
943 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)944 svn_client__mtcc_add_update_file(const char *relpath,
945 svn_stream_t *src_stream,
946 const svn_checksum_t *src_checksum,
947 svn_stream_t *base_stream,
948 const svn_checksum_t *base_checksum,
949 svn_client__mtcc_t *mtcc,
950 apr_pool_t *scratch_pool)
951 {
952 mtcc_op_t *op;
953 svn_boolean_t created;
954 svn_node_kind_t kind;
955 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
956
957 SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
958 mtcc, scratch_pool));
959
960 if (kind != svn_node_file)
961 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
962 _("Can't update '%s' because it is not a file"),
963 relpath);
964
965 SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
966 TRUE, mtcc->pool, scratch_pool));
967
968 if (!op
969 || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE)
970 || (op->src_stream != NULL))
971 {
972 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
973 _("Can't update file at '%s'"), relpath);
974 }
975
976 op->src_stream = src_stream;
977 op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
978 : NULL;
979
980 op->base_stream = base_stream;
981 op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum,
982 mtcc->pool)
983 : NULL;
984
985 return SVN_NO_ERROR;
986 }
987
988 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)989 svn_client__mtcc_check_path(svn_node_kind_t *kind,
990 const char *relpath,
991 svn_boolean_t check_repository,
992 svn_client__mtcc_t *mtcc,
993 apr_pool_t *scratch_pool)
994 {
995 const char *origin_relpath;
996 svn_revnum_t origin_rev;
997 mtcc_op_t *op;
998
999 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
1000
1001 if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)
1002 && !mtcc->root_op->performed_stat)
1003 {
1004 /* We know nothing about the root. Perhaps it is a file? */
1005 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision,
1006 kind, scratch_pool));
1007
1008 mtcc->root_op->performed_stat = TRUE;
1009 if (*kind == svn_node_file)
1010 {
1011 mtcc->root_op->kind = OP_OPEN_FILE;
1012 mtcc->root_op->children = NULL;
1013 }
1014 return SVN_NO_ERROR;
1015 }
1016
1017 SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
1018 FALSE, mtcc->pool, scratch_pool));
1019
1020 if (!op || (check_repository && !op->performed_stat))
1021 {
1022 SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
1023 relpath, TRUE, mtcc,
1024 scratch_pool, scratch_pool));
1025
1026 if (!origin_relpath)
1027 *kind = svn_node_none;
1028 else
1029 SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath,
1030 origin_rev, kind, scratch_pool));
1031
1032 if (op && *kind == svn_node_dir)
1033 {
1034 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1035 op->performed_stat = TRUE;
1036 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1037 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
1038 _("Can't perform file operation "
1039 "on '%s' as it is not a file"),
1040 relpath);
1041 }
1042 else if (op && *kind == svn_node_file)
1043 {
1044 if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1045 op->performed_stat = TRUE;
1046 else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1047 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1048 _("Can't perform directory operation "
1049 "on '%s' as it is not a directory"),
1050 relpath);
1051 }
1052 else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE))
1053 {
1054 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1055 _("Can't open '%s' as it does not exist"),
1056 relpath);
1057 }
1058
1059 return SVN_NO_ERROR;
1060 }
1061
1062 /* op != NULL */
1063 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1064 {
1065 *kind = svn_node_dir;
1066 return SVN_NO_ERROR;
1067 }
1068 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1069 {
1070 *kind = svn_node_file;
1071 return SVN_NO_ERROR;
1072 }
1073 SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */
1074 }
1075
1076 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)1077 commit_properties(const svn_delta_editor_t *editor,
1078 const mtcc_op_t *op,
1079 void *node_baton,
1080 apr_pool_t *scratch_pool)
1081 {
1082 int i;
1083 apr_pool_t *iterpool;
1084
1085 if (!op->prop_mods || op->prop_mods->nelts == 0)
1086 return SVN_NO_ERROR;
1087
1088 iterpool = svn_pool_create(scratch_pool);
1089 for (i = 0; i < op->prop_mods->nelts; i++)
1090 {
1091 const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t);
1092
1093 svn_pool_clear(iterpool);
1094
1095 if (op->kind == OP_ADD_DIR || op->kind == OP_OPEN_DIR)
1096 SVN_ERR(editor->change_dir_prop(node_baton, mod->name, mod->value,
1097 iterpool));
1098 else if (op->kind == OP_ADD_FILE || op->kind == OP_OPEN_FILE)
1099 SVN_ERR(editor->change_file_prop(node_baton, mod->name, mod->value,
1100 iterpool));
1101 }
1102
1103 svn_pool_destroy(iterpool);
1104 return SVN_NO_ERROR;
1105 }
1106
1107 /* Handles updating a file to a delta editor and then closes it */
1108 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)1109 commit_file(const svn_delta_editor_t *editor,
1110 mtcc_op_t *op,
1111 void *file_baton,
1112 const char *session_url,
1113 const char *relpath,
1114 svn_client_ctx_t *ctx,
1115 apr_pool_t *scratch_pool)
1116 {
1117 const char *text_checksum = NULL;
1118 svn_checksum_t *src_checksum = op->src_checksum;
1119 SVN_ERR(commit_properties(editor, op, file_baton, scratch_pool));
1120
1121 if (op->src_stream)
1122 {
1123 const char *base_checksum = NULL;
1124 apr_pool_t *txdelta_pool = scratch_pool;
1125 svn_txdelta_window_handler_t window_handler;
1126 void *handler_baton;
1127 svn_stream_t *src_stream = op->src_stream;
1128
1129 if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5)
1130 base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool);
1131
1132 /* ### TODO: Future enhancement: Allocate in special pool and send
1133 files after the true edit operation, like a wc commit */
1134 SVN_ERR(editor->apply_textdelta(file_baton, base_checksum, txdelta_pool,
1135 &window_handler, &handler_baton));
1136
1137 if (ctx->notify_func2)
1138 {
1139 svn_wc_notify_t *notify;
1140
1141 notify = svn_wc_create_notify_url(
1142 svn_path_url_add_component2(session_url, relpath,
1143 scratch_pool),
1144 svn_wc_notify_commit_postfix_txdelta,
1145 scratch_pool);
1146
1147 notify->path = relpath;
1148 notify->kind = svn_node_file;
1149
1150 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
1151 }
1152
1153 if (window_handler != svn_delta_noop_window_handler)
1154 {
1155 if (!src_checksum || src_checksum->kind != svn_checksum_md5)
1156 src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL,
1157 svn_checksum_md5,
1158 TRUE, scratch_pool);
1159
1160 if (!op->base_stream)
1161 SVN_ERR(svn_txdelta_send_stream(src_stream,
1162 window_handler, handler_baton, NULL,
1163 scratch_pool));
1164 else
1165 SVN_ERR(svn_txdelta_run(op->base_stream, src_stream,
1166 window_handler, handler_baton,
1167 svn_checksum_md5, NULL,
1168 ctx->cancel_func, ctx->cancel_baton,
1169 scratch_pool, scratch_pool));
1170 }
1171
1172 SVN_ERR(svn_stream_close(src_stream));
1173 if (op->base_stream)
1174 SVN_ERR(svn_stream_close(op->base_stream));
1175 }
1176
1177 if (src_checksum && src_checksum->kind == svn_checksum_md5)
1178 text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool);
1179
1180 return svn_error_trace(editor->close_file(file_baton, text_checksum,
1181 scratch_pool));
1182 }
1183
1184 /* Handles updating a directory to a delta editor and then closes it */
1185 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)1186 commit_directory(const svn_delta_editor_t *editor,
1187 mtcc_op_t *op,
1188 const char *relpath,
1189 svn_revnum_t base_rev,
1190 void *dir_baton,
1191 const char *session_url,
1192 svn_client_ctx_t *ctx,
1193 apr_pool_t *scratch_pool)
1194 {
1195 SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool));
1196
1197 if (op->children && op->children->nelts > 0)
1198 {
1199 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1200 int i;
1201
1202 for (i = 0; i < op->children->nelts; i++)
1203 {
1204 mtcc_op_t *cop;
1205 const char * child_relpath;
1206 void *child_baton;
1207
1208 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1209
1210 svn_pool_clear(iterpool);
1211
1212 if (ctx->cancel_func)
1213 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1214
1215 child_relpath = svn_relpath_join(relpath, cop->name, iterpool);
1216
1217 switch (cop->kind)
1218 {
1219 case OP_DELETE:
1220 SVN_ERR(editor->delete_entry(child_relpath, base_rev,
1221 dir_baton, iterpool));
1222 break;
1223
1224 case OP_ADD_DIR:
1225 SVN_ERR(editor->add_directory(child_relpath, dir_baton,
1226 cop->src_relpath
1227 ? svn_path_url_add_component2(
1228 session_url,
1229 cop->src_relpath,
1230 iterpool)
1231 : NULL,
1232 cop->src_rev,
1233 iterpool, &child_baton));
1234 SVN_ERR(commit_directory(editor, cop, child_relpath,
1235 SVN_INVALID_REVNUM, child_baton,
1236 session_url, ctx, iterpool));
1237 break;
1238 case OP_OPEN_DIR:
1239 SVN_ERR(editor->open_directory(child_relpath, dir_baton,
1240 base_rev, iterpool, &child_baton));
1241 SVN_ERR(commit_directory(editor, cop, child_relpath,
1242 base_rev, child_baton,
1243 session_url, ctx, iterpool));
1244 break;
1245
1246 case OP_ADD_FILE:
1247 SVN_ERR(editor->add_file(child_relpath, dir_baton,
1248 cop->src_relpath
1249 ? svn_path_url_add_component2(
1250 session_url,
1251 cop->src_relpath,
1252 iterpool)
1253 : NULL,
1254 cop->src_rev,
1255 iterpool, &child_baton));
1256 SVN_ERR(commit_file(editor, cop, child_baton,
1257 session_url, child_relpath, ctx, iterpool));
1258 break;
1259 case OP_OPEN_FILE:
1260 SVN_ERR(editor->open_file(child_relpath, dir_baton, base_rev,
1261 iterpool, &child_baton));
1262 SVN_ERR(commit_file(editor, cop, child_baton,
1263 session_url, child_relpath, ctx, iterpool));
1264 break;
1265
1266 default:
1267 SVN_ERR_MALFUNCTION();
1268 }
1269 }
1270 }
1271
1272 return svn_error_trace(editor->close_directory(dir_baton, scratch_pool));
1273 }
1274
1275
1276 /* Helper function to recursively create svn_client_commit_item3_t items
1277 to provide to the log message callback */
1278 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)1279 add_commit_items(mtcc_op_t *op,
1280 const char *session_url,
1281 const char *url,
1282 apr_array_header_t *commit_items,
1283 apr_pool_t *result_pool,
1284 apr_pool_t *scratch_pool)
1285 {
1286 if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE)
1287 || (op->prop_mods && op->prop_mods->nelts)
1288 || (op->src_stream))
1289 {
1290 svn_client_commit_item3_t *item;
1291
1292 item = svn_client_commit_item3_create(result_pool);
1293
1294 item->path = NULL;
1295 if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1296 item->kind = svn_node_dir;
1297 else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1298 item->kind = svn_node_file;
1299 else
1300 item->kind = svn_node_unknown;
1301
1302 item->url = apr_pstrdup(result_pool, url);
1303 item->session_relpath = svn_uri_skip_ancestor(session_url, item->url,
1304 result_pool);
1305
1306 if (op->src_relpath)
1307 {
1308 item->copyfrom_url = svn_path_url_add_component2(session_url,
1309 op->src_relpath,
1310 result_pool);
1311 item->copyfrom_rev = op->src_rev;
1312 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1313 }
1314 else
1315 item->copyfrom_rev = SVN_INVALID_REVNUM;
1316
1317 if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
1318 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1319 else if (op->kind == OP_DELETE)
1320 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1321 /* else item->state_flags = 0; */
1322
1323 if (op->prop_mods && op->prop_mods->nelts)
1324 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1325
1326 if (op->src_stream)
1327 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
1328
1329 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1330 }
1331
1332 if (op->children && op->children->nelts)
1333 {
1334 int i;
1335 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1336
1337 for (i = 0; i < op->children->nelts; i++)
1338 {
1339 mtcc_op_t *cop;
1340 const char * child_url;
1341
1342 cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1343
1344 svn_pool_clear(iterpool);
1345
1346 child_url = svn_path_url_add_component2(url, cop->name, iterpool);
1347
1348 SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items,
1349 result_pool, iterpool));
1350 }
1351
1352 svn_pool_destroy(iterpool);
1353 }
1354
1355 return SVN_NO_ERROR;
1356 }
1357
1358 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)1359 svn_client__mtcc_commit(apr_hash_t *revprop_table,
1360 svn_commit_callback2_t commit_callback,
1361 void *commit_baton,
1362 svn_client__mtcc_t *mtcc,
1363 apr_pool_t *scratch_pool)
1364 {
1365 const svn_delta_editor_t *editor;
1366 void *edit_baton;
1367 void *root_baton;
1368 apr_hash_t *commit_revprops;
1369 svn_node_kind_t kind;
1370 svn_error_t *err;
1371 const char *session_url;
1372 const char *log_msg;
1373
1374 if (MTCC_UNMODIFIED(mtcc))
1375 {
1376 /* No changes -> no revision. Easy out */
1377 svn_pool_destroy(mtcc->pool);
1378 return SVN_NO_ERROR;
1379 }
1380
1381 SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool));
1382
1383 if (mtcc->root_op->kind != OP_OPEN_DIR)
1384 {
1385 const char *name;
1386
1387 svn_uri_split(&session_url, &name, session_url, scratch_pool);
1388
1389 if (*name)
1390 {
1391 SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool));
1392
1393 SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool));
1394 }
1395 }
1396
1397 /* Create new commit items and add them to the array. */
1398 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx))
1399 {
1400 svn_client_commit_item3_t *item;
1401 const char *tmp_file;
1402 apr_array_header_t *commit_items
1403 = apr_array_make(scratch_pool, 32, sizeof(item));
1404
1405 SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url,
1406 commit_items, scratch_pool, scratch_pool));
1407
1408 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
1409 mtcc->ctx, scratch_pool));
1410
1411 if (! log_msg)
1412 return SVN_NO_ERROR;
1413 }
1414 else
1415 log_msg = "";
1416
1417 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1418 log_msg, mtcc->ctx, scratch_pool));
1419
1420 /* Ugly corner case: The ra session might have died while we were waiting
1421 for the callback */
1422
1423 err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind,
1424 scratch_pool);
1425
1426 if (err)
1427 {
1428 svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session,
1429 session_url,
1430 NULL, mtcc->ctx,
1431 mtcc->pool,
1432 scratch_pool);
1433
1434 if (err2)
1435 {
1436 svn_pool_destroy(mtcc->pool);
1437 return svn_error_trace(svn_error_compose_create(err, err2));
1438 }
1439 svn_error_clear(err);
1440
1441 SVN_ERR(svn_ra_check_path(mtcc->ra_session, "",
1442 mtcc->base_revision, &kind, scratch_pool));
1443 }
1444
1445 if (kind != svn_node_dir)
1446 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1447 _("Can't commit to '%s' because it "
1448 "is not a directory"),
1449 session_url);
1450
1451 /* Beware that the editor object must not live longer than the MTCC.
1452 Otherwise, txn objects etc. in EDITOR may live longer than their
1453 respective FS objects. So, we can't use SCRATCH_POOL here. */
1454 SVN_ERR(svn_ra_get_commit_editor3(mtcc->ra_session, &editor, &edit_baton,
1455 commit_revprops,
1456 commit_callback, commit_baton,
1457 NULL /* lock_tokens */,
1458 FALSE /* keep_locks */,
1459 mtcc->pool));
1460
1461 err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton);
1462
1463 if (!err)
1464 err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision,
1465 root_baton, session_url, mtcc->ctx, scratch_pool);
1466
1467 if (!err)
1468 {
1469 if (mtcc->ctx->notify_func2)
1470 {
1471 svn_wc_notify_t *notify;
1472 notify = svn_wc_create_notify_url(session_url,
1473 svn_wc_notify_commit_finalizing,
1474 scratch_pool);
1475 mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify,
1476 scratch_pool);
1477 }
1478 SVN_ERR(editor->close_edit(edit_baton, scratch_pool));
1479 }
1480 else
1481 err = svn_error_compose_create(err,
1482 editor->abort_edit(edit_baton, scratch_pool));
1483
1484 svn_pool_destroy(mtcc->pool);
1485
1486 return svn_error_trace(err);
1487 }
1488