commit.c revision 299742
1/* commit.c --- editor for committing changes to a filesystem.
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23
24#include <string.h>
25
26#include <apr_pools.h>
27#include <apr_file_io.h>
28
29#include "svn_hash.h"
30#include "svn_compat.h"
31#include "svn_pools.h"
32#include "svn_error.h"
33#include "svn_dirent_uri.h"
34#include "svn_path.h"
35#include "svn_delta.h"
36#include "svn_fs.h"
37#include "svn_repos.h"
38#include "svn_checksum.h"
39#include "svn_ctype.h"
40#include "svn_props.h"
41#include "svn_mergeinfo.h"
42#include "svn_private_config.h"
43
44#include "repos.h"
45
46#include "private/svn_fspath.h"
47#include "private/svn_fs_private.h"
48#include "private/svn_repos_private.h"
49#include "private/svn_editor.h"
50
51
52
53/*** Editor batons. ***/
54
55struct edit_baton
56{
57  apr_pool_t *pool;
58
59  /** Supplied when the editor is created: **/
60
61  /* Revision properties to set for this commit. */
62  apr_hash_t *revprop_table;
63
64  /* Callback to run when the commit is done. */
65  svn_commit_callback2_t commit_callback;
66  void *commit_callback_baton;
67
68  /* Callback to check authorizations on paths. */
69  svn_repos_authz_callback_t authz_callback;
70  void *authz_baton;
71
72  /* The already-open svn repository to commit to. */
73  svn_repos_t *repos;
74
75  /* URL to the root of the open repository. */
76  const char *repos_url_decoded;
77
78  /* The name of the repository (here for convenience). */
79  const char *repos_name;
80
81  /* The filesystem associated with the REPOS above (here for
82     convenience). */
83  svn_fs_t *fs;
84
85  /* Location in fs where the edit will begin. */
86  const char *base_path;
87
88  /* Does this set of interfaces 'own' the commit transaction? */
89  svn_boolean_t txn_owner;
90
91  /* svn transaction associated with this edit (created in
92     open_root, or supplied by the public API caller). */
93  svn_fs_txn_t *txn;
94
95  /** Filled in during open_root: **/
96
97  /* The name of the transaction. */
98  const char *txn_name;
99
100  /* The object representing the root directory of the svn txn. */
101  svn_fs_root_t *txn_root;
102
103  /* Avoid aborting an fs transaction more than once */
104  svn_boolean_t txn_aborted;
105
106  /** Filled in when the edit is closed: **/
107
108  /* The new revision created by this commit. */
109  svn_revnum_t *new_rev;
110
111  /* The date (according to the repository) of this commit. */
112  const char **committed_date;
113
114  /* The author (also according to the repository) of this commit. */
115  const char **committed_author;
116};
117
118
119struct dir_baton
120{
121  struct edit_baton *edit_baton;
122  struct dir_baton *parent;
123  const char *path; /* the -absolute- path to this dir in the fs */
124  svn_revnum_t base_rev;        /* the revision I'm based on  */
125  svn_boolean_t was_copied; /* was this directory added with history? */
126  apr_pool_t *pool; /* my personal pool, in which I am allocated. */
127};
128
129
130struct file_baton
131{
132  struct edit_baton *edit_baton;
133  const char *path; /* the -absolute- path to this file in the fs */
134};
135
136
137struct ev2_baton
138{
139  /* The repository we are editing.  */
140  svn_repos_t *repos;
141
142  /* The authz baton for checks; NULL to skip authz.  */
143  svn_authz_t *authz;
144
145  /* The repository name and user for performing authz checks.  */
146  const char *authz_repos_name;
147  const char *authz_user;
148
149  /* Callback to provide info about the committed revision.  */
150  svn_commit_callback2_t commit_cb;
151  void *commit_baton;
152
153  /* The FS txn editor  */
154  svn_editor_t *inner;
155
156  /* The name of the open transaction (so we know what to commit)  */
157  const char *txn_name;
158};
159
160
161/* Create and return a generic out-of-dateness error. */
162static svn_error_t *
163out_of_date(const char *path, svn_node_kind_t kind)
164{
165  return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
166                           (kind == svn_node_dir
167                            ? _("Directory '%s' is out of date")
168                            : kind == svn_node_file
169                            ? _("File '%s' is out of date")
170                            : _("'%s' is out of date")),
171                           path);
172}
173
174
175static svn_error_t *
176invoke_commit_cb(svn_commit_callback2_t commit_cb,
177                 void *commit_baton,
178                 svn_fs_t *fs,
179                 svn_revnum_t revision,
180                 const char *post_commit_errstr,
181                 apr_pool_t *scratch_pool)
182{
183  /* FS interface returns non-const values.  */
184  /* const */ svn_string_t *date;
185  /* const */ svn_string_t *author;
186  svn_commit_info_t *commit_info;
187
188  if (commit_cb == NULL)
189    return SVN_NO_ERROR;
190
191  SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE,
192                               scratch_pool));
193  SVN_ERR(svn_fs_revision_prop(&author, fs, revision,
194                               SVN_PROP_REVISION_AUTHOR,
195                               scratch_pool));
196
197  commit_info = svn_create_commit_info(scratch_pool);
198
199  /* fill up the svn_commit_info structure */
200  commit_info->revision = revision;
201  commit_info->date = date ? date->data : NULL;
202  commit_info->author = author ? author->data : NULL;
203  commit_info->post_commit_err = post_commit_errstr;
204  /* commit_info->repos_root is not set by the repos layer, only by RA layers */
205
206  return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
207}
208
209
210
211/* If EDITOR_BATON contains a valid authz callback, verify that the
212   REQUIRED access to PATH in ROOT is authorized.  Return an error
213   appropriate for throwing out of the commit editor with SVN_ERR.  If
214   no authz callback is present in EDITOR_BATON, then authorize all
215   paths.  Use POOL for temporary allocation only. */
216static svn_error_t *
217check_authz(struct edit_baton *editor_baton, const char *path,
218            svn_fs_root_t *root, svn_repos_authz_access_t required,
219            apr_pool_t *pool)
220{
221  if (editor_baton->authz_callback)
222    {
223      svn_boolean_t allowed;
224
225      SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
226                                           editor_baton->authz_baton, pool));
227      if (!allowed)
228        return svn_error_create(required & svn_authz_write ?
229                                SVN_ERR_AUTHZ_UNWRITABLE :
230                                SVN_ERR_AUTHZ_UNREADABLE,
231                                NULL, "Access denied");
232    }
233
234  return SVN_NO_ERROR;
235}
236
237
238/* Return a directory baton allocated in POOL which represents
239   FULL_PATH, which is the immediate directory child of the directory
240   represented by PARENT_BATON.  EDIT_BATON is the commit editor
241   baton.  WAS_COPIED reveals whether or not this directory is the
242   result of a copy operation.  BASE_REVISION is the base revision of
243   the directory. */
244static struct dir_baton *
245make_dir_baton(struct edit_baton *edit_baton,
246               struct dir_baton *parent_baton,
247               const char *full_path,
248               svn_boolean_t was_copied,
249               svn_revnum_t base_revision,
250               apr_pool_t *pool)
251{
252  struct dir_baton *db;
253  db = apr_pcalloc(pool, sizeof(*db));
254  db->edit_baton = edit_baton;
255  db->parent = parent_baton;
256  db->pool = pool;
257  db->path = full_path;
258  db->was_copied = was_copied;
259  db->base_rev = base_revision;
260  return db;
261}
262
263/* This function is the shared guts of add_file() and add_directory(),
264   which see for the meanings of the parameters.  The only extra
265   parameter here is IS_DIR, which is TRUE when adding a directory,
266   and FALSE when adding a file.
267
268   COPY_PATH must be a full URL, not a relative path. */
269static svn_error_t *
270add_file_or_directory(const char *path,
271                      void *parent_baton,
272                      const char *copy_path,
273                      svn_revnum_t copy_revision,
274                      svn_boolean_t is_dir,
275                      apr_pool_t *pool,
276                      void **return_baton)
277{
278  struct dir_baton *pb = parent_baton;
279  struct edit_baton *eb = pb->edit_baton;
280  apr_pool_t *subpool = svn_pool_create(pool);
281  svn_boolean_t was_copied = FALSE;
282  const char *full_path;
283
284  /* Reject paths which contain control characters (related to issue #4340). */
285  SVN_ERR(svn_path_check_valid(path, pool));
286
287  full_path = svn_fspath__join(eb->base_path,
288                               svn_relpath_canonicalize(path, pool), pool);
289
290  /* Sanity check. */
291  if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
292    return svn_error_createf
293      (SVN_ERR_FS_GENERAL, NULL,
294       _("Got source path but no source revision for '%s'"), full_path);
295
296  if (copy_path)
297    {
298      const char *fs_path;
299      svn_fs_root_t *copy_root;
300      svn_node_kind_t kind;
301      size_t repos_url_len;
302      svn_repos_authz_access_t required;
303
304      /* Copy requires recursive write access to the destination path
305         and write access to the parent path. */
306      required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
307      SVN_ERR(check_authz(eb, full_path, eb->txn_root,
308                          required, subpool));
309      SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
310                          svn_authz_write, subpool));
311
312      /* Check PATH in our transaction.  Make sure it does not exist
313         unless its parent directory was copied (in which case, the
314         thing might have been copied in as well), else return an
315         out-of-dateness error. */
316      SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
317      if ((kind != svn_node_none) && (! pb->was_copied))
318        return svn_error_trace(out_of_date(full_path, kind));
319
320      /* For now, require that the url come from the same repository
321         that this commit is operating on. */
322      copy_path = svn_path_uri_decode(copy_path, subpool);
323      repos_url_len = strlen(eb->repos_url_decoded);
324      if (strncmp(copy_path, eb->repos_url_decoded, repos_url_len) != 0)
325        return svn_error_createf
326          (SVN_ERR_FS_GENERAL, NULL,
327           _("Source url '%s' is from different repository"), copy_path);
328
329      fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
330
331      /* Now use the "fs_path" as an absolute path within the
332         repository to make the copy from. */
333      SVN_ERR(svn_fs_revision_root(&copy_root, eb->fs,
334                                   copy_revision, subpool));
335
336      /* Copy also requires (recursive) read access to the source */
337      required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
338      SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
339
340      SVN_ERR(svn_fs_copy(copy_root, fs_path,
341                          eb->txn_root, full_path, subpool));
342      was_copied = TRUE;
343    }
344  else
345    {
346      /* No ancestry given, just make a new directory or empty file.
347         Note that we don't perform an existence check here like the
348         copy-from case does -- that's because svn_fs_make_*()
349         already errors out if the file already exists.  Verify write
350         access to the full path and to the parent. */
351      SVN_ERR(check_authz(eb, full_path, eb->txn_root,
352                          svn_authz_write, subpool));
353      SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
354                          svn_authz_write, subpool));
355      if (is_dir)
356        SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
357      else
358        SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
359    }
360
361  /* Cleanup our temporary subpool. */
362  svn_pool_destroy(subpool);
363
364  /* Build a new child baton. */
365  if (is_dir)
366    {
367      *return_baton = make_dir_baton(eb, pb, full_path, was_copied,
368                                     SVN_INVALID_REVNUM, pool);
369    }
370  else
371    {
372      struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
373      new_fb->edit_baton = eb;
374      new_fb->path = full_path;
375      *return_baton = new_fb;
376    }
377
378  return SVN_NO_ERROR;
379}
380
381
382
383/*** Editor functions ***/
384
385static svn_error_t *
386open_root(void *edit_baton,
387          svn_revnum_t base_revision,
388          apr_pool_t *pool,
389          void **root_baton)
390{
391  struct dir_baton *dirb;
392  struct edit_baton *eb = edit_baton;
393  svn_revnum_t youngest;
394
395  /* Ignore BASE_REVISION.  We always build our transaction against
396     HEAD.  However, we will keep it in our dir baton for out of
397     dateness checks.  */
398  SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
399
400  if (base_revision > youngest)
401    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
402                             _("No such revision %ld (HEAD is %ld)"),
403                             base_revision, youngest);
404
405  /* Unless we've been instructed to use a specific transaction, we'll
406     make our own. */
407  if (eb->txn_owner)
408    {
409      SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
410                                                 eb->repos,
411                                                 youngest,
412                                                 eb->revprop_table,
413                                                 eb->pool));
414    }
415  else /* Even if we aren't the owner of the transaction, we might
416          have been instructed to set some properties. */
417    {
418      apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
419                                                         pool);
420      SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
421    }
422  SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
423  SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
424
425  /* Create a root dir baton.  The `base_path' field is an -absolute-
426     path in the filesystem, upon which all further editor paths are
427     based. */
428  dirb = apr_pcalloc(pool, sizeof(*dirb));
429  dirb->edit_baton = edit_baton;
430  dirb->parent = NULL;
431  dirb->pool = pool;
432  dirb->was_copied = FALSE;
433  dirb->path = apr_pstrdup(pool, eb->base_path);
434  dirb->base_rev = base_revision;
435
436  *root_baton = dirb;
437  return SVN_NO_ERROR;
438}
439
440
441
442static svn_error_t *
443delete_entry(const char *path,
444             svn_revnum_t revision,
445             void *parent_baton,
446             apr_pool_t *pool)
447{
448  struct dir_baton *parent = parent_baton;
449  struct edit_baton *eb = parent->edit_baton;
450  svn_node_kind_t kind;
451  svn_revnum_t cr_rev;
452  svn_repos_authz_access_t required = svn_authz_write;
453  const char *full_path;
454
455  full_path = svn_fspath__join(eb->base_path,
456                               svn_relpath_canonicalize(path, pool), pool);
457
458  /* Check PATH in our transaction.  */
459  SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
460
461  /* Deletion requires a recursive write access, as well as write
462     access to the parent directory. */
463  if (kind == svn_node_dir)
464    required |= svn_authz_recursive;
465  SVN_ERR(check_authz(eb, full_path, eb->txn_root,
466                      required, pool));
467  SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
468                      svn_authz_write, pool));
469
470  /* If PATH doesn't exist in the txn, the working copy is out of date. */
471  if (kind == svn_node_none)
472    return svn_error_trace(out_of_date(full_path, kind));
473
474  /* Now, make sure we're deleting the node we *think* we're
475     deleting, else return an out-of-dateness error. */
476  SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
477  if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
478    return svn_error_trace(out_of_date(full_path, kind));
479
480  /* This routine is a mindless wrapper.  We call svn_fs_delete()
481     because that will delete files and recursively delete
482     directories.  */
483  return svn_fs_delete(eb->txn_root, full_path, pool);
484}
485
486
487static svn_error_t *
488add_directory(const char *path,
489              void *parent_baton,
490              const char *copy_path,
491              svn_revnum_t copy_revision,
492              apr_pool_t *pool,
493              void **child_baton)
494{
495  return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
496                               TRUE /* is_dir */, pool, child_baton);
497}
498
499
500static svn_error_t *
501open_directory(const char *path,
502               void *parent_baton,
503               svn_revnum_t base_revision,
504               apr_pool_t *pool,
505               void **child_baton)
506{
507  struct dir_baton *pb = parent_baton;
508  struct edit_baton *eb = pb->edit_baton;
509  svn_node_kind_t kind;
510  const char *full_path;
511
512  full_path = svn_fspath__join(eb->base_path,
513                               svn_relpath_canonicalize(path, pool), pool);
514
515  /* Check PATH in our transaction.  If it does not exist,
516     return a 'Path not present' error. */
517  SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
518  if (kind == svn_node_none)
519    return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
520                             _("Path '%s' not present"),
521                             path);
522
523  /* Build a new dir baton for this directory. */
524  *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
525                                base_revision, pool);
526  return SVN_NO_ERROR;
527}
528
529
530static svn_error_t *
531apply_textdelta(void *file_baton,
532                const char *base_checksum,
533                apr_pool_t *pool,
534                svn_txdelta_window_handler_t *handler,
535                void **handler_baton)
536{
537  struct file_baton *fb = file_baton;
538
539  /* Check for write authorization. */
540  SVN_ERR(check_authz(fb->edit_baton, fb->path,
541                      fb->edit_baton->txn_root,
542                      svn_authz_write, pool));
543
544  return svn_fs_apply_textdelta(handler, handler_baton,
545                                fb->edit_baton->txn_root,
546                                fb->path,
547                                base_checksum,
548                                NULL,
549                                pool);
550}
551
552
553static svn_error_t *
554add_file(const char *path,
555         void *parent_baton,
556         const char *copy_path,
557         svn_revnum_t copy_revision,
558         apr_pool_t *pool,
559         void **file_baton)
560{
561  return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
562                               FALSE /* is_dir */, pool, file_baton);
563}
564
565
566static svn_error_t *
567open_file(const char *path,
568          void *parent_baton,
569          svn_revnum_t base_revision,
570          apr_pool_t *pool,
571          void **file_baton)
572{
573  struct file_baton *new_fb;
574  struct dir_baton *pb = parent_baton;
575  struct edit_baton *eb = pb->edit_baton;
576  svn_revnum_t cr_rev;
577  apr_pool_t *subpool = svn_pool_create(pool);
578  const char *full_path;
579
580  full_path = svn_fspath__join(eb->base_path,
581                               svn_relpath_canonicalize(path, pool), pool);
582
583  /* Check for read authorization. */
584  SVN_ERR(check_authz(eb, full_path, eb->txn_root,
585                      svn_authz_read, subpool));
586
587  /* Get this node's creation revision (doubles as an existence check). */
588  SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
589                                  subpool));
590
591  /* If the node our caller has is an older revision number than the
592     one in our transaction, return an out-of-dateness error. */
593  if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
594    return svn_error_trace(out_of_date(full_path, svn_node_file));
595
596  /* Build a new file baton */
597  new_fb = apr_pcalloc(pool, sizeof(*new_fb));
598  new_fb->edit_baton = eb;
599  new_fb->path = full_path;
600
601  *file_baton = new_fb;
602
603  /* Destory the work subpool. */
604  svn_pool_destroy(subpool);
605
606  return SVN_NO_ERROR;
607}
608
609
610static svn_error_t *
611change_file_prop(void *file_baton,
612                 const char *name,
613                 const svn_string_t *value,
614                 apr_pool_t *pool)
615{
616  struct file_baton *fb = file_baton;
617  struct edit_baton *eb = fb->edit_baton;
618
619  /* Check for write authorization. */
620  SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
621                      svn_authz_write, pool));
622
623  return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
624                                       name, value, pool);
625}
626
627
628static svn_error_t *
629close_file(void *file_baton,
630           const char *text_digest,
631           apr_pool_t *pool)
632{
633  struct file_baton *fb = file_baton;
634
635  if (text_digest)
636    {
637      svn_checksum_t *checksum;
638      svn_checksum_t *text_checksum;
639
640      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
641                                   fb->edit_baton->txn_root, fb->path,
642                                   TRUE, pool));
643      SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
644                                     text_digest, pool));
645
646      if (!svn_checksum_match(text_checksum, checksum))
647        return svn_checksum_mismatch_err(text_checksum, checksum, pool,
648                            _("Checksum mismatch for resulting fulltext\n(%s)"),
649                            fb->path);
650    }
651
652  return SVN_NO_ERROR;
653}
654
655
656static svn_error_t *
657change_dir_prop(void *dir_baton,
658                const char *name,
659                const svn_string_t *value,
660                apr_pool_t *pool)
661{
662  struct dir_baton *db = dir_baton;
663  struct edit_baton *eb = db->edit_baton;
664
665  /* Check for write authorization. */
666  SVN_ERR(check_authz(eb, db->path, eb->txn_root,
667                      svn_authz_write, pool));
668
669  if (SVN_IS_VALID_REVNUM(db->base_rev))
670    {
671      /* Subversion rule:  propchanges can only happen on a directory
672         which is up-to-date. */
673      svn_revnum_t created_rev;
674      SVN_ERR(svn_fs_node_created_rev(&created_rev,
675                                      eb->txn_root, db->path, pool));
676
677      if (db->base_rev < created_rev)
678        return svn_error_trace(out_of_date(db->path, svn_node_dir));
679    }
680
681  return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
682                                       name, value, pool);
683}
684
685const char *
686svn_repos__post_commit_error_str(svn_error_t *err,
687                                 apr_pool_t *pool)
688{
689  svn_error_t *hook_err1, *hook_err2;
690  const char *msg;
691
692  if (! err)
693    return _("(no error)");
694
695  err = svn_error_purge_tracing(err);
696
697  /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
698     error from the post-commit script, if any, and hook_err2 should
699     be the original error, but be defensive and handle a case where
700     SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
701  hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
702  if (hook_err1 && hook_err1->child)
703    hook_err2 = hook_err1->child;
704  else
705    hook_err2 = hook_err1;
706
707  /* This implementation counts on svn_repos_fs_commit_txn() and
708     libsvn_repos/commit.c:complete_cb() returning
709     svn_fs_commit_txn() as the parent error with a child
710     SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error.  If the parent error
711     is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
712     in svn_fs_commit_txn().
713
714     The post-commit hook error message is already self describing, so
715     it can be dropped into an error message without any additional
716     text. */
717  if (hook_err1)
718    {
719      if (err == hook_err1)
720        {
721          if (hook_err2->message)
722            msg = apr_pstrdup(pool, hook_err2->message);
723          else
724            msg = _("post-commit hook failed with no error message.");
725        }
726      else
727        {
728          msg = hook_err2->message
729                  ? apr_pstrdup(pool, hook_err2->message)
730                  : _("post-commit hook failed with no error message.");
731          msg = apr_psprintf(
732                  pool,
733                  _("post commit FS processing had error:\n%s\n%s"),
734                  err->message ? err->message : _("(no error message)"),
735                  msg);
736        }
737    }
738  else
739    {
740      msg = apr_psprintf(pool,
741                         _("post commit FS processing had error:\n%s"),
742                         err->message ? err->message
743                                      : _("(no error message)"));
744    }
745
746  return msg;
747}
748
749static svn_error_t *
750close_edit(void *edit_baton,
751           apr_pool_t *pool)
752{
753  struct edit_baton *eb = edit_baton;
754  svn_revnum_t new_revision = SVN_INVALID_REVNUM;
755  svn_error_t *err;
756  const char *conflict;
757  const char *post_commit_err = NULL;
758
759  /* If no transaction has been created (ie. if open_root wasn't
760     called before close_edit), abort the operation here with an
761     error. */
762  if (! eb->txn)
763    return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
764                            "No valid transaction supplied to close_edit");
765
766  /* Commit. */
767  err = svn_repos_fs_commit_txn(&conflict, eb->repos,
768                                &new_revision, eb->txn, pool);
769
770  if (SVN_IS_VALID_REVNUM(new_revision))
771    {
772      /* The actual commit succeeded, i.e. the transaction does no longer
773         exist and we can't use txn_root for conflict resolution etc.
774
775         Since close_edit is supposed to release resources, do it now. */
776      if (eb->txn_root)
777        svn_fs_close_root(eb->txn_root);
778
779      if (err)
780        {
781          /* If the error was in post-commit, then the commit itself
782             succeeded.  In which case, save the post-commit warning
783             (to be reported back to the client, who will probably
784             display it as a warning) and clear the error. */
785          post_commit_err = svn_repos__post_commit_error_str(err, pool);
786          svn_error_clear(err);
787        }
788
789      /* Make sure a future abort doesn't perform
790         any work. This may occur if the commit
791         callback returns an error! */
792
793      eb->txn = NULL;
794      eb->txn_root = NULL;
795    }
796  else
797    {
798      /* ### todo: we should check whether it really was a conflict,
799         and return the conflict info if so? */
800
801      /* If the commit failed, it's *probably* due to a conflict --
802         that is, the txn being out-of-date.  The filesystem gives us
803         the ability to continue diddling the transaction and try
804         again; but let's face it: that's not how the cvs or svn works
805         from a user interface standpoint.  Thus we don't make use of
806         this fs feature (for now, at least.)
807
808         So, in a nutshell: svn commits are an all-or-nothing deal.
809         Each commit creates a new fs txn which either succeeds or is
810         aborted completely.  No second chances;  the user simply
811         needs to update and commit again  :) */
812
813      eb->txn_aborted = TRUE;
814
815      return svn_error_trace(
816                svn_error_compose_create(err,
817                                         svn_fs_abort_txn(eb->txn, pool)));
818    }
819
820  /* At this point, the post-commit error has been converted to a string.
821     That information will be passed to a callback, if provided. If the
822     callback invocation fails in some way, that failure is returned here.
823     IOW, the post-commit error information is low priority compared to
824     other gunk here.  */
825
826  /* Pass new revision information to the caller's callback. */
827  return svn_error_trace(invoke_commit_cb(eb->commit_callback,
828                                          eb->commit_callback_baton,
829                                          eb->repos->fs,
830                                          new_revision,
831                                          post_commit_err,
832                                          pool));
833}
834
835
836static svn_error_t *
837abort_edit(void *edit_baton,
838           apr_pool_t *pool)
839{
840  struct edit_baton *eb = edit_baton;
841  if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
842    return SVN_NO_ERROR;
843
844  eb->txn_aborted = TRUE;
845
846  /* Since abort_edit is supposed to release resources, do it now. */
847  if (eb->txn_root)
848    svn_fs_close_root(eb->txn_root);
849
850  return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
851}
852
853
854static svn_error_t *
855fetch_props_func(apr_hash_t **props,
856                 void *baton,
857                 const char *path,
858                 svn_revnum_t base_revision,
859                 apr_pool_t *result_pool,
860                 apr_pool_t *scratch_pool)
861{
862  struct edit_baton *eb = baton;
863  svn_fs_root_t *fs_root;
864  svn_error_t *err;
865
866  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
867                               svn_fs_txn_base_revision(eb->txn),
868                               scratch_pool));
869  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
870  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
871    {
872      svn_error_clear(err);
873      *props = apr_hash_make(result_pool);
874      return SVN_NO_ERROR;
875    }
876  else if (err)
877    return svn_error_trace(err);
878
879  return SVN_NO_ERROR;
880}
881
882static svn_error_t *
883fetch_kind_func(svn_node_kind_t *kind,
884                void *baton,
885                const char *path,
886                svn_revnum_t base_revision,
887                apr_pool_t *scratch_pool)
888{
889  struct edit_baton *eb = baton;
890  svn_fs_root_t *fs_root;
891
892  if (!SVN_IS_VALID_REVNUM(base_revision))
893    base_revision = svn_fs_txn_base_revision(eb->txn);
894
895  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
896
897  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
898
899  return SVN_NO_ERROR;
900}
901
902static svn_error_t *
903fetch_base_func(const char **filename,
904                void *baton,
905                const char *path,
906                svn_revnum_t base_revision,
907                apr_pool_t *result_pool,
908                apr_pool_t *scratch_pool)
909{
910  struct edit_baton *eb = baton;
911  svn_stream_t *contents;
912  svn_stream_t *file_stream;
913  const char *tmp_filename;
914  svn_fs_root_t *fs_root;
915  svn_error_t *err;
916
917  if (!SVN_IS_VALID_REVNUM(base_revision))
918    base_revision = svn_fs_txn_base_revision(eb->txn);
919
920  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
921
922  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
923  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
924    {
925      svn_error_clear(err);
926      *filename = NULL;
927      return SVN_NO_ERROR;
928    }
929  else if (err)
930    return svn_error_trace(err);
931  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
932                                 svn_io_file_del_on_pool_cleanup,
933                                 scratch_pool, scratch_pool));
934  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
935
936  *filename = apr_pstrdup(result_pool, tmp_filename);
937
938  return SVN_NO_ERROR;
939}
940
941
942
943/*** Public interfaces. ***/
944
945svn_error_t *
946svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
947                             void **edit_baton,
948                             svn_repos_t *repos,
949                             svn_fs_txn_t *txn,
950                             const char *repos_url_decoded,
951                             const char *base_path,
952                             apr_hash_t *revprop_table,
953                             svn_commit_callback2_t commit_callback,
954                             void *commit_baton,
955                             svn_repos_authz_callback_t authz_callback,
956                             void *authz_baton,
957                             apr_pool_t *pool)
958{
959  svn_delta_editor_t *e;
960  apr_pool_t *subpool = svn_pool_create(pool);
961  struct edit_baton *eb;
962  svn_delta_shim_callbacks_t *shim_callbacks =
963                                    svn_delta_shim_callbacks_default(pool);
964  const char *repos_url = svn_path_uri_encode(repos_url_decoded, pool);
965
966  /* Do a global authz access lookup.  Users with no write access
967     whatsoever to the repository don't get a commit editor. */
968  if (authz_callback)
969    {
970      svn_boolean_t allowed;
971
972      SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
973                             authz_baton, pool));
974      if (!allowed)
975        return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
976                                "Not authorized to open a commit editor.");
977    }
978
979  /* Allocate the structures. */
980  e = svn_delta_default_editor(pool);
981  eb = apr_pcalloc(subpool, sizeof(*eb));
982
983  /* Set up the editor. */
984  e->open_root         = open_root;
985  e->delete_entry      = delete_entry;
986  e->add_directory     = add_directory;
987  e->open_directory    = open_directory;
988  e->change_dir_prop   = change_dir_prop;
989  e->add_file          = add_file;
990  e->open_file         = open_file;
991  e->close_file        = close_file;
992  e->apply_textdelta   = apply_textdelta;
993  e->change_file_prop  = change_file_prop;
994  e->close_edit        = close_edit;
995  e->abort_edit        = abort_edit;
996
997  /* Set up the edit baton. */
998  eb->pool = subpool;
999  eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
1000  eb->commit_callback = commit_callback;
1001  eb->commit_callback_baton = commit_baton;
1002  eb->authz_callback = authz_callback;
1003  eb->authz_baton = authz_baton;
1004  eb->base_path = svn_fspath__canonicalize(base_path, subpool);
1005  eb->repos = repos;
1006  eb->repos_url_decoded = repos_url_decoded;
1007  eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
1008                                       subpool);
1009  eb->fs = svn_repos_fs(repos);
1010  eb->txn = txn;
1011  eb->txn_owner = txn == NULL;
1012
1013  *edit_baton = eb;
1014  *editor = e;
1015
1016  shim_callbacks->fetch_props_func = fetch_props_func;
1017  shim_callbacks->fetch_kind_func = fetch_kind_func;
1018  shim_callbacks->fetch_base_func = fetch_base_func;
1019  shim_callbacks->fetch_baton = eb;
1020
1021  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1022                                   repos_url, eb->base_path,
1023                                   shim_callbacks, pool, pool));
1024
1025  return SVN_NO_ERROR;
1026}
1027
1028
1029#if 0
1030static svn_error_t *
1031ev2_check_authz(const struct ev2_baton *eb,
1032                const char *relpath,
1033                svn_repos_authz_access_t required,
1034                apr_pool_t *scratch_pool)
1035{
1036  const char *fspath;
1037  svn_boolean_t allowed;
1038
1039  if (eb->authz == NULL)
1040    return SVN_NO_ERROR;
1041
1042  if (relpath)
1043    fspath = apr_pstrcat(scratch_pool, "/", relpath, SVN_VA_NULL);
1044  else
1045    fspath = NULL;
1046
1047  SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1048                                       eb->authz_user, required,
1049                                       &allowed, scratch_pool));
1050  if (!allowed)
1051    return svn_error_create(required & svn_authz_write
1052                            ? SVN_ERR_AUTHZ_UNWRITABLE
1053                            : SVN_ERR_AUTHZ_UNREADABLE,
1054                            NULL, "Access denied");
1055
1056  return SVN_NO_ERROR;
1057}
1058#endif
1059
1060
1061/* This implements svn_editor_cb_add_directory_t */
1062static svn_error_t *
1063add_directory_cb(void *baton,
1064                 const char *relpath,
1065                 const apr_array_header_t *children,
1066                 apr_hash_t *props,
1067                 svn_revnum_t replaces_rev,
1068                 apr_pool_t *scratch_pool)
1069{
1070  struct ev2_baton *eb = baton;
1071
1072  SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
1073                                   replaces_rev));
1074  return SVN_NO_ERROR;
1075}
1076
1077
1078/* This implements svn_editor_cb_add_file_t */
1079static svn_error_t *
1080add_file_cb(void *baton,
1081            const char *relpath,
1082            const svn_checksum_t *checksum,
1083            svn_stream_t *contents,
1084            apr_hash_t *props,
1085            svn_revnum_t replaces_rev,
1086            apr_pool_t *scratch_pool)
1087{
1088  struct ev2_baton *eb = baton;
1089
1090  SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1091                              replaces_rev));
1092  return SVN_NO_ERROR;
1093}
1094
1095
1096/* This implements svn_editor_cb_add_symlink_t */
1097static svn_error_t *
1098add_symlink_cb(void *baton,
1099               const char *relpath,
1100               const char *target,
1101               apr_hash_t *props,
1102               svn_revnum_t replaces_rev,
1103               apr_pool_t *scratch_pool)
1104{
1105  struct ev2_baton *eb = baton;
1106
1107  SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1108                                 replaces_rev));
1109  return SVN_NO_ERROR;
1110}
1111
1112
1113/* This implements svn_editor_cb_add_absent_t */
1114static svn_error_t *
1115add_absent_cb(void *baton,
1116              const char *relpath,
1117              svn_node_kind_t kind,
1118              svn_revnum_t replaces_rev,
1119              apr_pool_t *scratch_pool)
1120{
1121  struct ev2_baton *eb = baton;
1122
1123  SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1124  return SVN_NO_ERROR;
1125}
1126
1127
1128/* This implements svn_editor_cb_alter_directory_t */
1129static svn_error_t *
1130alter_directory_cb(void *baton,
1131                   const char *relpath,
1132                   svn_revnum_t revision,
1133                   const apr_array_header_t *children,
1134                   apr_hash_t *props,
1135                   apr_pool_t *scratch_pool)
1136{
1137  struct ev2_baton *eb = baton;
1138
1139  SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1140                                     children, props));
1141  return SVN_NO_ERROR;
1142}
1143
1144
1145/* This implements svn_editor_cb_alter_file_t */
1146static svn_error_t *
1147alter_file_cb(void *baton,
1148              const char *relpath,
1149              svn_revnum_t revision,
1150              const svn_checksum_t *checksum,
1151              svn_stream_t *contents,
1152              apr_hash_t *props,
1153              apr_pool_t *scratch_pool)
1154{
1155  struct ev2_baton *eb = baton;
1156
1157  SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision,
1158                                checksum, contents, props));
1159  return SVN_NO_ERROR;
1160}
1161
1162
1163/* This implements svn_editor_cb_alter_symlink_t */
1164static svn_error_t *
1165alter_symlink_cb(void *baton,
1166                 const char *relpath,
1167                 svn_revnum_t revision,
1168                 const char *target,
1169                 apr_hash_t *props,
1170                 apr_pool_t *scratch_pool)
1171{
1172  struct ev2_baton *eb = baton;
1173
1174  SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision,
1175                                   target, props));
1176  return SVN_NO_ERROR;
1177}
1178
1179
1180/* This implements svn_editor_cb_delete_t */
1181static svn_error_t *
1182delete_cb(void *baton,
1183          const char *relpath,
1184          svn_revnum_t revision,
1185          apr_pool_t *scratch_pool)
1186{
1187  struct ev2_baton *eb = baton;
1188
1189  SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1190  return SVN_NO_ERROR;
1191}
1192
1193
1194/* This implements svn_editor_cb_copy_t */
1195static svn_error_t *
1196copy_cb(void *baton,
1197        const char *src_relpath,
1198        svn_revnum_t src_revision,
1199        const char *dst_relpath,
1200        svn_revnum_t replaces_rev,
1201        apr_pool_t *scratch_pool)
1202{
1203  struct ev2_baton *eb = baton;
1204
1205  SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1206                          replaces_rev));
1207  return SVN_NO_ERROR;
1208}
1209
1210
1211/* This implements svn_editor_cb_move_t */
1212static svn_error_t *
1213move_cb(void *baton,
1214        const char *src_relpath,
1215        svn_revnum_t src_revision,
1216        const char *dst_relpath,
1217        svn_revnum_t replaces_rev,
1218        apr_pool_t *scratch_pool)
1219{
1220  struct ev2_baton *eb = baton;
1221
1222  SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1223                          replaces_rev));
1224  return SVN_NO_ERROR;
1225}
1226
1227
1228/* This implements svn_editor_cb_complete_t */
1229static svn_error_t *
1230complete_cb(void *baton,
1231            apr_pool_t *scratch_pool)
1232{
1233  struct ev2_baton *eb = baton;
1234  svn_revnum_t revision;
1235  svn_error_t *post_commit_err;
1236  const char *conflict_path;
1237  svn_error_t *err;
1238  const char *post_commit_errstr;
1239  apr_hash_t *hooks_env;
1240
1241  /* Parse the hooks-env file (if any). */
1242  SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1243                                     scratch_pool, scratch_pool));
1244
1245  /* The transaction has been fully edited. Let the pre-commit hook
1246     have a look at the thing.  */
1247  SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1248                                      eb->txn_name, scratch_pool));
1249
1250  /* Hook is done. Let's do the actual commit.  */
1251  SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1252                                eb->inner, scratch_pool, scratch_pool));
1253
1254  /* Did a conflict occur during the commit process?  */
1255  if (conflict_path != NULL)
1256    return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1257                             _("Conflict at '%s'"), conflict_path);
1258
1259  /* Since did not receive an error during the commit process, and no
1260     conflict was specified... we committed a revision. Run the hooks.
1261     Other errors may have occurred within the FS (specified by the
1262     POST_COMMIT_ERR localvar), but we need to run the hooks.  */
1263  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1264  err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1265                                     eb->txn_name, scratch_pool);
1266  if (err)
1267    err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1268                           _("Commit succeeded, but post-commit hook failed"));
1269
1270  /* Combine the FS errors with the hook errors, and stringify.  */
1271  err = svn_error_compose_create(post_commit_err, err);
1272  if (err)
1273    {
1274      post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1275      svn_error_clear(err);
1276    }
1277  else
1278    {
1279      post_commit_errstr = NULL;
1280    }
1281
1282  return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1283                                          eb->repos->fs, revision,
1284                                          post_commit_errstr,
1285                                          scratch_pool));
1286}
1287
1288
1289/* This implements svn_editor_cb_abort_t */
1290static svn_error_t *
1291abort_cb(void *baton,
1292         apr_pool_t *scratch_pool)
1293{
1294  struct ev2_baton *eb = baton;
1295
1296  SVN_ERR(svn_editor_abort(eb->inner));
1297  return SVN_NO_ERROR;
1298}
1299
1300
1301static svn_error_t *
1302apply_revprops(svn_fs_t *fs,
1303               const char *txn_name,
1304               apr_hash_t *revprops,
1305               apr_pool_t *scratch_pool)
1306{
1307  svn_fs_txn_t *txn;
1308  const apr_array_header_t *revprops_array;
1309
1310  /* The FS editor has a TXN inside it, but we can't access it. Open another
1311     based on the TXN_NAME.  */
1312  SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1313
1314  /* Validate and apply the revision properties.  */
1315  revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1316  SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1317
1318  /* ### do we need to force the txn to close, or is it enough to wait
1319     ### for the pool to be cleared?  */
1320  return SVN_NO_ERROR;
1321}
1322
1323
1324svn_error_t *
1325svn_repos__get_commit_ev2(svn_editor_t **editor,
1326                          svn_repos_t *repos,
1327                          svn_authz_t *authz,
1328                          const char *authz_repos_name,
1329                          const char *authz_user,
1330                          apr_hash_t *revprops,
1331                          svn_commit_callback2_t commit_cb,
1332                          void *commit_baton,
1333                          svn_cancel_func_t cancel_func,
1334                          void *cancel_baton,
1335                          apr_pool_t *result_pool,
1336                          apr_pool_t *scratch_pool)
1337{
1338  static const svn_editor_cb_many_t editor_cbs = {
1339    add_directory_cb,
1340    add_file_cb,
1341    add_symlink_cb,
1342    add_absent_cb,
1343    alter_directory_cb,
1344    alter_file_cb,
1345    alter_symlink_cb,
1346    delete_cb,
1347    copy_cb,
1348    move_cb,
1349    complete_cb,
1350    abort_cb
1351  };
1352  struct ev2_baton *eb;
1353  const svn_string_t *author;
1354  apr_hash_t *hooks_env;
1355
1356  /* Parse the hooks-env file (if any). */
1357  SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1358                                     scratch_pool, scratch_pool));
1359
1360  /* Can the user modify the repository at all?  */
1361  /* ### check against AUTHZ.  */
1362
1363  author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1364
1365  eb = apr_palloc(result_pool, sizeof(*eb));
1366  eb->repos = repos;
1367  eb->authz = authz;
1368  eb->authz_repos_name = authz_repos_name;
1369  eb->authz_user = authz_user;
1370  eb->commit_cb = commit_cb;
1371  eb->commit_baton = commit_baton;
1372
1373  SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1374                                repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1375                                cancel_func, cancel_baton,
1376                                result_pool, scratch_pool));
1377
1378  /* The TXN has been created. Go ahead and apply all revision properties.  */
1379  SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1380
1381  /* Okay... some access is allowed. Let's run the start-commit hook.  */
1382  SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1383                                        author ? author->data : NULL,
1384                                        repos->client_capabilities,
1385                                        eb->txn_name, scratch_pool));
1386
1387  /* Wrap the FS editor within our editor.  */
1388  SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1389                            result_pool, scratch_pool));
1390  SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1391
1392  return SVN_NO_ERROR;
1393}
1394