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