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