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