]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/mtcc.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_client / mtcc.c
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 svn_error_t *
572 svn_client__mtcc_add_delete(const char *relpath,
573                             svn_client__mtcc_t *mtcc,
574                             apr_pool_t *scratch_pool)
575 {
576   mtcc_op_t *op;
577   svn_boolean_t created;
578   svn_node_kind_t kind;
579
580   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
581
582   SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
583                                       mtcc, scratch_pool));
584
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 "
588                                 "does not exist"),
589                              relpath);
590
591   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
592     {
593       /* Turn root operation into delete */
594       op = mtcc->root_op;
595     }
596   else
597     {
598       SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE,
599                            TRUE, mtcc->pool, scratch_pool));
600
601       if (!op || !created)
602         {
603           return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
604                                    _("Can't delete node at '%s'"),
605                                    relpath);
606         }
607     }
608
609   op->kind = OP_DELETE;
610   op->children = NULL;
611   op->prop_mods = NULL;
612
613   return SVN_NO_ERROR;
614 }
615
616 svn_error_t *
617 svn_client__mtcc_add_mkdir(const char *relpath,
618                            svn_client__mtcc_t *mtcc,
619                            apr_pool_t *scratch_pool)
620 {
621   mtcc_op_t *op;
622   svn_boolean_t created;
623   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
624
625   SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
626
627   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
628     {
629       /* Turn the root of the operation in an MKDIR */
630       mtcc->root_op->kind = OP_ADD_DIR;
631
632       return SVN_NO_ERROR;
633     }
634
635   SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
636                        FALSE, mtcc->pool, scratch_pool));
637
638   if (!op || !created)
639     {
640       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
641                                _("Can't create directory at '%s'"),
642                                relpath);
643     }
644
645   op->kind = OP_ADD_DIR;
646
647   return SVN_NO_ERROR;
648 }
649
650 svn_error_t *
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)
655 {
656   const char *origin_relpath;
657   svn_revnum_t origin_rev;
658
659   SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
660                           src_relpath, FALSE, mtcc,
661                           scratch_pool, scratch_pool));
662
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));
666
667   return SVN_NO_ERROR;
668 }
669
670 /* Baton for mtcc_prop_getter */
671 struct mtcc_prop_get_baton
672 {
673   svn_client__mtcc_t *mtcc;
674   const char *relpath;
675   svn_cancel_func_t cancel_func;
676   void *cancel_baton;
677 };
678
679 /* Implements svn_wc_canonicalize_svn_prop_get_file_t */
680 static svn_error_t *
681 mtcc_prop_getter(const svn_string_t **mime_type,
682                  svn_stream_t *stream,
683                  void *baton,
684                  apr_pool_t *pool)
685 {
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;
690
691   mtcc_op_t *op;
692
693   if (mime_type)
694     *mime_type = NULL;
695
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));
699
700   if (op)
701     {
702       if (mime_type)
703         {
704           int i;
705
706           for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++)
707             {
708               const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i,
709                                                      svn_prop_t);
710
711               if (! strcmp(mod->name, SVN_PROP_MIME_TYPE))
712                 {
713                   *mime_type = svn_string_dup(mod->value, pool);
714                   mime_type = NULL;
715                 }
716             }
717         }
718
719       if (stream && op->src_stream)
720         {
721           svn_stream_mark_t *mark;
722           svn_error_t *err;
723
724           /* Is the source stream capable of being read multiple times? */
725           err = svn_stream_mark(op->src_stream, &mark, pool);
726
727           if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED)
728             return svn_error_trace(err);
729           svn_error_clear(err);
730
731           if (!err)
732             {
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,
736                                      pool);
737
738               SVN_ERR(svn_error_compose_create(
739                             err,
740                             svn_stream_seek(op->src_stream, mark)));
741             }
742           /* else: ### Create tempfile? */
743
744           stream = NULL; /* Stream is handled */
745         }
746     }
747
748   if (!stream && !mime_type)
749     return SVN_NO_ERROR;
750
751   SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE,
752                           mpgb->mtcc, pool, pool));
753
754   if (!origin_relpath)
755     return SVN_NO_ERROR; /* Nothing to fetch at repository */
756
757   SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev,
758                           stream, NULL, mime_type ? &props : NULL, pool));
759
760   if (mime_type && props)
761     *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
762
763   return SVN_NO_ERROR;
764 }
765
766 svn_error_t *
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)
773 {
774   mtcc_op_t *op;
775   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
776
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);
780
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);
785
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);
790
791   if (!skip_checks && svn_prop_needs_translation(propname))
792     {
793       svn_string_t *translated_value;
794       SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
795                                             NULL, propval,
796                                             NULL, FALSE,
797                                             scratch_pool, scratch_pool),
798                 _("Error normalizing property value"));
799
800       propval = translated_value;
801     }
802
803   if (propval && svn_prop_is_svn_prop(propname))
804     {
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,
808                                           scratch_pool));
809
810       mpbg.mtcc = mtcc;
811       mpbg.relpath = relpath;
812       mpbg.cancel_func = mtcc->ctx->cancel_func;
813       mpbg.cancel_baton = mtcc->ctx->cancel_baton;
814
815       SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval,
816                                            relpath, kind, skip_checks,
817                                            mtcc_prop_getter, &mpbg,
818                                            scratch_pool));
819     }
820
821   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
822     {
823       svn_node_kind_t kind;
824
825       /* Probing the node for an unmodified root will fix the node type to
826          a file if necessary */
827
828       SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
829                                           mtcc, scratch_pool));
830
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'"),
834                                    relpath);
835
836       op = mtcc->root_op;
837     }
838   else
839     {
840       SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
841                            FALSE, mtcc->pool, scratch_pool));
842
843       if (!op)
844         {
845           svn_node_kind_t kind;
846           svn_boolean_t created;
847
848           /* ### TODO: Check if this node is within a newly copied directory,
849                        and update origin values accordingly */
850
851           SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
852                                               mtcc, scratch_pool));
853
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'"),
857                                      relpath);
858
859           SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
860                                (kind != svn_node_dir),
861                                mtcc->pool, scratch_pool));
862
863           SVN_ERR_ASSERT(op != NULL);
864         }
865     }
866
867   if (!op->prop_mods)
868       op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t));
869
870   {
871     svn_prop_t propchange;
872     propchange.name = apr_pstrdup(mtcc->pool, propname);
873
874     if (propval)
875       propchange.value = svn_string_dup(propval, mtcc->pool);
876     else
877       propchange.value = NULL;
878
879     APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange;
880   }
881
882   return SVN_NO_ERROR;
883 }
884
885 svn_error_t *
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)
893 {
894   mtcc_op_t *op;
895   svn_boolean_t created;
896   svn_node_kind_t kind;
897   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
898
899   SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
900                                       mtcc, scratch_pool));
901
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"),
905                              relpath);
906
907   SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
908                        TRUE, mtcc->pool, scratch_pool));
909
910   if (!op
911       || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE)
912       || (op->src_stream != NULL))
913     {
914       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
915                                _("Can't update file at '%s'"), relpath);
916     }
917
918   op->src_stream = src_stream;
919   op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
920                                   : NULL;
921
922   op->base_stream = base_stream;
923   op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum,
924                                                        mtcc->pool)
925                                     : NULL;
926
927   return SVN_NO_ERROR;
928 }
929
930 svn_error_t *
931 svn_client__mtcc_check_path(svn_node_kind_t *kind,
932                             const char *relpath,
933                             svn_boolean_t check_repository,
934                             svn_client__mtcc_t *mtcc,
935                             apr_pool_t *scratch_pool)
936 {
937   const char *origin_relpath;
938   svn_revnum_t origin_rev;
939   mtcc_op_t *op;
940
941   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
942
943   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)
944       && !mtcc->root_op->performed_stat)
945     {
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));
949
950       mtcc->root_op->performed_stat = TRUE;
951       if (*kind == svn_node_file)
952         {
953           mtcc->root_op->kind = OP_OPEN_FILE;
954           mtcc->root_op->children = NULL;
955         }
956       return SVN_NO_ERROR;
957     }
958
959   SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
960                        FALSE, mtcc->pool, scratch_pool));
961
962   if (!op || (check_repository && !op->performed_stat))
963     {
964       SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
965                               relpath, TRUE, mtcc,
966                               scratch_pool, scratch_pool));
967
968       if (!origin_relpath)
969         *kind = svn_node_none;
970       else
971         SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath,
972                                   origin_rev, kind, scratch_pool));
973
974       if (op && *kind == svn_node_dir)
975         {
976           if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
977             op->performed_stat = TRUE;
978           else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
979             return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
980                                      _("Can't perform file operation "
981                                        "on '%s' as it is not a file"),
982                                      relpath);
983         }
984       else if (op && *kind == svn_node_file)
985         {
986           if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
987             op->performed_stat = TRUE;
988           else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
989             return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
990                                      _("Can't perform directory operation "
991                                        "on '%s' as it is not a directory"),
992                                      relpath);
993         }
994       else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE))
995         {
996           return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
997                                    _("Can't open '%s' as it does not exist"),
998                                    relpath);
999         }
1000
1001       return SVN_NO_ERROR;
1002     }
1003
1004   /* op != NULL */
1005   if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1006     {
1007       *kind = svn_node_dir;
1008       return SVN_NO_ERROR;
1009     }
1010   else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1011     {
1012       *kind = svn_node_file;
1013       return SVN_NO_ERROR;
1014     }
1015   SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */
1016 }
1017
1018 static svn_error_t *
1019 commit_properties(const svn_delta_editor_t *editor,
1020                   const mtcc_op_t *op,
1021                   void *node_baton,
1022                   apr_pool_t *scratch_pool)
1023 {
1024   int i;
1025   apr_pool_t *iterpool;
1026
1027   if (!op->prop_mods || op->prop_mods->nelts == 0)
1028     return SVN_NO_ERROR;
1029
1030   iterpool = svn_pool_create(scratch_pool);
1031   for (i = 0; i < op->prop_mods->nelts; i++)
1032     {
1033       const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t);
1034
1035       svn_pool_clear(iterpool);
1036
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,
1039                                         iterpool));
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,
1042                                          iterpool));
1043     }
1044
1045   svn_pool_destroy(iterpool);
1046   return SVN_NO_ERROR;
1047 }
1048
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,
1052             mtcc_op_t *op,
1053             void *file_baton,
1054             const char *session_url,
1055             const char *relpath,
1056             svn_client_ctx_t *ctx,
1057             apr_pool_t *scratch_pool)
1058 {
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));
1062
1063   if (op->src_stream)
1064     {
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;
1070
1071       if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5)
1072         base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool);
1073
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));
1078
1079       if (ctx->notify_func2)
1080         {
1081           svn_wc_notify_t *notify;
1082
1083           notify = svn_wc_create_notify_url(
1084                             svn_path_url_add_component2(session_url, relpath,
1085                                                         scratch_pool),
1086                             svn_wc_notify_commit_postfix_txdelta,
1087                             scratch_pool);
1088
1089           notify->path = relpath;
1090           notify->kind = svn_node_file;
1091
1092           ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
1093         }
1094
1095       if (window_handler != svn_delta_noop_window_handler)
1096         {
1097           if (!src_checksum || src_checksum->kind != svn_checksum_md5)
1098             src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL,
1099                                                  svn_checksum_md5,
1100                                                  TRUE, scratch_pool);
1101
1102           if (!op->base_stream)
1103             SVN_ERR(svn_txdelta_send_stream(src_stream,
1104                                             window_handler, handler_baton, NULL,
1105                                             scratch_pool));
1106           else
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));
1112         }
1113
1114       SVN_ERR(svn_stream_close(src_stream));
1115       if (op->base_stream)
1116         SVN_ERR(svn_stream_close(op->base_stream));
1117     }
1118
1119   if (src_checksum && src_checksum->kind == svn_checksum_md5)
1120     text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool);
1121
1122   return svn_error_trace(editor->close_file(file_baton, text_checksum,
1123                                             scratch_pool));
1124 }
1125
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,
1129                  mtcc_op_t *op,
1130                  const char *relpath,
1131                  svn_revnum_t base_rev,
1132                  void *dir_baton,
1133                  const char *session_url,
1134                  svn_client_ctx_t *ctx,
1135                  apr_pool_t *scratch_pool)
1136 {
1137   SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool));
1138
1139   if (op->children && op->children->nelts > 0)
1140     {
1141       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1142       int i;
1143
1144       for (i = 0; i < op->children->nelts; i++)
1145         {
1146           mtcc_op_t *cop;
1147           const char * child_relpath;
1148           void *child_baton;
1149
1150           cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1151
1152           svn_pool_clear(iterpool);
1153
1154           if (ctx->cancel_func)
1155             SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1156
1157           child_relpath = svn_relpath_join(relpath, cop->name, iterpool);
1158
1159           switch (cop->kind)
1160             {
1161               case OP_DELETE:
1162                 SVN_ERR(editor->delete_entry(child_relpath, base_rev,
1163                                              dir_baton, iterpool));
1164                 break;
1165
1166               case OP_ADD_DIR:
1167                 SVN_ERR(editor->add_directory(child_relpath, dir_baton,
1168                                               cop->src_relpath
1169                                                 ? svn_path_url_add_component2(
1170                                                               session_url,
1171                                                               cop->src_relpath,
1172                                                               iterpool)
1173                                                 : NULL,
1174                                               cop->src_rev,
1175                                               iterpool, &child_baton));
1176                 SVN_ERR(commit_directory(editor, cop, child_relpath,
1177                                          SVN_INVALID_REVNUM, child_baton,
1178                                          session_url, ctx, iterpool));
1179                 break;
1180               case OP_OPEN_DIR:
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));
1186                 break;
1187
1188               case OP_ADD_FILE:
1189                 SVN_ERR(editor->add_file(child_relpath, dir_baton,
1190                                          cop->src_relpath
1191                                             ? svn_path_url_add_component2(
1192                                                             session_url,
1193                                                             cop->src_relpath,
1194                                                             iterpool)
1195                                             : NULL,
1196                                          cop->src_rev,
1197                                          iterpool, &child_baton));
1198                 SVN_ERR(commit_file(editor, cop, child_baton,
1199                                     session_url, child_relpath, ctx, iterpool));
1200                 break;
1201               case OP_OPEN_FILE:
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));
1206                 break;
1207
1208               default:
1209                 SVN_ERR_MALFUNCTION();
1210             }
1211         }
1212     }
1213
1214   return svn_error_trace(editor->close_directory(dir_baton, scratch_pool));
1215 }
1216
1217
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,
1223                  const char *url,
1224                  apr_array_header_t *commit_items,
1225                  apr_pool_t *result_pool,
1226                  apr_pool_t *scratch_pool)
1227 {
1228   if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE)
1229       || (op->prop_mods && op->prop_mods->nelts)
1230       || (op->src_stream))
1231     {
1232       svn_client_commit_item3_t *item;
1233
1234       item = svn_client_commit_item3_create(result_pool);
1235
1236       item->path = NULL;
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;
1241       else
1242         item->kind = svn_node_unknown;
1243
1244       item->url = apr_pstrdup(result_pool, url);
1245       item->session_relpath = svn_uri_skip_ancestor(session_url, item->url,
1246                                                     result_pool);
1247
1248       if (op->src_relpath)
1249         {
1250           item->copyfrom_url = svn_path_url_add_component2(session_url,
1251                                                            op->src_relpath,
1252                                                            result_pool);
1253           item->copyfrom_rev = op->src_rev;
1254           item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1255         }
1256       else
1257         item->copyfrom_rev = SVN_INVALID_REVNUM;
1258
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; */
1264
1265       if (op->prop_mods && op->prop_mods->nelts)
1266         item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1267
1268       if (op->src_stream)
1269         item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
1270
1271       APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1272     }
1273
1274   if (op->children && op->children->nelts)
1275     {
1276       int i;
1277       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1278
1279       for (i = 0; i < op->children->nelts; i++)
1280         {
1281           mtcc_op_t *cop;
1282           const char * child_url;
1283
1284           cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1285
1286           svn_pool_clear(iterpool);
1287
1288           child_url = svn_path_url_add_component2(url, cop->name, iterpool);
1289
1290           SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items,
1291                                    result_pool, iterpool));
1292         }
1293
1294       svn_pool_destroy(iterpool);
1295     }
1296
1297   return SVN_NO_ERROR;
1298 }
1299
1300 svn_error_t *
1301 svn_client__mtcc_commit(apr_hash_t *revprop_table,
1302                         svn_commit_callback2_t commit_callback,
1303                         void *commit_baton,
1304                         svn_client__mtcc_t *mtcc,
1305                         apr_pool_t *scratch_pool)
1306 {
1307   const svn_delta_editor_t *editor;
1308   void *edit_baton;
1309   void *root_baton;
1310   apr_hash_t *commit_revprops;
1311   svn_node_kind_t kind;
1312   svn_error_t *err;
1313   const char *session_url;
1314   const char *log_msg;
1315
1316   if (MTCC_UNMODIFIED(mtcc))
1317     {
1318       /* No changes -> no revision. Easy out */
1319       svn_pool_destroy(mtcc->pool);
1320       return SVN_NO_ERROR;
1321     }
1322
1323   SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool));
1324
1325   if (mtcc->root_op->kind != OP_OPEN_DIR)
1326     {
1327       const char *name;
1328
1329       svn_uri_split(&session_url, &name, session_url, scratch_pool);
1330
1331       if (*name)
1332         {
1333           SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool));
1334
1335           SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool));
1336         }
1337     }
1338
1339     /* Create new commit items and add them to the array. */
1340   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx))
1341     {
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));
1346
1347       SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url,
1348                                commit_items, scratch_pool, scratch_pool));
1349
1350       SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
1351                                       mtcc->ctx, scratch_pool));
1352
1353       if (! log_msg)
1354         return SVN_NO_ERROR;
1355     }
1356   else
1357     log_msg = "";
1358
1359   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1360                                            log_msg, mtcc->ctx, scratch_pool));
1361
1362   /* Ugly corner case: The ra session might have died while we were waiting
1363      for the callback */
1364
1365   err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind,
1366                           scratch_pool);
1367
1368   if (err)
1369     {
1370       svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session,
1371                                                       session_url,
1372                                                       NULL, mtcc->ctx,
1373                                                       mtcc->pool,
1374                                                       scratch_pool);
1375
1376       if (err2)
1377         {
1378           svn_pool_destroy(mtcc->pool);
1379           return svn_error_trace(svn_error_compose_create(err, err2));
1380         }
1381       svn_error_clear(err);
1382
1383       SVN_ERR(svn_ra_check_path(mtcc->ra_session, "",
1384                                 mtcc->base_revision, &kind, scratch_pool));
1385     }
1386
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"),
1391                              session_url);
1392
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,
1397                                     commit_revprops,
1398                                     commit_callback, commit_baton,
1399                                     NULL /* lock_tokens */,
1400                                     FALSE /* keep_locks */,
1401                                     mtcc->pool));
1402
1403   err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton);
1404
1405   if (!err)
1406     err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision,
1407                            root_baton, session_url, mtcc->ctx, scratch_pool);
1408
1409   if (!err)
1410     {
1411       if (mtcc->ctx->notify_func2)
1412         {
1413           svn_wc_notify_t *notify;
1414           notify = svn_wc_create_notify_url(session_url,
1415                                             svn_wc_notify_commit_finalizing,
1416                                             scratch_pool);
1417           mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify,
1418                                   scratch_pool);
1419         }
1420       SVN_ERR(editor->close_edit(edit_baton, scratch_pool));
1421     }
1422   else
1423     err = svn_error_compose_create(err,
1424                                    editor->abort_edit(edit_baton, scratch_pool));
1425
1426   svn_pool_destroy(mtcc->pool);
1427
1428   return svn_error_trace(err);
1429 }