mtcc.c revision 299742
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"),
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
480svn_error_t *
481svn_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
519svn_error_t *
520svn_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
571svn_error_t *
572svn_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
616svn_error_t *
617svn_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
650svn_error_t *
651svn_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 */
671struct 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 */
680static svn_error_t *
681mtcc_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
766svn_error_t *
767svn_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
885svn_error_t *
886svn_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
930svn_error_t *
931svn_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
1018static svn_error_t *
1019commit_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 */
1050static svn_error_t *
1051commit_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 */
1127static svn_error_t *
1128commit_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 */
1220static svn_error_t *
1221add_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
1300svn_error_t *
1301svn_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}
1430