xref: /freebsd-13-stable/contrib/subversion/subversion/libsvn_repos/commit.c (revision b7ec5dea64b6513b41316a38cc72efa9139bc4ae)
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 
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 *
out_of_date(const char * path,svn_node_kind_t kind)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 *
check_out_of_date(struct edit_baton * eb,const char * path,svn_node_kind_t kind,svn_revnum_t base_rev,svn_revnum_t created_rev)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 *
invoke_commit_cb(svn_commit_callback2_t commit_cb,void * commit_baton,svn_fs_t * fs,svn_revnum_t revision,const char * post_commit_errstr,apr_pool_t * scratch_pool)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 
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 *
check_authz(struct edit_baton * editor_baton,const char * path,svn_fs_root_t * root,svn_repos_authz_access_t required,apr_pool_t * pool)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 *
make_dir_baton(struct edit_baton * edit_baton,struct dir_baton * parent_baton,const char * full_path,svn_boolean_t was_copied,svn_revnum_t base_revision,apr_pool_t * pool)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 *
add_file_or_directory(const char * path,void * parent_baton,const char * copy_path,svn_revnum_t copy_revision,svn_boolean_t is_dir,apr_pool_t * pool,void ** return_baton)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 
412 /*** Editor functions ***/
413 
414 static svn_error_t *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** root_baton)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 *
delete_entry(const char * path,svn_revnum_t revision,void * parent_baton,apr_pool_t * pool)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 *
add_directory(const char * path,void * parent_baton,const char * copy_path,svn_revnum_t copy_revision,apr_pool_t * pool,void ** child_baton)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 *
open_directory(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** child_baton)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 *
apply_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)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 *
add_file(const char * path,void * parent_baton,const char * copy_path,svn_revnum_t copy_revision,apr_pool_t * pool,void ** file_baton)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 *
open_file(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** file_baton)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 *
change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)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 *
close_file(void * file_baton,const char * text_digest,apr_pool_t * pool)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 *
change_dir_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)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 *
svn_repos__post_commit_error_str(svn_error_t * err,apr_pool_t * pool)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 *
close_edit(void * edit_baton,apr_pool_t * pool)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 *
abort_edit(void * edit_baton,apr_pool_t * pool)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 *
fetch_props_func(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
fetch_kind_func(svn_node_kind_t * kind,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * scratch_pool)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 *
fetch_base_func(const char ** filename,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 
993 /*** Public interfaces. ***/
994 
995 svn_error_t *
svn_repos_get_commit_editor5(const svn_delta_editor_t ** editor,void ** edit_baton,svn_repos_t * repos,svn_fs_txn_t * txn,const char * repos_url_decoded,const char * base_path,apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_repos_authz_callback_t authz_callback,void * authz_baton,apr_pool_t * pool)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 *
add_directory_cb(void * baton,const char * relpath,const apr_array_header_t * children,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_file_cb(void * baton,const char * relpath,const svn_checksum_t * checksum,svn_stream_t * contents,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_symlink_cb(void * baton,const char * relpath,const char * target,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
add_absent_cb(void * baton,const char * relpath,svn_node_kind_t kind,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
alter_directory_cb(void * baton,const char * relpath,svn_revnum_t revision,const apr_array_header_t * children,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
alter_file_cb(void * baton,const char * relpath,svn_revnum_t revision,const svn_checksum_t * checksum,svn_stream_t * contents,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
alter_symlink_cb(void * baton,const char * relpath,svn_revnum_t revision,const char * target,apr_hash_t * props,apr_pool_t * scratch_pool)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 *
delete_cb(void * baton,const char * relpath,svn_revnum_t revision,apr_pool_t * scratch_pool)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 *
copy_cb(void * baton,const char * src_relpath,svn_revnum_t src_revision,const char * dst_relpath,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
move_cb(void * baton,const char * src_relpath,svn_revnum_t src_revision,const char * dst_relpath,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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 *
complete_cb(void * baton,apr_pool_t * scratch_pool)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 *
abort_cb(void * baton,apr_pool_t * scratch_pool)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 *
apply_revprops(svn_fs_t * fs,const char * txn_name,apr_hash_t * revprops,apr_pool_t * scratch_pool)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 *
svn_repos__get_commit_ev2(svn_editor_t ** editor,svn_repos_t * repos,svn_authz_t * authz,const char * authz_repos_name,const char * authz_user,apr_hash_t * revprops,svn_commit_callback2_t commit_cb,void * commit_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 }
1444