1/* commit.c --- editor for committing changes to a filesystem.
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23
24#include <string.h>
25
26#include <apr_pools.h>
27#include <apr_file_io.h>
28
29#include "svn_hash.h"
30#include "svn_compat.h"
31#include "svn_pools.h"
32#include "svn_error.h"
33#include "svn_dirent_uri.h"
34#include "svn_path.h"
35#include "svn_delta.h"
36#include "svn_fs.h"
37#include "svn_repos.h"
38#include "svn_checksum.h"
39#include "svn_ctype.h"
40#include "svn_props.h"
41#include "svn_mergeinfo.h"
42#include "svn_private_config.h"
43
44#include "repos.h"
45
46#include "private/svn_fspath.h"
47#include "private/svn_fs_private.h"
48#include "private/svn_repos_private.h"
49#include "private/svn_editor.h"
50
51
52
53/*** Editor batons. ***/
54
55struct edit_baton
56{
57  apr_pool_t *pool;
58
59  /** Supplied when the editor is created: **/
60
61  /* Revision properties to set for this commit. */
62  apr_hash_t *revprop_table;
63
64  /* Callback to run when the commit is done. */
65  svn_commit_callback2_t commit_callback;
66  void *commit_callback_baton;
67
68  /* Callback to check authorizations on paths. */
69  svn_repos_authz_callback_t authz_callback;
70  void *authz_baton;
71
72  /* The already-open svn repository to commit to. */
73  svn_repos_t *repos;
74
75  /* URL to the root of the open repository. */
76  const char *repos_url_decoded;
77
78  /* The name of the repository (here for convenience). */
79  const char *repos_name;
80
81  /* The filesystem associated with the REPOS above (here for
82     convenience). */
83  svn_fs_t *fs;
84
85  /* Location in fs where the edit will begin. */
86  const char *base_path;
87
88  /* Does this set of interfaces 'own' the commit transaction? */
89  svn_boolean_t txn_owner;
90
91  /* svn transaction associated with this edit (created in
92     open_root, or supplied by the public API caller). */
93  svn_fs_txn_t *txn;
94
95  /** Filled in during open_root: **/
96
97  /* The name of the transaction. */
98  const char *txn_name;
99
100  /* The object representing the root directory of the svn txn. */
101  svn_fs_root_t *txn_root;
102
103  /* Avoid aborting an fs transaction more than once */
104  svn_boolean_t txn_aborted;
105
106  /** Filled in when the edit is closed: **/
107
108  /* The new revision created by this commit. */
109  svn_revnum_t *new_rev;
110
111  /* The date (according to the repository) of this commit. */
112  const char **committed_date;
113
114  /* The author (also according to the repository) of this commit. */
115  const char **committed_author;
116};
117
118
119struct dir_baton
120{
121  struct edit_baton *edit_baton;
122  struct dir_baton *parent;
123  const char *path; /* the -absolute- path to this dir in the fs */
124  svn_revnum_t base_rev;        /* the revision I'm based on  */
125  svn_boolean_t was_copied; /* was this directory added with history? */
126  apr_pool_t *pool; /* my personal pool, in which I am allocated. */
127  svn_boolean_t checked_write; /* TRUE after successfull write check */
128};
129
130
131struct 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
139struct 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. */
164static svn_error_t *
165out_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. */
178static svn_error_t *
179check_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
201static svn_error_t *
202invoke_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
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. */
243static svn_error_t *
244check_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. */
271static struct dir_baton *
272make_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. */
296static svn_error_t *
297add_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
412/*** Editor functions ***/
413
414static svn_error_t *
415open_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
471static svn_error_t *
472delete_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
520static svn_error_t *
521add_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
533static svn_error_t *
534open_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
564static svn_error_t *
565apply_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
592static svn_error_t *
593add_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
605static svn_error_t *
606open_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
651static svn_error_t *
652change_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
673static svn_error_t *
674close_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
701static svn_error_t *
702change_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
735const char *
736svn_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
799static svn_error_t *
800close_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
886static svn_error_t *
887abort_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
904static svn_error_t *
905fetch_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
932static svn_error_t *
933fetch_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
952static svn_error_t *
953fetch_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
993/*** Public interfaces. ***/
994
995svn_error_t *
996svn_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
1080static svn_error_t *
1081ev2_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 */
1112static svn_error_t *
1113add_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 */
1129static svn_error_t *
1130add_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 */
1147static svn_error_t *
1148add_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 */
1164static svn_error_t *
1165add_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 */
1179static svn_error_t *
1180alter_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 */
1196static svn_error_t *
1197alter_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 */
1214static svn_error_t *
1215alter_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 */
1231static svn_error_t *
1232delete_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 */
1245static svn_error_t *
1246copy_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 */
1262static svn_error_t *
1263move_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 */
1279static svn_error_t *
1280complete_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 */
1340static svn_error_t *
1341abort_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
1351static svn_error_t *
1352apply_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
1374svn_error_t *
1375svn_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}
1444