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"),
457                                  new_relpath);
458 
459       SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE,
460                            FALSE, mtcc->pool, scratch_pool));
461 
462       if (op)
463         return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */
464     }
465 
466   /* mod_dav_svn used to allow overwriting existing directories. Let's hide
467      that for users of this api */
468   SVN_ERR(svn_client__mtcc_check_path(&kind, new_relpath, FALSE,
469                                       mtcc, scratch_pool));
470 
471   if (kind != svn_node_none)
472     return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
473                              _("Path '%s' already exists"),
474                              new_relpath);
475 
476   return SVN_NO_ERROR;
477 }
478 
479 
480 svn_error_t *
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)481 svn_client__mtcc_add_add_file(const char *relpath,
482                               svn_stream_t *src_stream,
483                               const svn_checksum_t *src_checksum,
484                               svn_client__mtcc_t *mtcc,
485                               apr_pool_t *scratch_pool)
486 {
487   mtcc_op_t *op;
488   svn_boolean_t created;
489   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
490 
491   SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
492 
493   if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
494     {
495       /* Turn the root operation into a file addition */
496       op = mtcc->root_op;
497     }
498   else
499     {
500       SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
501                            TRUE, mtcc->pool, scratch_pool));
502 
503       if (!op || !created)
504         {
505           return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
506                                    _("Can't add file at '%s'"),
507                                    relpath);
508         }
509     }
510 
511   op->kind = OP_ADD_FILE;
512   op->src_stream = src_stream;
513   op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
514                                   : NULL;
515 
516   return SVN_NO_ERROR;
517 }
518 
519 svn_error_t *
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)520 svn_client__mtcc_add_copy(const char *src_relpath,
521                           svn_revnum_t revision,
522                           const char *dst_relpath,
523                           svn_client__mtcc_t *mtcc,
524                           apr_pool_t *scratch_pool)
525 {
526   mtcc_op_t *op;
527   svn_boolean_t created;
528   svn_node_kind_t kind;
529 
530   SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath)
531                  && svn_relpath_is_canonical(dst_relpath));
532 
533   if (! SVN_IS_VALID_REVNUM(revision))
534     revision = mtcc->head_revision;
535   else if (revision > mtcc->head_revision)
536     {
537       return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
538                                _("No such revision %ld"), revision);
539     }
540 
541   SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool));
542 
543   /* Subversion requires the kind of a copy */
544   SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind,
545                             scratch_pool));
546 
547   if (kind != svn_node_dir && kind != svn_node_file)
548     {
549       return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
550                                _("Path '%s' not found in revision %ld"),
551                                src_relpath, revision);
552     }
553 
554   SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE,
555                        (kind == svn_node_file), mtcc->pool, scratch_pool));
556 
557   if (!op || !created)
558     {
559       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
560                                _("Can't add node at '%s'"),
561                                dst_relpath);
562     }
563 
564   op->kind = (kind == svn_node_file) ? OP_ADD_FILE : OP_ADD_DIR;
565   op->src_relpath = apr_pstrdup(mtcc->pool, src_relpath);
566   op->src_rev = revision;
567 
568   return SVN_NO_ERROR;
569 }
570 
571 /* Check if this operation contains at least one change that is not a
572    plain delete */
573 static svn_boolean_t
mtcc_op_contains_non_delete(const mtcc_op_t * op)574 mtcc_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 
604 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)605 mtcc_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 
664 svn_error_t *
svn_client__mtcc_add_delete(const char * relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)665 svn_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 
672 svn_error_t *
svn_client__mtcc_add_mkdir(const char * relpath,svn_client__mtcc_t * mtcc,apr_pool_t * scratch_pool)673 svn_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 
706 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)707 svn_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 */
727 struct 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 */
736 static svn_error_t *
mtcc_prop_getter(const svn_string_t ** mime_type,svn_stream_t * stream,void * baton,apr_pool_t * pool)737 mtcc_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 
823 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)824 svn_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 
942 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)943 svn_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 
987 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)988 svn_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 
1075 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)1076 commit_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 */
1107 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)1108 commit_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 */
1184 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)1185 commit_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 */
1277 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)1278 add_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 
1357 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)1358 svn_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