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