]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/libsvn_repos/commit.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.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       /* The actual commit succeeded, i.e. the transaction does no longer
765          exist and we can't use txn_root for conflict resolution etc.
766
767          Since close_edit is supposed to release resources, do it now. */
768       if (eb->txn_root)
769         svn_fs_close_root(eb->txn_root);
770
771       if (err)
772         {
773           /* If the error was in post-commit, then the commit itself
774              succeeded.  In which case, save the post-commit warning
775              (to be reported back to the client, who will probably
776              display it as a warning) and clear the error. */
777           post_commit_err = svn_repos__post_commit_error_str(err, pool);
778           svn_error_clear(err);
779         }
780
781       /* Make sure a future abort doesn't perform
782          any work. This may occur if the commit
783          callback returns an error! */
784
785       eb->txn = NULL;
786       eb->txn_root = NULL;
787     }
788   else
789     {
790       /* ### todo: we should check whether it really was a conflict,
791          and return the conflict info if so? */
792
793       /* If the commit failed, it's *probably* due to a conflict --
794          that is, the txn being out-of-date.  The filesystem gives us
795          the ability to continue diddling the transaction and try
796          again; but let's face it: that's not how the cvs or svn works
797          from a user interface standpoint.  Thus we don't make use of
798          this fs feature (for now, at least.)
799
800          So, in a nutshell: svn commits are an all-or-nothing deal.
801          Each commit creates a new fs txn which either succeeds or is
802          aborted completely.  No second chances;  the user simply
803          needs to update and commit again  :) */
804
805       eb->txn_aborted = TRUE;
806
807       return svn_error_trace(
808                 svn_error_compose_create(err,
809                                          svn_fs_abort_txn(eb->txn, pool)));
810     }
811
812   /* At this point, the post-commit error has been converted to a string.
813      That information will be passed to a callback, if provided. If the
814      callback invocation fails in some way, that failure is returned here.
815      IOW, the post-commit error information is low priority compared to
816      other gunk here.  */
817
818   /* Pass new revision information to the caller's callback. */
819   return svn_error_trace(invoke_commit_cb(eb->commit_callback,
820                                           eb->commit_callback_baton,
821                                           eb->repos->fs,
822                                           new_revision,
823                                           post_commit_err,
824                                           pool));
825 }
826
827
828 static svn_error_t *
829 abort_edit(void *edit_baton,
830            apr_pool_t *pool)
831 {
832   struct edit_baton *eb = edit_baton;
833   if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
834     return SVN_NO_ERROR;
835
836   eb->txn_aborted = TRUE;
837
838   /* Since abort_edit is supposed to release resources, do it now. */
839   if (eb->txn_root)
840     svn_fs_close_root(eb->txn_root);
841
842   return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
843 }
844
845
846 static svn_error_t *
847 fetch_props_func(apr_hash_t **props,
848                  void *baton,
849                  const char *path,
850                  svn_revnum_t base_revision,
851                  apr_pool_t *result_pool,
852                  apr_pool_t *scratch_pool)
853 {
854   struct edit_baton *eb = baton;
855   svn_fs_root_t *fs_root;
856   svn_error_t *err;
857
858   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
859                                svn_fs_txn_base_revision(eb->txn),
860                                scratch_pool));
861   err = svn_fs_node_proplist(props, fs_root, path, result_pool);
862   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
863     {
864       svn_error_clear(err);
865       *props = apr_hash_make(result_pool);
866       return SVN_NO_ERROR;
867     }
868   else if (err)
869     return svn_error_trace(err);
870
871   return SVN_NO_ERROR;
872 }
873
874 static svn_error_t *
875 fetch_kind_func(svn_node_kind_t *kind,
876                 void *baton,
877                 const char *path,
878                 svn_revnum_t base_revision,
879                 apr_pool_t *scratch_pool)
880 {
881   struct edit_baton *eb = baton;
882   svn_fs_root_t *fs_root;
883
884   if (!SVN_IS_VALID_REVNUM(base_revision))
885     base_revision = svn_fs_txn_base_revision(eb->txn);
886
887   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
888
889   SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
890
891   return SVN_NO_ERROR;
892 }
893
894 static svn_error_t *
895 fetch_base_func(const char **filename,
896                 void *baton,
897                 const char *path,
898                 svn_revnum_t base_revision,
899                 apr_pool_t *result_pool,
900                 apr_pool_t *scratch_pool)
901 {
902   struct edit_baton *eb = baton;
903   svn_stream_t *contents;
904   svn_stream_t *file_stream;
905   const char *tmp_filename;
906   svn_fs_root_t *fs_root;
907   svn_error_t *err;
908
909   if (!SVN_IS_VALID_REVNUM(base_revision))
910     base_revision = svn_fs_txn_base_revision(eb->txn);
911
912   SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
913
914   err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
915   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
916     {
917       svn_error_clear(err);
918       *filename = NULL;
919       return SVN_NO_ERROR;
920     }
921   else if (err)
922     return svn_error_trace(err);
923   SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
924                                  svn_io_file_del_on_pool_cleanup,
925                                  scratch_pool, scratch_pool));
926   SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
927
928   *filename = apr_pstrdup(result_pool, tmp_filename);
929
930   return SVN_NO_ERROR;
931 }
932
933
934 \f
935 /*** Public interfaces. ***/
936
937 svn_error_t *
938 svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
939                              void **edit_baton,
940                              svn_repos_t *repos,
941                              svn_fs_txn_t *txn,
942                              const char *repos_url,
943                              const char *base_path,
944                              apr_hash_t *revprop_table,
945                              svn_commit_callback2_t commit_callback,
946                              void *commit_baton,
947                              svn_repos_authz_callback_t authz_callback,
948                              void *authz_baton,
949                              apr_pool_t *pool)
950 {
951   svn_delta_editor_t *e;
952   apr_pool_t *subpool = svn_pool_create(pool);
953   struct edit_baton *eb;
954   svn_delta_shim_callbacks_t *shim_callbacks =
955                                     svn_delta_shim_callbacks_default(pool);
956
957   /* Do a global authz access lookup.  Users with no write access
958      whatsoever to the repository don't get a commit editor. */
959   if (authz_callback)
960     {
961       svn_boolean_t allowed;
962
963       SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
964                              authz_baton, pool));
965       if (!allowed)
966         return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
967                                 "Not authorized to open a commit editor.");
968     }
969
970   /* Allocate the structures. */
971   e = svn_delta_default_editor(pool);
972   eb = apr_pcalloc(subpool, sizeof(*eb));
973
974   /* Set up the editor. */
975   e->open_root         = open_root;
976   e->delete_entry      = delete_entry;
977   e->add_directory     = add_directory;
978   e->open_directory    = open_directory;
979   e->change_dir_prop   = change_dir_prop;
980   e->add_file          = add_file;
981   e->open_file         = open_file;
982   e->close_file        = close_file;
983   e->apply_textdelta   = apply_textdelta;
984   e->change_file_prop  = change_file_prop;
985   e->close_edit        = close_edit;
986   e->abort_edit        = abort_edit;
987
988   /* Set up the edit baton. */
989   eb->pool = subpool;
990   eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
991   eb->commit_callback = commit_callback;
992   eb->commit_callback_baton = commit_baton;
993   eb->authz_callback = authz_callback;
994   eb->authz_baton = authz_baton;
995   eb->base_path = svn_fspath__canonicalize(base_path, subpool);
996   eb->repos = repos;
997   eb->repos_url = repos_url;
998   eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
999                                        subpool);
1000   eb->fs = svn_repos_fs(repos);
1001   eb->txn = txn;
1002   eb->txn_owner = txn == NULL;
1003
1004   *edit_baton = eb;
1005   *editor = e;
1006
1007   shim_callbacks->fetch_props_func = fetch_props_func;
1008   shim_callbacks->fetch_kind_func = fetch_kind_func;
1009   shim_callbacks->fetch_base_func = fetch_base_func;
1010   shim_callbacks->fetch_baton = eb;
1011
1012   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1013                                    eb->repos_url, eb->base_path,
1014                                    shim_callbacks, pool, pool));
1015
1016   return SVN_NO_ERROR;
1017 }
1018
1019
1020 #if 0
1021 static svn_error_t *
1022 ev2_check_authz(const struct ev2_baton *eb,
1023                 const char *relpath,
1024                 svn_repos_authz_access_t required,
1025                 apr_pool_t *scratch_pool)
1026 {
1027   const char *fspath;
1028   svn_boolean_t allowed;
1029
1030   if (eb->authz == NULL)
1031     return SVN_NO_ERROR;
1032
1033   if (relpath)
1034     fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL);
1035   else
1036     fspath = NULL;
1037
1038   SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1039                                        eb->authz_user, required,
1040                                        &allowed, scratch_pool));
1041   if (!allowed)
1042     return svn_error_create(required & svn_authz_write
1043                             ? SVN_ERR_AUTHZ_UNWRITABLE
1044                             : SVN_ERR_AUTHZ_UNREADABLE,
1045                             NULL, "Access denied");
1046
1047   return SVN_NO_ERROR;
1048 }
1049 #endif
1050
1051
1052 /* This implements svn_editor_cb_add_directory_t */
1053 static svn_error_t *
1054 add_directory_cb(void *baton,
1055                  const char *relpath,
1056                  const apr_array_header_t *children,
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_directory(eb->inner, relpath, children, props,
1064                                    replaces_rev));
1065   return SVN_NO_ERROR;
1066 }
1067
1068
1069 /* This implements svn_editor_cb_add_file_t */
1070 static svn_error_t *
1071 add_file_cb(void *baton,
1072             const char *relpath,
1073             const svn_checksum_t *checksum,
1074             svn_stream_t *contents,
1075             apr_hash_t *props,
1076             svn_revnum_t replaces_rev,
1077             apr_pool_t *scratch_pool)
1078 {
1079   struct ev2_baton *eb = baton;
1080
1081   SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1082                               replaces_rev));
1083   return SVN_NO_ERROR;
1084 }
1085
1086
1087 /* This implements svn_editor_cb_add_symlink_t */
1088 static svn_error_t *
1089 add_symlink_cb(void *baton,
1090                const char *relpath,
1091                const char *target,
1092                apr_hash_t *props,
1093                svn_revnum_t replaces_rev,
1094                apr_pool_t *scratch_pool)
1095 {
1096   struct ev2_baton *eb = baton;
1097
1098   SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1099                                  replaces_rev));
1100   return SVN_NO_ERROR;
1101 }
1102
1103
1104 /* This implements svn_editor_cb_add_absent_t */
1105 static svn_error_t *
1106 add_absent_cb(void *baton,
1107               const char *relpath,
1108               svn_node_kind_t kind,
1109               svn_revnum_t replaces_rev,
1110               apr_pool_t *scratch_pool)
1111 {
1112   struct ev2_baton *eb = baton;
1113
1114   SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1115   return SVN_NO_ERROR;
1116 }
1117
1118
1119 /* This implements svn_editor_cb_alter_directory_t */
1120 static svn_error_t *
1121 alter_directory_cb(void *baton,
1122                    const char *relpath,
1123                    svn_revnum_t revision,
1124                    const apr_array_header_t *children,
1125                    apr_hash_t *props,
1126                    apr_pool_t *scratch_pool)
1127 {
1128   struct ev2_baton *eb = baton;
1129
1130   SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1131                                      children, props));
1132   return SVN_NO_ERROR;
1133 }
1134
1135
1136 /* This implements svn_editor_cb_alter_file_t */
1137 static svn_error_t *
1138 alter_file_cb(void *baton,
1139               const char *relpath,
1140               svn_revnum_t revision,
1141               apr_hash_t *props,
1142               const svn_checksum_t *checksum,
1143               svn_stream_t *contents,
1144               apr_pool_t *scratch_pool)
1145 {
1146   struct ev2_baton *eb = baton;
1147
1148   SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props,
1149                                 checksum, contents));
1150   return SVN_NO_ERROR;
1151 }
1152
1153
1154 /* This implements svn_editor_cb_alter_symlink_t */
1155 static svn_error_t *
1156 alter_symlink_cb(void *baton,
1157                  const char *relpath,
1158                  svn_revnum_t revision,
1159                  apr_hash_t *props,
1160                  const char *target,
1161                  apr_pool_t *scratch_pool)
1162 {
1163   struct ev2_baton *eb = baton;
1164
1165   SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props,
1166                                    target));
1167   return SVN_NO_ERROR;
1168 }
1169
1170
1171 /* This implements svn_editor_cb_delete_t */
1172 static svn_error_t *
1173 delete_cb(void *baton,
1174           const char *relpath,
1175           svn_revnum_t revision,
1176           apr_pool_t *scratch_pool)
1177 {
1178   struct ev2_baton *eb = baton;
1179
1180   SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1181   return SVN_NO_ERROR;
1182 }
1183
1184
1185 /* This implements svn_editor_cb_copy_t */
1186 static svn_error_t *
1187 copy_cb(void *baton,
1188         const char *src_relpath,
1189         svn_revnum_t src_revision,
1190         const char *dst_relpath,
1191         svn_revnum_t replaces_rev,
1192         apr_pool_t *scratch_pool)
1193 {
1194   struct ev2_baton *eb = baton;
1195
1196   SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1197                           replaces_rev));
1198   return SVN_NO_ERROR;
1199 }
1200
1201
1202 /* This implements svn_editor_cb_move_t */
1203 static svn_error_t *
1204 move_cb(void *baton,
1205         const char *src_relpath,
1206         svn_revnum_t src_revision,
1207         const char *dst_relpath,
1208         svn_revnum_t replaces_rev,
1209         apr_pool_t *scratch_pool)
1210 {
1211   struct ev2_baton *eb = baton;
1212
1213   SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1214                           replaces_rev));
1215   return SVN_NO_ERROR;
1216 }
1217
1218
1219 /* This implements svn_editor_cb_rotate_t */
1220 static svn_error_t *
1221 rotate_cb(void *baton,
1222           const apr_array_header_t *relpaths,
1223           const apr_array_header_t *revisions,
1224           apr_pool_t *scratch_pool)
1225 {
1226   struct ev2_baton *eb = baton;
1227
1228   SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions));
1229   return SVN_NO_ERROR;
1230 }
1231
1232
1233 /* This implements svn_editor_cb_complete_t */
1234 static svn_error_t *
1235 complete_cb(void *baton,
1236             apr_pool_t *scratch_pool)
1237 {
1238   struct ev2_baton *eb = baton;
1239   svn_revnum_t revision;
1240   svn_error_t *post_commit_err;
1241   const char *conflict_path;
1242   svn_error_t *err;
1243   const char *post_commit_errstr;
1244   apr_hash_t *hooks_env;
1245
1246   /* Parse the hooks-env file (if any). */
1247   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1248                                      scratch_pool, scratch_pool));
1249
1250   /* The transaction has been fully edited. Let the pre-commit hook
1251      have a look at the thing.  */
1252   SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1253                                       eb->txn_name, scratch_pool));
1254
1255   /* Hook is done. Let's do the actual commit.  */
1256   SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1257                                 eb->inner, scratch_pool, scratch_pool));
1258
1259   /* Did a conflict occur during the commit process?  */
1260   if (conflict_path != NULL)
1261     return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1262                              _("Conflict at '%s'"), conflict_path);
1263
1264   /* Since did not receive an error during the commit process, and no
1265      conflict was specified... we committed a revision. Run the hooks.
1266      Other errors may have occurred within the FS (specified by the
1267      POST_COMMIT_ERR localvar), but we need to run the hooks.  */
1268   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1269   err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1270                                      eb->txn_name, scratch_pool);
1271   if (err)
1272     err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1273                            _("Commit succeeded, but post-commit hook failed"));
1274
1275   /* Combine the FS errors with the hook errors, and stringify.  */
1276   err = svn_error_compose_create(post_commit_err, err);
1277   if (err)
1278     {
1279       post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1280       svn_error_clear(err);
1281     }
1282   else
1283     {
1284       post_commit_errstr = NULL;
1285     }
1286
1287   return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1288                                           eb->repos->fs, revision,
1289                                           post_commit_errstr,
1290                                           scratch_pool));
1291 }
1292
1293
1294 /* This implements svn_editor_cb_abort_t */
1295 static svn_error_t *
1296 abort_cb(void *baton,
1297          apr_pool_t *scratch_pool)
1298 {
1299   struct ev2_baton *eb = baton;
1300
1301   SVN_ERR(svn_editor_abort(eb->inner));
1302   return SVN_NO_ERROR;
1303 }
1304
1305
1306 static svn_error_t *
1307 apply_revprops(svn_fs_t *fs,
1308                const char *txn_name,
1309                apr_hash_t *revprops,
1310                apr_pool_t *scratch_pool)
1311 {
1312   svn_fs_txn_t *txn;
1313   const apr_array_header_t *revprops_array;
1314
1315   /* The FS editor has a TXN inside it, but we can't access it. Open another
1316      based on the TXN_NAME.  */
1317   SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1318
1319   /* Validate and apply the revision properties.  */
1320   revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1321   SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1322
1323   /* ### do we need to force the txn to close, or is it enough to wait
1324      ### for the pool to be cleared?  */
1325   return SVN_NO_ERROR;
1326 }
1327
1328
1329 svn_error_t *
1330 svn_repos__get_commit_ev2(svn_editor_t **editor,
1331                           svn_repos_t *repos,
1332                           svn_authz_t *authz,
1333                           const char *authz_repos_name,
1334                           const char *authz_user,
1335                           apr_hash_t *revprops,
1336                           svn_commit_callback2_t commit_cb,
1337                           void *commit_baton,
1338                           svn_cancel_func_t cancel_func,
1339                           void *cancel_baton,
1340                           apr_pool_t *result_pool,
1341                           apr_pool_t *scratch_pool)
1342 {
1343   static const svn_editor_cb_many_t editor_cbs = {
1344     add_directory_cb,
1345     add_file_cb,
1346     add_symlink_cb,
1347     add_absent_cb,
1348     alter_directory_cb,
1349     alter_file_cb,
1350     alter_symlink_cb,
1351     delete_cb,
1352     copy_cb,
1353     move_cb,
1354     rotate_cb,
1355     complete_cb,
1356     abort_cb
1357   };
1358   struct ev2_baton *eb;
1359   const svn_string_t *author;
1360   apr_hash_t *hooks_env;
1361
1362   /* Parse the hooks-env file (if any). */
1363   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1364                                      scratch_pool, scratch_pool));
1365
1366   /* Can the user modify the repository at all?  */
1367   /* ### check against AUTHZ.  */
1368
1369   author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1370
1371   eb = apr_palloc(result_pool, sizeof(*eb));
1372   eb->repos = repos;
1373   eb->authz = authz;
1374   eb->authz_repos_name = authz_repos_name;
1375   eb->authz_user = authz_user;
1376   eb->commit_cb = commit_cb;
1377   eb->commit_baton = commit_baton;
1378
1379   SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1380                                 repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1381                                 cancel_func, cancel_baton,
1382                                 result_pool, scratch_pool));
1383
1384   /* The TXN has been created. Go ahead and apply all revision properties.  */
1385   SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1386
1387   /* Okay... some access is allowed. Let's run the start-commit hook.  */
1388   SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1389                                         author ? author->data : NULL,
1390                                         repos->client_capabilities,
1391                                         eb->txn_name, scratch_pool));
1392
1393   /* Wrap the FS editor within our editor.  */
1394   SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1395                             result_pool, scratch_pool));
1396   SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1397
1398   return SVN_NO_ERROR;
1399 }