1 /*
2  * mtcc.c -- Multi Command Context implementation. This allows
3  *           performing many operations without a working copy.
4  *
5  * ====================================================================
6  *    Licensed to the Apache Software Foundation (ASF) under one
7  *    or more contributor license agreements.  See the NOTICE file
8  *    distributed with this work for additional information
9  *    regarding copyright ownership.  The ASF licenses this file
10  *    to you under the Apache License, Version 2.0 (the
11  *    "License"); you may not use this file except in compliance
12  *    with the License.  You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  *    Unless required by applicable law or agreed to in writing,
17  *    software distributed under the License is distributed on an
18  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19  *    KIND, either express or implied.  See the License for the
20  *    specific language governing permissions and limitations
21  *    under the License.
22  * ====================================================================
23  */
24 
25 #include "svn_dirent_uri.h"
26 #include "svn_hash.h"
27 #include "svn_path.h"
28 #include "svn_props.h"
29 #include "svn_pools.h"
30 #include "svn_subst.h"
31 
32 #include "private/svn_client_mtcc.h"
33 
34 
35 #include "svn_private_config.h"
36 
37 #include "client.h"
38 
39 #include <assert.h>
40 
41 #define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
42 
43 /* The kind of operation to perform in an mtcc_op_t */
44 typedef enum mtcc_kind_t
45 {
46   OP_OPEN_DIR,
47   OP_OPEN_FILE,
48   OP_ADD_DIR,
49   OP_ADD_FILE,
50   OP_DELETE
51 } mtcc_kind_t;
52 
53 typedef struct mtcc_op_t
54 {
55   const char *name;                 /* basename of operation */
56   mtcc_kind_t kind;                 /* editor operation */
57 
58   apr_array_header_t *children;     /* List of mtcc_op_t * */
59 
60   const char *src_relpath;              /* For ADD_DIR, ADD_FILE */
61   svn_revnum_t src_rev;                 /* For ADD_DIR, ADD_FILE */
62   svn_stream_t *src_stream;             /* For ADD_FILE, OPEN_FILE */
63   svn_checksum_t *src_checksum;         /* For ADD_FILE, OPEN_FILE */
64   svn_stream_t *base_stream;            /* For ADD_FILE, OPEN_FILE */
65   const svn_checksum_t *base_checksum;  /* For ADD_FILE, OPEN_FILE */
66 
67   apr_array_header_t *prop_mods;        /* For all except DELETE
68                                            List of svn_prop_t */
69 
70   svn_boolean_t performed_stat;         /* Verified kind with repository */
71 } mtcc_op_t;
72 
73 /* Check if the mtcc doesn't contain any modifications yet */
74 #define MTCC_UNMODIFIED(mtcc)                                               \
75     ((mtcc->root_op->kind == OP_OPEN_DIR                                    \
76                             || mtcc->root_op->kind == OP_OPEN_FILE)         \
77      && (mtcc->root_op->prop_mods == NULL                                   \
78                             || !mtcc->root_op->prop_mods->nelts)            \
79      && (mtcc->root_op->children == NULL                                    \
80                             || !mtcc->root_op->children->nelts))
81 
82 struct svn_client__mtcc_t
83 {
84   apr_pool_t *pool;
85   svn_revnum_t head_revision;
86   svn_revnum_t base_revision;
87 
88   svn_ra_session_t *ra_session;
89   svn_client_ctx_t *ctx;
90 
91   mtcc_op_t *root_op;
92 };
93 
94 static mtcc_op_t *
mtcc_op_create(const char * name,svn_boolean_t add,svn_boolean_t directory,apr_pool_t * result_pool)95 mtcc_op_create(const char *name,
96                svn_boolean_t add,
97                svn_boolean_t directory,
98                apr_pool_t *result_pool)
99 {
100   mtcc_op_t *op;
101 
102   op = apr_pcalloc(result_pool, sizeof(*op));
103   op->name = name ? apr_pstrdup(result_pool, name) : "";
104 
105   if (add)
106     op->kind = directory ? OP_ADD_DIR : OP_ADD_FILE;
107   else
108     op->kind = directory ? OP_OPEN_DIR : OP_OPEN_FILE;
109 
110   if (directory)
111     op->children = apr_array_make(result_pool, 4, sizeof(mtcc_op_t *));
112 
113   op->src_rev = SVN_INVALID_REVNUM;
114 
115   return op;
116 }
117 
118 static svn_error_t *
mtcc_op_find(mtcc_op_t ** op,svn_boolean_t * created,const char * relpath,mtcc_op_t * base_op,svn_boolean_t find_existing,svn_boolean_t find_deletes,svn_boolean_t create_file,apr_pool_t * result_pool,apr_pool_t * scratch_pool)119 mtcc_op_find(mtcc_op_t **op,
120              svn_boolean_t *created,
121              const char *relpath,
122              mtcc_op_t *base_op,
123              svn_boolean_t find_existing,
124              svn_boolean_t find_deletes,
125              svn_boolean_t create_file,
126              apr_pool_t *result_pool,
127              apr_pool_t *scratch_pool)
128 {
129   const char *name;
130   const char *child;
131   int i;
132 
133   assert(svn_relpath_is_canonical(relpath));
134   if (created)
135     *created = FALSE;
136 
137   if (SVN_PATH_IS_EMPTY(relpath))
138     {
139       if (find_existing)
140         *op = base_op;
141       else
142         *op = NULL;
143 
144       return SVN_NO_ERROR;
145     }
146 
147   child = strchr(relpath, '/');
148 
149   if (child)
150     {
151       name = apr_pstrmemdup(scratch_pool, relpath, (child-relpath));
152       child++; /* Skip '/' */
153     }
154   else
155     name = relpath;
156 
157   if (!base_op->children)
158     {
159       if (!created)
160         {
161           *op = NULL;
162            return SVN_NO_ERROR;
163         }
164       else
165         return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
166                                  _("Can't operate on '%s' because '%s' is not a "
167                                    "directory"),
168                                  name, base_op->name);
169     }
170 
171   for (i = base_op->children->nelts-1; i >= 0 ; i--)
172     {
173       mtcc_op_t *cop;
174 
175       cop = APR_ARRAY_IDX(base_op->children, i, mtcc_op_t *);
176 
177       if (! strcmp(cop->name, name)
178           && (find_deletes || cop->kind != OP_DELETE))
179         {
180           return svn_error_trace(
181                         mtcc_op_find(op, created, child ? child : "", cop,
182                                      find_existing, find_deletes, create_file,
183                                      result_pool, scratch_pool));
184         }
185     }
186 
187   if (!created)
188     {
189       *op = NULL;
190       return SVN_NO_ERROR;
191     }
192 
193   {
194     mtcc_op_t *cop;
195 
196     cop = mtcc_op_create(name, FALSE, child || !create_file, result_pool);
197 
198     APR_ARRAY_PUSH(base_op->children, mtcc_op_t *) = cop;
199 
200     if (!child)
201       {
202         *op = cop;
203         *created = TRUE;
204         return SVN_NO_ERROR;
205       }
206 
207     return svn_error_trace(
208                 mtcc_op_find(op, created, child, cop, find_existing,
209                              find_deletes, create_file,
210                              result_pool, scratch_pool));
211   }
212 }
213 
214 /* Gets the original repository location of RELPATH, checking things
215    like copies, moves, etc.  */
216 static svn_error_t *
get_origin(svn_boolean_t * done,const char ** origin_relpath,svn_revnum_t * rev,mtcc_op_t * op,const char * relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)217 get_origin(svn_boolean_t *done,
218            const char **origin_relpath,
219            svn_revnum_t *rev,
220            mtcc_op_t *op,
221            const char *relpath,
222            apr_pool_t *result_pool,
223            apr_pool_t *scratch_pool)
224 {
225   const char *child;
226   const char *name;
227   if (SVN_PATH_IS_EMPTY(relpath))
228     {
229       if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
230         *done = TRUE;
231       *origin_relpath = op->src_relpath
232                                 ? apr_pstrdup(result_pool, op->src_relpath)
233                                 : NULL;
234       *rev = op->src_rev;
235       return SVN_NO_ERROR;
236     }
237 
238   child = strchr(relpath, '/');
239   if (child)
240     {
241       name = apr_pstrmemdup(scratch_pool, relpath, child-relpath);
242       child++; /* Skip '/' */
243     }
244   else
245     name = relpath;
246 
247   if (op->children && op->children->nelts)
248     {
249       int i;
250 
251       for (i = op->children->nelts-1; i >= 0; i--)
252         {
253            mtcc_op_t *cop;
254 
255            cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
256 
257            if (! strcmp(cop->name, name))
258             {
259               if (cop->kind == OP_DELETE)
260                 {
261                   *done = TRUE;
262                   return SVN_NO_ERROR;
263                 }
264 
265               SVN_ERR(get_origin(done, origin_relpath, rev,
266                                  cop, child ? child : "",
267                                  result_pool, scratch_pool));
268 
269               if (*origin_relpath || *done)
270                 return SVN_NO_ERROR;
271 
272               break;
273             }
274         }
275     }
276 
277   if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
278     {
279       *done = TRUE;
280       if (op->src_relpath)
281         {
282           *origin_relpath = svn_relpath_join(op->src_relpath, relpath,
283                                              result_pool);
284           *rev = op->src_rev;
285         }
286     }
287 
288   return SVN_NO_ERROR;
289 }
290 
291 /* Obtains the original repository location for an mtcc relpath as
292    *ORIGIN_RELPATH @ *REV, if it has one. If it has not and IGNORE_ENOENT
293    is TRUE report *ORIGIN_RELPATH as NULL, otherwise return an error */
294 static svn_error_t *
mtcc_get_origin(const char ** origin_relpath,svn_revnum_t * rev,const char * relpath,svn_boolean_t ignore_enoent,svn_client__mtcc_t * mtcc,apr_pool_t * result_pool,apr_pool_t * scratch_pool)295 mtcc_get_origin(const char **origin_relpath,
296                 svn_revnum_t *rev,
297                 const char *relpath,
298                 svn_boolean_t ignore_enoent,
299                 svn_client__mtcc_t *mtcc,
300                 apr_pool_t *result_pool,
301                 apr_pool_t *scratch_pool)
302 {
303   svn_boolean_t done = FALSE;
304 
305   *origin_relpath = NULL;
306   *rev = SVN_INVALID_REVNUM;
307 
308   SVN_ERR(get_origin(&done, origin_relpath, rev, mtcc->root_op, relpath,
309                      result_pool, scratch_pool));
310 
311   if (!*origin_relpath && !done)
312     {
313       *origin_relpath = apr_pstrdup(result_pool, relpath);
314       *rev = mtcc->base_revision;
315     }
316   else if (!ignore_enoent)
317     {
318       return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
319                                _("No origin found for node at '%s'"),
320                                relpath);
321     }
322 
323   return SVN_NO_ERROR;
324 }
325 
326 svn_error_t *
svn_client__mtcc_create(svn_client__mtcc_t ** mtcc,const char * anchor_url,svn_revnum_t base_revision,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)327 svn_client__mtcc_create(svn_client__mtcc_t **mtcc,
328                         const char *anchor_url,
329                         svn_revnum_t base_revision,
330                         svn_client_ctx_t *ctx,
331                         apr_pool_t *result_pool,
332                         apr_pool_t *scratch_pool)
333 {
334   apr_pool_t *mtcc_pool;
335 
336   mtcc_pool = svn_pool_create(result_pool);
337 
338   *mtcc = apr_pcalloc(mtcc_pool, sizeof(**mtcc));
339   (*mtcc)->pool = mtcc_pool;
340 
341   (*mtcc)->root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc_pool);
342 
343   (*mtcc)->ctx = ctx;
344 
345   SVN_ERR(svn_client_open_ra_session2(&(*mtcc)->ra_session, anchor_url,
346                                       NULL /* wri_abspath */, ctx,
347                                       mtcc_pool, scratch_pool));
348 
349   SVN_ERR(svn_ra_get_latest_revnum((*mtcc)->ra_session, &(*mtcc)->head_revision,
350                                    scratch_pool));
351 
352   if (SVN_IS_VALID_REVNUM(base_revision))
353     (*mtcc)->base_revision = base_revision;
354   else
355     (*mtcc)->base_revision = (*mtcc)->head_revision;
356 
357   if ((*mtcc)->base_revision > (*mtcc)->head_revision)
358     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
359                              _("No such revision %ld (HEAD is %ld)"),
360                              base_revision, (*mtcc)->head_revision);
361 
362   return SVN_NO_ERROR;
363 }
364 
365 static svn_error_t *
update_copy_src(mtcc_op_t * op,const char * add_relpath,apr_pool_t * result_pool)366 update_copy_src(mtcc_op_t *op,
367                 const char *add_relpath,
368                 apr_pool_t *result_pool)
369 {
370   int i;
371 
372   if (op->src_relpath)
373     op->src_relpath = svn_relpath_join(add_relpath, op->src_relpath,
374                                        result_pool);
375 
376   if (!op->children)
377     return SVN_NO_ERROR;
378 
379   for (i = 0; i < op->children->nelts; i++)
380     {
381       mtcc_op_t *cop;
382 
383       cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
384 
385       SVN_ERR(update_copy_src(cop, add_relpath, result_pool));
386     }
387 
388   return SVN_NO_ERROR;
389 }
390 
391 static svn_error_t *
mtcc_reparent(const char * new_anchor_url,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)392 mtcc_reparent(const char *new_anchor_url,
393               svn_client__mtcc_t *mtcc,
394               apr_pool_t *scratch_pool)
395 {
396   const char *session_url;
397   const char *up;
398 
399   SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url,
400                                  scratch_pool));
401 
402   up = svn_uri_skip_ancestor(new_anchor_url, session_url, scratch_pool);
403 
404   if (! up)
405     {
406       return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
407                                _("'%s' is not an ancestor of  '%s'"),
408                                new_anchor_url, session_url);
409     }
410   else if (!*up)
411     {
412       return SVN_NO_ERROR; /* Same url */
413     }
414 
415   /* Update copy origins recursively...:( */
416   SVN_ERR(update_copy_src(mtcc->root_op, up, mtcc->pool));
417 
418   SVN_ERR(svn_ra_reparent(mtcc->ra_session, new_anchor_url, scratch_pool));
419 
420   /* Create directory open operations for new ancestors */
421   while (*up)
422     {
423       mtcc_op_t *root_op;
424 
425       mtcc->root_op->name = svn_relpath_basename(up, mtcc->pool);
426       up = svn_relpath_dirname(up, scratch_pool);
427 
428       root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc->pool);
429 
430       APR_ARRAY_PUSH(root_op->children, mtcc_op_t *) = mtcc->root_op;
431 
432       mtcc->root_op = root_op;
433     }
434 
435   return SVN_NO_ERROR;
436 }
437 
438 /* Check if it is safe to create a new node at NEW_RELPATH. Return a proper
439    error if it is not */
440 static svn_error_t *
mtcc_verify_create(svn_client__mtcc_t * mtcc,const char * new_relpath,apr_pool_t * scratch_pool)441 mtcc_verify_create(svn_client__mtcc_t *mtcc,
442                    const char *new_relpath,
443                    apr_pool_t *scratch_pool)
444 {
445   svn_node_kind_t kind;
446 
447   if (*new_relpath || !MTCC_UNMODIFIED(mtcc))
448     {
449       mtcc_op_t *op;
450 
451       SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, FALSE,
452                            FALSE, mtcc->pool, scratch_pool));
453 
454       if (op)
455         return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
456                                  _("Path '%s' already exists, or was created "
457                                    "by an earlier operation"),
458                                  new_relpath);
459 
460       SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE,
461                            FALSE, mtcc->pool, scratch_pool));
462 
463       if (op)
464         return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */
465     }
466 
467   /* mod_dav_svn used to allow overwriting existing directories. Let's hide
468      that for users of this api */
469   SVN_ERR(svn_client__mtcc_check_path(&kind, new_relpath, FALSE,
470                                       mtcc, scratch_pool));
471 
472   if (kind != svn_node_none)
473     return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
474                              _("Path '%s' already exists"),
475                              new_relpath);
476 
477   return SVN_NO_ERROR;
478 }
479 
480 
481 svn_error_t *
svn_client__mtcc_add_add_file(const char * relpath,svn_stream_t * src_stream,const svn_checksum_t * src_checksum,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)482 svn_client__mtcc_add_add_file(const char *relpath,
483                               svn_stream_t *src_stream,
484                               const svn_checksum_t *src_checksum,
485                               svn_client__mtcc_t *mtcc,
486                               apr_pool_t *scratch_pool)
487 {
488   mtcc_op_t *op;
489   svn_boolean_t created;
490   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
491 
492   SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
493 
494   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
495     {
496       /* Turn the root operation into a file addition */
497       op = mtcc->root_op;
498     }
499   else
500     {
501       SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
502                            TRUE, mtcc->pool, scratch_pool));
503 
504       if (!op || !created)
505         {
506           return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
507                                    _("Can't add file at '%s'"),
508                                    relpath);
509         }
510     }
511 
512   op->kind = OP_ADD_FILE;
513   op->src_stream = src_stream;
514   op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
515                                   : NULL;
516 
517   return SVN_NO_ERROR;
518 }
519 
520 svn_error_t *
svn_client__mtcc_add_copy(const char * src_relpath,svn_revnum_t revision,const char * dst_relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)521 svn_client__mtcc_add_copy(const char *src_relpath,
522                           svn_revnum_t revision,
523                           const char *dst_relpath,
524                           svn_client__mtcc_t *mtcc,
525                           apr_pool_t *scratch_pool)
526 {
527   mtcc_op_t *op;
528   svn_boolean_t created;
529   svn_node_kind_t kind;
530 
531   SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath)
532                  && svn_relpath_is_canonical(dst_relpath));
533 
534   if (! SVN_IS_VALID_REVNUM(revision))
535     revision = mtcc->head_revision;
536   else if (revision > mtcc->head_revision)
537     {
538       return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
539                                _("No such revision %ld"), revision);
540     }
541 
542   SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool));
543 
544   /* Subversion requires the kind of a copy */
545   SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind,
546                             scratch_pool));
547 
548   if (kind != svn_node_dir && kind != svn_node_file)
549     {
550       return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
551                                _("Path '%s' not found in revision %ld"),
552                                src_relpath, revision);
553     }
554 
555   SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE,
556                        (kind == svn_node_file), mtcc->pool, scratch_pool));
557 
558   if (!op || !created)
559     {
560       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
561                                _("Can't add node at '%s'"),
562                                dst_relpath);
563     }
564 
565   op->kind = (kind == svn_node_file) ? OP_ADD_FILE : OP_ADD_DIR;
566   op->src_relpath = apr_pstrdup(mtcc->pool, src_relpath);
567   op->src_rev = revision;
568 
569   return SVN_NO_ERROR;
570 }
571 
572 /* Check if this operation contains at least one change that is not a
573    plain delete */
574 static svn_boolean_t
mtcc_op_contains_non_delete(const mtcc_op_t * op)575 mtcc_op_contains_non_delete(const mtcc_op_t *op)
576 {
577   if (op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE
578       && op->kind != OP_DELETE)
579     {
580       return TRUE;
581     }
582 
583   if (op->prop_mods && op->prop_mods->nelts)
584     return TRUE;
585 
586   if (op->src_stream)
587     return TRUE;
588 
589   if (op->children)
590     {
591       int i;
592 
593       for (i = 0; i < op->children->nelts; i++)
594         {
595           const mtcc_op_t *c_op = APR_ARRAY_IDX(op->children, i,
596                                                 const mtcc_op_t *);
597 
598           if (mtcc_op_contains_non_delete(c_op))
599             return TRUE;
600         }
601     }
602   return FALSE;
603 }
604 
605 static svn_error_t *
mtcc_add_delete(const char * relpath,svn_boolean_t for_move,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)606 mtcc_add_delete(const char *relpath,
607                 svn_boolean_t for_move,
608                 svn_client__mtcc_t *mtcc,
609                 apr_pool_t *scratch_pool)
610 {
611   mtcc_op_t *op;
612   svn_boolean_t created;
613   svn_node_kind_t kind;
614 
615   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
616 
617   SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
618                                       mtcc, scratch_pool));
619 
620   if (kind == svn_node_none)
621     return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
622                              _("Can't delete node at '%s' as it "
623                                 "does not exist"),
624                              relpath);
625 
626   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
627     {
628       /* Turn root operation into delete */
629       op = mtcc->root_op;
630     }
631   else
632     {
633       SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE,
634                            TRUE, mtcc->pool, scratch_pool));
635 
636       if (!for_move && !op && !created)
637         {
638           /* Allow deleting directories, that are unmodified except for
639               one or more deleted descendants */
640 
641           SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE,
642                   FALSE, FALSE, mtcc->pool, scratch_pool));
643 
644           if (op && mtcc_op_contains_non_delete(op))
645             op = NULL;
646           else
647             created = TRUE;
648         }
649 
650       if (!op || !created)
651         {
652           return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
653                                    _("Can't delete node at '%s'"),
654                                    relpath);
655         }
656     }
657 
658   op->kind = OP_DELETE;
659   op->children = NULL;
660   op->prop_mods = NULL;
661 
662   return SVN_NO_ERROR;
663 }
664 
665 svn_error_t *
svn_client__mtcc_add_delete(const char * relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)666 svn_client__mtcc_add_delete(const char *relpath,
667                             svn_client__mtcc_t *mtcc,
668                             apr_pool_t *scratch_pool)
669 {
670   return svn_error_trace(mtcc_add_delete(relpath, FALSE, mtcc, scratch_pool));
671 }
672 
673 svn_error_t *
svn_client__mtcc_add_mkdir(const char * relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)674 svn_client__mtcc_add_mkdir(const char *relpath,
675                            svn_client__mtcc_t *mtcc,
676                            apr_pool_t *scratch_pool)
677 {
678   mtcc_op_t *op;
679   svn_boolean_t created;
680   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
681 
682   SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
683 
684   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
685     {
686       /* Turn the root of the operation in an MKDIR */
687       mtcc->root_op->kind = OP_ADD_DIR;
688 
689       return SVN_NO_ERROR;
690     }
691 
692   SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
693                        FALSE, mtcc->pool, scratch_pool));
694 
695   if (!op || !created)
696     {
697       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
698                                _("Can't create directory at '%s'"),
699                                relpath);
700     }
701 
702   op->kind = OP_ADD_DIR;
703 
704   return SVN_NO_ERROR;
705 }
706 
707 svn_error_t *
svn_client__mtcc_add_move(const char * src_relpath,const char * dst_relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)708 svn_client__mtcc_add_move(const char *src_relpath,
709                           const char *dst_relpath,
710                           svn_client__mtcc_t *mtcc,
711                           apr_pool_t *scratch_pool)
712 {
713   const char *origin_relpath;
714   svn_revnum_t origin_rev;
715 
716   SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
717                           src_relpath, FALSE, mtcc,
718                           scratch_pool, scratch_pool));
719 
720   SVN_ERR(svn_client__mtcc_add_copy(src_relpath, mtcc->base_revision,
721                                     dst_relpath, mtcc, scratch_pool));
722   SVN_ERR(mtcc_add_delete(src_relpath, TRUE, mtcc, scratch_pool));
723 
724   return SVN_NO_ERROR;
725 }
726 
727 /* Baton for mtcc_prop_getter */
728 struct mtcc_prop_get_baton
729 {
730   svn_client__mtcc_t *mtcc;
731   const char *relpath;
732   svn_cancel_func_t cancel_func;
733   void *cancel_baton;
734 };
735 
736 /* Implements svn_wc_canonicalize_svn_prop_get_file_t */
737 static svn_error_t *
mtcc_prop_getter(const svn_string_t ** mime_type,svn_stream_t * stream,void * baton,apr_pool_t * pool)738 mtcc_prop_getter(const svn_string_t **mime_type,
739                  svn_stream_t *stream,
740                  void *baton,
741                  apr_pool_t *pool)
742 {
743   struct mtcc_prop_get_baton *mpgb = baton;
744   const char *origin_relpath;
745   svn_revnum_t origin_rev;
746   apr_hash_t *props = NULL;
747 
748   mtcc_op_t *op;
749 
750   if (mime_type)
751     *mime_type = NULL;
752 
753   /* Check if we have the information locally */
754   SVN_ERR(mtcc_op_find(&op, NULL, mpgb->relpath, mpgb->mtcc->root_op, TRUE,
755                        FALSE, FALSE, pool, pool));
756 
757   if (op)
758     {
759       if (mime_type)
760         {
761           int i;
762 
763           for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++)
764             {
765               const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i,
766                                                      svn_prop_t);
767 
768               if (! strcmp(mod->name, SVN_PROP_MIME_TYPE))
769                 {
770                   *mime_type = svn_string_dup(mod->value, pool);
771                   mime_type = NULL;
772                   break;
773                 }
774             }
775         }
776 
777       if (stream && op->src_stream)
778         {
779           svn_stream_mark_t *mark;
780           svn_error_t *err;
781 
782           /* Is the source stream capable of being read multiple times? */
783           err = svn_stream_mark(op->src_stream, &mark, pool);
784 
785           if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED)
786             return svn_error_trace(err);
787           svn_error_clear(err);
788 
789           if (!err)
790             {
791               err = svn_stream_copy3(svn_stream_disown(op->src_stream, pool),
792                                      svn_stream_disown(stream, pool),
793                                      mpgb->cancel_func, mpgb->cancel_baton,
794                                      pool);
795 
796               SVN_ERR(svn_error_compose_create(
797                             err,
798                             svn_stream_seek(op->src_stream, mark)));
799             }
800           /* else: ### Create tempfile? */
801 
802           stream = NULL; /* Stream is handled */
803         }
804     }
805 
806   if (!stream && !mime_type)
807     return SVN_NO_ERROR;
808 
809   SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE,
810                           mpgb->mtcc, pool, pool));
811 
812   if (!origin_relpath)
813     return SVN_NO_ERROR; /* Nothing to fetch at repository */
814 
815   SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev,
816                           stream, NULL, mime_type ? &props : NULL, pool));
817 
818   if (mime_type && props)
819     *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
820 
821   return SVN_NO_ERROR;
822 }
823 
824 svn_error_t *
svn_client__mtcc_add_propset(const char * relpath,const char * propname,const svn_string_t * propval,svn_boolean_t skip_checks,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)825 svn_client__mtcc_add_propset(const char *relpath,
826                              const char *propname,
827                              const svn_string_t *propval,
828                              svn_boolean_t skip_checks,
829                              svn_client__mtcc_t *mtcc,
830                              apr_pool_t *scratch_pool)
831 {
832   mtcc_op_t *op;
833   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
834 
835   if (! svn_prop_name_is_valid(propname))
836     return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
837                              _("Bad property name: '%s'"), propname);
838 
839   if (svn_prop_is_known_svn_rev_prop(propname))
840     return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
841                              _("Revision property '%s' not allowed "
842                                "in this context"), propname);
843 
844   if (svn_property_kind2(propname) == svn_prop_wc_kind)
845     return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
846                              _("'%s' is a wcprop, thus not accessible "
847                                "to clients"), propname);
848 
849   if (!skip_checks && svn_prop_needs_translation(propname))
850     {
851       svn_string_t *translated_value;
852       SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
853                                             NULL, propval,
854                                             NULL, FALSE,
855                                             scratch_pool, scratch_pool),
856                 _("Error normalizing property value"));
857 
858       propval = translated_value;
859     }
860 
861   if (propval && svn_prop_is_svn_prop(propname))
862     {
863       struct mtcc_prop_get_baton mpbg;
864       svn_node_kind_t kind;
865       SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, mtcc,
866                                           scratch_pool));
867 
868       mpbg.mtcc = mtcc;
869       mpbg.relpath = relpath;
870       mpbg.cancel_func = mtcc->ctx->cancel_func;
871       mpbg.cancel_baton = mtcc->ctx->cancel_baton;
872 
873       SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval,
874                                            relpath, kind, skip_checks,
875                                            mtcc_prop_getter, &mpbg,
876                                            scratch_pool));
877     }
878 
879   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
880     {
881       svn_node_kind_t kind;
882 
883       /* Probing the node for an unmodified root will fix the node type to
884          a file if necessary */
885 
886       SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
887                                           mtcc, scratch_pool));
888 
889       if (kind == svn_node_none)
890         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
891                                  _("Can't set properties at not existing '%s'"),
892                                    relpath);
893 
894       op = mtcc->root_op;
895     }
896   else
897     {
898       SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
899                            FALSE, mtcc->pool, scratch_pool));
900 
901       if (!op)
902         {
903           svn_node_kind_t kind;
904           svn_boolean_t created;
905 
906           /* ### TODO: Check if this node is within a newly copied directory,
907                        and update origin values accordingly */
908 
909           SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
910                                               mtcc, scratch_pool));
911 
912           if (kind == svn_node_none)
913             return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
914                                      _("Can't set properties at not existing '%s'"),
915                                      relpath);
916 
917           SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
918                                (kind != svn_node_dir),
919                                mtcc->pool, scratch_pool));
920 
921           SVN_ERR_ASSERT(op != NULL);
922         }
923     }
924 
925   if (!op->prop_mods)
926       op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t));
927 
928   {
929     svn_prop_t propchange;
930     propchange.name = apr_pstrdup(mtcc->pool, propname);
931 
932     if (propval)
933       propchange.value = svn_string_dup(propval, mtcc->pool);
934     else
935       propchange.value = NULL;
936 
937     APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange;
938   }
939 
940   return SVN_NO_ERROR;
941 }
942 
943 svn_error_t *
svn_client__mtcc_add_update_file(const char * relpath,svn_stream_t * src_stream,const svn_checksum_t * src_checksum,svn_stream_t * base_stream,const svn_checksum_t * base_checksum,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)944 svn_client__mtcc_add_update_file(const char *relpath,
945                                  svn_stream_t *src_stream,
946                                  const svn_checksum_t *src_checksum,
947                                  svn_stream_t *base_stream,
948                                  const svn_checksum_t *base_checksum,
949                                  svn_client__mtcc_t *mtcc,
950                                  apr_pool_t *scratch_pool)
951 {
952   mtcc_op_t *op;
953   svn_boolean_t created;
954   svn_node_kind_t kind;
955   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
956 
957   SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
958                                       mtcc, scratch_pool));
959 
960   if (kind != svn_node_file)
961     return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
962                              _("Can't update '%s' because it is not a file"),
963                              relpath);
964 
965   SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
966                        TRUE, mtcc->pool, scratch_pool));
967 
968   if (!op
969       || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE)
970       || (op->src_stream != NULL))
971     {
972       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
973                                _("Can't update file at '%s'"), relpath);
974     }
975 
976   op->src_stream = src_stream;
977   op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
978                                   : NULL;
979 
980   op->base_stream = base_stream;
981   op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum,
982                                                        mtcc->pool)
983                                     : NULL;
984 
985   return SVN_NO_ERROR;
986 }
987 
988 svn_error_t *
svn_client__mtcc_check_path(svn_node_kind_t * kind,const char * relpath,svn_boolean_t check_repository,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)989 svn_client__mtcc_check_path(svn_node_kind_t *kind,
990                             const char *relpath,
991                             svn_boolean_t check_repository,
992                             svn_client__mtcc_t *mtcc,
993                             apr_pool_t *scratch_pool)
994 {
995   const char *origin_relpath;
996   svn_revnum_t origin_rev;
997   mtcc_op_t *op;
998 
999   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
1000 
1001   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)
1002       && !mtcc->root_op->performed_stat)
1003     {
1004       /* We know nothing about the root. Perhaps it is a file? */
1005       SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision,
1006                                 kind, scratch_pool));
1007 
1008       mtcc->root_op->performed_stat = TRUE;
1009       if (*kind == svn_node_file)
1010         {
1011           mtcc->root_op->kind = OP_OPEN_FILE;
1012           mtcc->root_op->children = NULL;
1013         }
1014       return SVN_NO_ERROR;
1015     }
1016 
1017   SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
1018                        FALSE, mtcc->pool, scratch_pool));
1019 
1020   if (!op || (check_repository && !op->performed_stat))
1021     {
1022       SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
1023                               relpath, TRUE, mtcc,
1024                               scratch_pool, scratch_pool));
1025 
1026       if (!origin_relpath)
1027         *kind = svn_node_none;
1028       else
1029         SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath,
1030                                   origin_rev, kind, scratch_pool));
1031 
1032       if (op && *kind == svn_node_dir)
1033         {
1034           if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1035             op->performed_stat = TRUE;
1036           else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1037             return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
1038                                      _("Can't perform file operation "
1039                                        "on '%s' as it is not a file"),
1040                                      relpath);
1041         }
1042       else if (op && *kind == svn_node_file)
1043         {
1044           if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1045             op->performed_stat = TRUE;
1046           else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1047             return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1048                                      _("Can't perform directory operation "
1049                                        "on '%s' as it is not a directory"),
1050                                      relpath);
1051         }
1052       else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE))
1053         {
1054           return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1055                                    _("Can't open '%s' as it does not exist"),
1056                                    relpath);
1057         }
1058 
1059       return SVN_NO_ERROR;
1060     }
1061 
1062   /* op != NULL */
1063   if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1064     {
1065       *kind = svn_node_dir;
1066       return SVN_NO_ERROR;
1067     }
1068   else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1069     {
1070       *kind = svn_node_file;
1071       return SVN_NO_ERROR;
1072     }
1073   SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */
1074 }
1075 
1076 static svn_error_t *
commit_properties(const svn_delta_editor_t * editor,const mtcc_op_t * op,void * node_baton,apr_pool_t * scratch_pool)1077 commit_properties(const svn_delta_editor_t *editor,
1078                   const mtcc_op_t *op,
1079                   void *node_baton,
1080                   apr_pool_t *scratch_pool)
1081 {
1082   int i;
1083   apr_pool_t *iterpool;
1084 
1085   if (!op->prop_mods || op->prop_mods->nelts == 0)
1086     return SVN_NO_ERROR;
1087 
1088   iterpool = svn_pool_create(scratch_pool);
1089   for (i = 0; i < op->prop_mods->nelts; i++)
1090     {
1091       const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t);
1092 
1093       svn_pool_clear(iterpool);
1094 
1095       if (op->kind == OP_ADD_DIR || op->kind == OP_OPEN_DIR)
1096         SVN_ERR(editor->change_dir_prop(node_baton, mod->name, mod->value,
1097                                         iterpool));
1098       else if (op->kind == OP_ADD_FILE || op->kind == OP_OPEN_FILE)
1099         SVN_ERR(editor->change_file_prop(node_baton, mod->name, mod->value,
1100                                          iterpool));
1101     }
1102 
1103   svn_pool_destroy(iterpool);
1104   return SVN_NO_ERROR;
1105 }
1106 
1107 /* Handles updating a file to a delta editor and then closes it */
1108 static svn_error_t *
commit_file(const svn_delta_editor_t * editor,mtcc_op_t * op,void * file_baton,const char * session_url,const char * relpath,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1109 commit_file(const svn_delta_editor_t *editor,
1110             mtcc_op_t *op,
1111             void *file_baton,
1112             const char *session_url,
1113             const char *relpath,
1114             svn_client_ctx_t *ctx,
1115             apr_pool_t *scratch_pool)
1116 {
1117   const char *text_checksum = NULL;
1118   svn_checksum_t *src_checksum = op->src_checksum;
1119   SVN_ERR(commit_properties(editor, op, file_baton, scratch_pool));
1120 
1121   if (op->src_stream)
1122     {
1123       const char *base_checksum = NULL;
1124       apr_pool_t *txdelta_pool = scratch_pool;
1125       svn_txdelta_window_handler_t window_handler;
1126       void *handler_baton;
1127       svn_stream_t *src_stream = op->src_stream;
1128 
1129       if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5)
1130         base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool);
1131 
1132       /* ### TODO: Future enhancement: Allocate in special pool and send
1133                    files after the true edit operation, like a wc commit */
1134       SVN_ERR(editor->apply_textdelta(file_baton, base_checksum, txdelta_pool,
1135                                       &window_handler, &handler_baton));
1136 
1137       if (ctx->notify_func2)
1138         {
1139           svn_wc_notify_t *notify;
1140 
1141           notify = svn_wc_create_notify_url(
1142                             svn_path_url_add_component2(session_url, relpath,
1143                                                         scratch_pool),
1144                             svn_wc_notify_commit_postfix_txdelta,
1145                             scratch_pool);
1146 
1147           notify->path = relpath;
1148           notify->kind = svn_node_file;
1149 
1150           ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
1151         }
1152 
1153       if (window_handler != svn_delta_noop_window_handler)
1154         {
1155           if (!src_checksum || src_checksum->kind != svn_checksum_md5)
1156             src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL,
1157                                                  svn_checksum_md5,
1158                                                  TRUE, scratch_pool);
1159 
1160           if (!op->base_stream)
1161             SVN_ERR(svn_txdelta_send_stream(src_stream,
1162                                             window_handler, handler_baton, NULL,
1163                                             scratch_pool));
1164           else
1165             SVN_ERR(svn_txdelta_run(op->base_stream, src_stream,
1166                                     window_handler, handler_baton,
1167                                     svn_checksum_md5, NULL,
1168                                     ctx->cancel_func, ctx->cancel_baton,
1169                                     scratch_pool, scratch_pool));
1170         }
1171 
1172       SVN_ERR(svn_stream_close(src_stream));
1173       if (op->base_stream)
1174         SVN_ERR(svn_stream_close(op->base_stream));
1175     }
1176 
1177   if (src_checksum && src_checksum->kind == svn_checksum_md5)
1178     text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool);
1179 
1180   return svn_error_trace(editor->close_file(file_baton, text_checksum,
1181                                             scratch_pool));
1182 }
1183 
1184 /* Handles updating a directory to a delta editor and then closes it */
1185 static svn_error_t *
commit_directory(const svn_delta_editor_t * editor,mtcc_op_t * op,const char * relpath,svn_revnum_t base_rev,void * dir_baton,const char * session_url,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1186 commit_directory(const svn_delta_editor_t *editor,
1187                  mtcc_op_t *op,
1188                  const char *relpath,
1189                  svn_revnum_t base_rev,
1190                  void *dir_baton,
1191                  const char *session_url,
1192                  svn_client_ctx_t *ctx,
1193                  apr_pool_t *scratch_pool)
1194 {
1195   SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool));
1196 
1197   if (op->children && op->children->nelts > 0)
1198     {
1199       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1200       int i;
1201 
1202       for (i = 0; i < op->children->nelts; i++)
1203         {
1204           mtcc_op_t *cop;
1205           const char * child_relpath;
1206           void *child_baton;
1207 
1208           cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1209 
1210           svn_pool_clear(iterpool);
1211 
1212           if (ctx->cancel_func)
1213             SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1214 
1215           child_relpath = svn_relpath_join(relpath, cop->name, iterpool);
1216 
1217           switch (cop->kind)
1218             {
1219               case OP_DELETE:
1220                 SVN_ERR(editor->delete_entry(child_relpath, base_rev,
1221                                              dir_baton, iterpool));
1222                 break;
1223 
1224               case OP_ADD_DIR:
1225                 SVN_ERR(editor->add_directory(child_relpath, dir_baton,
1226                                               cop->src_relpath
1227                                                 ? svn_path_url_add_component2(
1228                                                               session_url,
1229                                                               cop->src_relpath,
1230                                                               iterpool)
1231                                                 : NULL,
1232                                               cop->src_rev,
1233                                               iterpool, &child_baton));
1234                 SVN_ERR(commit_directory(editor, cop, child_relpath,
1235                                          SVN_INVALID_REVNUM, child_baton,
1236                                          session_url, ctx, iterpool));
1237                 break;
1238               case OP_OPEN_DIR:
1239                 SVN_ERR(editor->open_directory(child_relpath, dir_baton,
1240                                                base_rev, iterpool, &child_baton));
1241                 SVN_ERR(commit_directory(editor, cop, child_relpath,
1242                                          base_rev, child_baton,
1243                                          session_url, ctx, iterpool));
1244                 break;
1245 
1246               case OP_ADD_FILE:
1247                 SVN_ERR(editor->add_file(child_relpath, dir_baton,
1248                                          cop->src_relpath
1249                                             ? svn_path_url_add_component2(
1250                                                             session_url,
1251                                                             cop->src_relpath,
1252                                                             iterpool)
1253                                             : NULL,
1254                                          cop->src_rev,
1255                                          iterpool, &child_baton));
1256                 SVN_ERR(commit_file(editor, cop, child_baton,
1257                                     session_url, child_relpath, ctx, iterpool));
1258                 break;
1259               case OP_OPEN_FILE:
1260                 SVN_ERR(editor->open_file(child_relpath, dir_baton, base_rev,
1261                                           iterpool, &child_baton));
1262                 SVN_ERR(commit_file(editor, cop, child_baton,
1263                                     session_url, child_relpath, ctx, iterpool));
1264                 break;
1265 
1266               default:
1267                 SVN_ERR_MALFUNCTION();
1268             }
1269         }
1270     }
1271 
1272   return svn_error_trace(editor->close_directory(dir_baton, scratch_pool));
1273 }
1274 
1275 
1276 /* Helper function to recursively create svn_client_commit_item3_t items
1277    to provide to the log message callback */
1278 static svn_error_t *
add_commit_items(mtcc_op_t * op,const char * session_url,const char * url,apr_array_header_t * commit_items,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1279 add_commit_items(mtcc_op_t *op,
1280                  const char *session_url,
1281                  const char *url,
1282                  apr_array_header_t *commit_items,
1283                  apr_pool_t *result_pool,
1284                  apr_pool_t *scratch_pool)
1285 {
1286   if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE)
1287       || (op->prop_mods && op->prop_mods->nelts)
1288       || (op->src_stream))
1289     {
1290       svn_client_commit_item3_t *item;
1291 
1292       item = svn_client_commit_item3_create(result_pool);
1293 
1294       item->path = NULL;
1295       if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1296         item->kind = svn_node_dir;
1297       else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1298         item->kind = svn_node_file;
1299       else
1300         item->kind = svn_node_unknown;
1301 
1302       item->url = apr_pstrdup(result_pool, url);
1303       item->session_relpath = svn_uri_skip_ancestor(session_url, item->url,
1304                                                     result_pool);
1305 
1306       if (op->src_relpath)
1307         {
1308           item->copyfrom_url = svn_path_url_add_component2(session_url,
1309                                                            op->src_relpath,
1310                                                            result_pool);
1311           item->copyfrom_rev = op->src_rev;
1312           item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1313         }
1314       else
1315         item->copyfrom_rev = SVN_INVALID_REVNUM;
1316 
1317       if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
1318         item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1319       else if (op->kind == OP_DELETE)
1320         item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1321       /* else item->state_flags = 0; */
1322 
1323       if (op->prop_mods && op->prop_mods->nelts)
1324         item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1325 
1326       if (op->src_stream)
1327         item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
1328 
1329       APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1330     }
1331 
1332   if (op->children && op->children->nelts)
1333     {
1334       int i;
1335       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1336 
1337       for (i = 0; i < op->children->nelts; i++)
1338         {
1339           mtcc_op_t *cop;
1340           const char * child_url;
1341 
1342           cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1343 
1344           svn_pool_clear(iterpool);
1345 
1346           child_url = svn_path_url_add_component2(url, cop->name, iterpool);
1347 
1348           SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items,
1349                                    result_pool, iterpool));
1350         }
1351 
1352       svn_pool_destroy(iterpool);
1353     }
1354 
1355   return SVN_NO_ERROR;
1356 }
1357 
1358 svn_error_t *
svn_client__mtcc_commit(apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)1359 svn_client__mtcc_commit(apr_hash_t *revprop_table,
1360                         svn_commit_callback2_t commit_callback,
1361                         void *commit_baton,
1362                         svn_client__mtcc_t *mtcc,
1363                         apr_pool_t *scratch_pool)
1364 {
1365   const svn_delta_editor_t *editor;
1366   void *edit_baton;
1367   void *root_baton;
1368   apr_hash_t *commit_revprops;
1369   svn_node_kind_t kind;
1370   svn_error_t *err;
1371   const char *session_url;
1372   const char *log_msg;
1373 
1374   if (MTCC_UNMODIFIED(mtcc))
1375     {
1376       /* No changes -> no revision. Easy out */
1377       svn_pool_destroy(mtcc->pool);
1378       return SVN_NO_ERROR;
1379     }
1380 
1381   SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool));
1382 
1383   if (mtcc->root_op->kind != OP_OPEN_DIR)
1384     {
1385       const char *name;
1386 
1387       svn_uri_split(&session_url, &name, session_url, scratch_pool);
1388 
1389       if (*name)
1390         {
1391           SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool));
1392 
1393           SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool));
1394         }
1395     }
1396 
1397     /* Create new commit items and add them to the array. */
1398   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx))
1399     {
1400       svn_client_commit_item3_t *item;
1401       const char *tmp_file;
1402       apr_array_header_t *commit_items
1403                 = apr_array_make(scratch_pool, 32, sizeof(item));
1404 
1405       SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url,
1406                                commit_items, scratch_pool, scratch_pool));
1407 
1408       SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
1409                                       mtcc->ctx, scratch_pool));
1410 
1411       if (! log_msg)
1412         return SVN_NO_ERROR;
1413     }
1414   else
1415     log_msg = "";
1416 
1417   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1418                                            log_msg, mtcc->ctx, scratch_pool));
1419 
1420   /* Ugly corner case: The ra session might have died while we were waiting
1421      for the callback */
1422 
1423   err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind,
1424                           scratch_pool);
1425 
1426   if (err)
1427     {
1428       svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session,
1429                                                       session_url,
1430                                                       NULL, mtcc->ctx,
1431                                                       mtcc->pool,
1432                                                       scratch_pool);
1433 
1434       if (err2)
1435         {
1436           svn_pool_destroy(mtcc->pool);
1437           return svn_error_trace(svn_error_compose_create(err, err2));
1438         }
1439       svn_error_clear(err);
1440 
1441       SVN_ERR(svn_ra_check_path(mtcc->ra_session, "",
1442                                 mtcc->base_revision, &kind, scratch_pool));
1443     }
1444 
1445   if (kind != svn_node_dir)
1446     return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1447                              _("Can't commit to '%s' because it "
1448                                "is not a directory"),
1449                              session_url);
1450 
1451   /* Beware that the editor object must not live longer than the MTCC.
1452      Otherwise, txn objects etc. in EDITOR may live longer than their
1453      respective FS objects.  So, we can't use SCRATCH_POOL here. */
1454   SVN_ERR(svn_ra_get_commit_editor3(mtcc->ra_session, &editor, &edit_baton,
1455                                     commit_revprops,
1456                                     commit_callback, commit_baton,
1457                                     NULL /* lock_tokens */,
1458                                     FALSE /* keep_locks */,
1459                                     mtcc->pool));
1460 
1461   err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton);
1462 
1463   if (!err)
1464     err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision,
1465                            root_baton, session_url, mtcc->ctx, scratch_pool);
1466 
1467   if (!err)
1468     {
1469       if (mtcc->ctx->notify_func2)
1470         {
1471           svn_wc_notify_t *notify;
1472           notify = svn_wc_create_notify_url(session_url,
1473                                             svn_wc_notify_commit_finalizing,
1474                                             scratch_pool);
1475           mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify,
1476                                   scratch_pool);
1477         }
1478       SVN_ERR(editor->close_edit(edit_baton, scratch_pool));
1479     }
1480   else
1481     err = svn_error_compose_create(err,
1482                                    editor->abort_edit(edit_baton, scratch_pool));
1483 
1484   svn_pool_destroy(mtcc->pool);
1485 
1486   return svn_error_trace(err);
1487 }
1488