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 */
44typedef 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
53typedef 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
82struct 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
94static mtcc_op_t *
95mtcc_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
118static svn_error_t *
119mtcc_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.  */
216static svn_error_t *
217get_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 */
294static svn_error_t *
295mtcc_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
326svn_error_t *
327svn_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
365static svn_error_t *
366update_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
391static svn_error_t *
392mtcc_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 */
440static svn_error_t *
441mtcc_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
481svn_error_t *
482svn_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
520svn_error_t *
521svn_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 */
574static svn_boolean_t
575mtcc_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
605static svn_error_t *
606mtcc_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
665svn_error_t *
666svn_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
673svn_error_t *
674svn_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
707svn_error_t *
708svn_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 */
728struct 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 */
737static svn_error_t *
738mtcc_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
824svn_error_t *
825svn_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
943svn_error_t *
944svn_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
988svn_error_t *
989svn_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
1076static svn_error_t *
1077commit_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 */
1108static svn_error_t *
1109commit_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 */
1185static svn_error_t *
1186commit_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 */
1278static svn_error_t *
1279add_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
1358svn_error_t *
1359svn_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