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