~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/libsvn_client/mtcc.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

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