commit.c revision 289166
1231200Smm/* commit.c --- editor for committing changes to a filesystem.
2231200Smm *
3231200Smm * ====================================================================
4231200Smm *    Licensed to the Apache Software Foundation (ASF) under one
5231200Smm *    or more contributor license agreements.  See the NOTICE file
6231200Smm *    distributed with this work for additional information
7231200Smm *    regarding copyright ownership.  The ASF licenses this file
8231200Smm *    to you under the Apache License, Version 2.0 (the
9231200Smm *    "License"); you may not use this file except in compliance
10231200Smm *    with the License.  You may obtain a copy of the License at
11231200Smm *
12231200Smm *      http://www.apache.org/licenses/LICENSE-2.0
13231200Smm *
14231200Smm *    Unless required by applicable law or agreed to in writing,
15231200Smm *    software distributed under the License is distributed on an
16231200Smm *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17231200Smm *    KIND, either express or implied.  See the License for the
18231200Smm *    specific language governing permissions and limitations
19231200Smm *    under the License.
20231200Smm * ====================================================================
21231200Smm */
22231200Smm
23231200Smm
24231200Smm#include <string.h>
25238856Smm
26231200Smm#include <apr_pools.h>
27358090Smm#include <apr_file_io.h>
28231200Smm
29231200Smm#include "svn_hash.h"
30231200Smm#include "svn_compat.h"
31231200Smm#include "svn_pools.h"
32231200Smm#include "svn_error.h"
33231200Smm#include "svn_dirent_uri.h"
34231200Smm#include "svn_path.h"
35302295Smm#include "svn_delta.h"
36238856Smm#include "svn_fs.h"
37238856Smm#include "svn_repos.h"
38231200Smm#include "svn_checksum.h"
39231200Smm#include "svn_ctype.h"
40231200Smm#include "svn_props.h"
41231200Smm#include "svn_mergeinfo.h"
42231200Smm#include "svn_private_config.h"
43231200Smm
44231200Smm#include "repos.h"
45231200Smm
46231200Smm#include "private/svn_fspath.h"
47231200Smm#include "private/svn_fs_private.h"
48231200Smm#include "private/svn_repos_private.h"
49231200Smm#include "private/svn_editor.h"
50231200Smm
51231200Smm
52231200Smm
53231200Smm/*** Editor batons. ***/
54231200Smm
55231200Smmstruct edit_baton
56231200Smm{
57231200Smm  apr_pool_t *pool;
58231200Smm
59231200Smm  /** Supplied when the editor is created: **/
60231200Smm
61231200Smm  /* Revision properties to set for this commit. */
62231200Smm  apr_hash_t *revprop_table;
63231200Smm
64231200Smm  /* Callback to run when the commit is done. */
65231200Smm  svn_commit_callback2_t commit_callback;
66231200Smm  void *commit_callback_baton;
67231200Smm
68231200Smm  /* Callback to check authorizations on paths. */
69231200Smm  svn_repos_authz_callback_t authz_callback;
70231200Smm  void *authz_baton;
71231200Smm
72231200Smm  /* The already-open svn repository to commit to. */
73353377Smm  svn_repos_t *repos;
74231200Smm
75231200Smm  /* URL to the root of the open repository. */
76231200Smm  const char *repos_url;
77231200Smm
78231200Smm  /* The name of the repository (here for convenience). */
79231200Smm  const char *repos_name;
80231200Smm
81231200Smm  /* The filesystem associated with the REPOS above (here for
82231200Smm     convenience). */
83231200Smm  svn_fs_t *fs;
84231200Smm
85231200Smm  /* Location in fs where the edit will begin. */
86231200Smm  const char *base_path;
87231200Smm
88231200Smm  /* Does this set of interfaces 'own' the commit transaction? */
89231200Smm  svn_boolean_t txn_owner;
90231200Smm
91231200Smm  /* svn transaction associated with this edit (created in
92231200Smm     open_root, or supplied by the public API caller). */
93231200Smm  svn_fs_txn_t *txn;
94231200Smm
95231200Smm  /** Filled in during open_root: **/
96231200Smm
97231200Smm  /* The name of the transaction. */
98231200Smm  const char *txn_name;
99231200Smm
100231200Smm  /* The object representing the root directory of the svn txn. */
101231200Smm  svn_fs_root_t *txn_root;
102231200Smm
103231200Smm  /* Avoid aborting an fs transaction more than once */
104302001Smm  svn_boolean_t txn_aborted;
105302001Smm
106302001Smm  /** Filled in when the edit is closed: **/
107302001Smm
108302001Smm  /* The new revision created by this commit. */
109302001Smm  svn_revnum_t *new_rev;
110231200Smm
111231200Smm  /* The date (according to the repository) of this commit. */
112231200Smm  const char **committed_date;
113231200Smm
114231200Smm  /* The author (also according to the repository) of this commit. */
115231200Smm  const char **committed_author;
116231200Smm};
117231200Smm
118231200Smm
119231200Smmstruct dir_baton
120231200Smm{
121231200Smm  struct edit_baton *edit_baton;
122231200Smm  struct dir_baton *parent;
123231200Smm  const char *path; /* the -absolute- path to this dir in the fs */
124231200Smm  svn_revnum_t base_rev;        /* the revision I'm based on  */
125231200Smm  svn_boolean_t was_copied; /* was this directory added with history? */
126231200Smm  apr_pool_t *pool; /* my personal pool, in which I am allocated. */
127231200Smm};
128231200Smm
129231200Smm
130231200Smmstruct file_baton
131302001Smm{
132231200Smm  struct edit_baton *edit_baton;
133231200Smm  const char *path; /* the -absolute- path to this file in the fs */
134231200Smm};
135231200Smm
136231200Smm
137231200Smmstruct ev2_baton
138231200Smm{
139231200Smm  /* The repository we are editing.  */
140231200Smm  svn_repos_t *repos;
141353377Smm
142231200Smm  /* The authz baton for checks; NULL to skip authz.  */
143302001Smm  svn_authz_t *authz;
144231200Smm
145231200Smm  /* The repository name and user for performing authz checks.  */
146231200Smm  const char *authz_repos_name;
147231200Smm  const char *authz_user;
148231200Smm
149231200Smm  /* Callback to provide info about the committed revision.  */
150231200Smm  svn_commit_callback2_t commit_cb;
151231200Smm  void *commit_baton;
152231200Smm
153231200Smm  /* The FS txn editor  */
154231200Smm  svn_editor_t *inner;
155231200Smm
156231200Smm  /* The name of the open transaction (so we know what to commit)  */
157231200Smm  const char *txn_name;
158231200Smm};
159231200Smm
160231200Smm
161231200Smm/* Create and return a generic out-of-dateness error. */
162231200Smmstatic svn_error_t *
163231200Smmout_of_date(const char *path, svn_node_kind_t kind)
164231200Smm{
165231200Smm  return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
166231200Smm                           (kind == svn_node_dir
167231200Smm                            ? _("Directory '%s' is out of date")
168231200Smm                            : kind == svn_node_file
169231200Smm                            ? _("File '%s' is out of date")
170231200Smm                            : _("'%s' is out of date")),
171231200Smm                           path);
172231200Smm}
173358090Smm
174358090Smm
175358090Smmstatic svn_error_t *
176358090Smminvoke_commit_cb(svn_commit_callback2_t commit_cb,
177358090Smm                 void *commit_baton,
178358090Smm                 svn_fs_t *fs,
179358090Smm                 svn_revnum_t revision,
180358090Smm                 const char *post_commit_errstr,
181358090Smm                 apr_pool_t *scratch_pool)
182358090Smm{
183358090Smm  /* FS interface returns non-const values.  */
184358090Smm  /* const */ svn_string_t *date;
185358090Smm  /* const */ svn_string_t *author;
186231200Smm  svn_commit_info_t *commit_info;
187231200Smm
188231200Smm  if (commit_cb == NULL)
189231200Smm    return SVN_NO_ERROR;
190358090Smm
191358090Smm  SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE,
192358090Smm                               scratch_pool));
193231200Smm  SVN_ERR(svn_fs_revision_prop(&author, fs, revision,
194358090Smm                               SVN_PROP_REVISION_AUTHOR,
195358090Smm                               scratch_pool));
196358090Smm
197358090Smm  commit_info = svn_create_commit_info(scratch_pool);
198358090Smm
199358090Smm  /* fill up the svn_commit_info structure */
200358090Smm  commit_info->revision = revision;
201358090Smm  commit_info->date = date ? date->data : NULL;
202358090Smm  commit_info->author = author ? author->data : NULL;
203358090Smm  commit_info->post_commit_err = post_commit_errstr;
204358090Smm
205358090Smm  return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
206358090Smm}
207358090Smm
208358090Smm
209358090Smm
210358090Smm/* If EDITOR_BATON contains a valid authz callback, verify that the
211358090Smm   REQUIRED access to PATH in ROOT is authorized.  Return an error
212358090Smm   appropriate for throwing out of the commit editor with SVN_ERR.  If
213358090Smm   no authz callback is present in EDITOR_BATON, then authorize all
214358090Smm   paths.  Use POOL for temporary allocation only. */
215358090Smmstatic svn_error_t *
216358090Smmcheck_authz(struct edit_baton *editor_baton, const char *path,
217358090Smm            svn_fs_root_t *root, svn_repos_authz_access_t required,
218358090Smm            apr_pool_t *pool)
219358090Smm{
220358090Smm  if (editor_baton->authz_callback)
221358090Smm    {
222358090Smm      svn_boolean_t allowed;
223358090Smm
224358090Smm      SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
225358090Smm                                           editor_baton->authz_baton, pool));
226358090Smm      if (!allowed)
227358090Smm        return svn_error_create(required & svn_authz_write ?
228358090Smm                                SVN_ERR_AUTHZ_UNWRITABLE :
229358090Smm                                SVN_ERR_AUTHZ_UNREADABLE,
230358090Smm                                NULL, "Access denied");
231358090Smm    }
232358090Smm
233358090Smm  return SVN_NO_ERROR;
234358090Smm}
235358090Smm
236358090Smm
237358090Smm/* Return a directory baton allocated in POOL which represents
238358090Smm   FULL_PATH, which is the immediate directory child of the directory
239358090Smm   represented by PARENT_BATON.  EDIT_BATON is the commit editor
240358090Smm   baton.  WAS_COPIED reveals whether or not this directory is the
241358090Smm   result of a copy operation.  BASE_REVISION is the base revision of
242358090Smm   the directory. */
243231200Smmstatic struct dir_baton *
244231200Smmmake_dir_baton(struct edit_baton *edit_baton,
245231200Smm               struct dir_baton *parent_baton,
246231200Smm               const char *full_path,
247358090Smm               svn_boolean_t was_copied,
248358090Smm               svn_revnum_t base_revision,
249358090Smm               apr_pool_t *pool)
250358090Smm{
251358090Smm  struct dir_baton *db;
252358090Smm  db = apr_pcalloc(pool, sizeof(*db));
253358090Smm  db->edit_baton = edit_baton;
254358090Smm  db->parent = parent_baton;
255358090Smm  db->pool = pool;
256358090Smm  db->path = full_path;
257358090Smm  db->was_copied = was_copied;
258362134Smm  db->base_rev = base_revision;
259362134Smm  return db;
260358090Smm}
261358090Smm
262358090Smm/* This function is the shared guts of add_file() and add_directory(),
263358090Smm   which see for the meanings of the parameters.  The only extra
264358090Smm   parameter here is IS_DIR, which is TRUE when adding a directory,
265358090Smm   and FALSE when adding a file.  */
266358090Smmstatic svn_error_t *
267358090Smmadd_file_or_directory(const char *path,
268358090Smm                      void *parent_baton,
269358090Smm                      const char *copy_path,
270358090Smm                      svn_revnum_t copy_revision,
271358090Smm                      svn_boolean_t is_dir,
272358090Smm                      apr_pool_t *pool,
273358090Smm                      void **return_baton)
274358090Smm{
275358090Smm  struct dir_baton *pb = parent_baton;
276358090Smm  struct edit_baton *eb = pb->edit_baton;
277231200Smm  apr_pool_t *subpool = svn_pool_create(pool);
278358090Smm  svn_boolean_t was_copied = FALSE;
279358090Smm  const char *full_path;
280358090Smm
281231200Smm  /* Reject paths which contain control characters (related to issue #4340). */
282358090Smm  SVN_ERR(svn_path_check_valid(path, pool));
283231200Smm
284358090Smm  full_path = svn_fspath__join(eb->base_path,
285358090Smm                               svn_relpath_canonicalize(path, pool), pool);
286358090Smm
287231200Smm  /* Sanity check. */
288358090Smm  if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
289358090Smm    return svn_error_createf
290358090Smm      (SVN_ERR_FS_GENERAL, NULL,
291358090Smm       _("Got source path but no source revision for '%s'"), full_path);
292358090Smm
293358090Smm  if (copy_path)
294231200Smm    {
295231200Smm      const char *fs_path;
296231200Smm      svn_fs_root_t *copy_root;
297231200Smm      svn_node_kind_t kind;
298231200Smm      size_t repos_url_len;
299353377Smm      svn_repos_authz_access_t required;
300353377Smm
301231200Smm      /* Copy requires recursive write access to the destination path
302231200Smm         and write access to the parent path. */
303353377Smm      required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
304353377Smm      SVN_ERR(check_authz(eb, full_path, eb->txn_root,
305231200Smm                          required, subpool));
306231200Smm      SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
307353377Smm                          svn_authz_write, subpool));
308353377Smm
309231200Smm      /* Check PATH in our transaction.  Make sure it does not exist
310231200Smm         unless its parent directory was copied (in which case, the
311353377Smm         thing might have been copied in as well), else return an
312353377Smm         out-of-dateness error. */
313231200Smm      SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
314231200Smm      if ((kind != svn_node_none) && (! pb->was_copied))
315353377Smm        return svn_error_trace(out_of_date(full_path, kind));
316353377Smm
317231200Smm      /* For now, require that the url come from the same repository
318231200Smm         that this commit is operating on. */
319353377Smm      copy_path = svn_path_uri_decode(copy_path, subpool);
320353377Smm      repos_url_len = strlen(eb->repos_url);
321231200Smm      if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0)
322231200Smm        return svn_error_createf
323231200Smm          (SVN_ERR_FS_GENERAL, NULL,
324231200Smm           _("Source url '%s' is from different repository"), copy_path);
325231200Smm
326231200Smm      fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
327231200Smm
328231200Smm      /* Now use the "fs_path" as an absolute path within the
329231200Smm         repository to make the copy from. */
330231200Smm      SVN_ERR(svn_fs_revision_root(&copy_root, eb->fs,
331231200Smm                                   copy_revision, subpool));
332231200Smm
333231200Smm      /* Copy also requires (recursive) read access to the source */
334231200Smm      required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
335231200Smm      SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
336231200Smm
337231200Smm      SVN_ERR(svn_fs_copy(copy_root, fs_path,
338231200Smm                          eb->txn_root, full_path, subpool));
339231200Smm      was_copied = TRUE;
340231200Smm    }
341231200Smm  else
342231200Smm    {
343231200Smm      /* No ancestry given, just make a new directory or empty file.
344231200Smm         Note that we don't perform an existence check here like the
345231200Smm         copy-from case does -- that's because svn_fs_make_*()
346231200Smm         already errors out if the file already exists.  Verify write
347231200Smm         access to the full path and to the parent. */
348231200Smm      SVN_ERR(check_authz(eb, full_path, eb->txn_root,
349231200Smm                          svn_authz_write, subpool));
350231200Smm      SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
351231200Smm                          svn_authz_write, subpool));
352231200Smm      if (is_dir)
353231200Smm        SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
354231200Smm      else
355231200Smm        SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
356231200Smm    }
357231200Smm
358302001Smm  /* Cleanup our temporary subpool. */
359231200Smm  svn_pool_destroy(subpool);
360231200Smm
361231200Smm  /* Build a new child baton. */
362231200Smm  if (is_dir)
363231200Smm    {
364231200Smm      *return_baton = make_dir_baton(eb, pb, full_path, was_copied,
365231200Smm                                     SVN_INVALID_REVNUM, pool);
366231200Smm    }
367231200Smm  else
368353377Smm    {
369231200Smm      struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
370231200Smm      new_fb->edit_baton = eb;
371231200Smm      new_fb->path = full_path;
372231200Smm      *return_baton = new_fb;
373231200Smm    }
374231200Smm
375231200Smm  return SVN_NO_ERROR;
376231200Smm}
377231200Smm
378231200Smm
379231200Smm
380231200Smm/*** Editor functions ***/
381231200Smm
382231200Smmstatic svn_error_t *
383231200Smmopen_root(void *edit_baton,
384231200Smm          svn_revnum_t base_revision,
385231200Smm          apr_pool_t *pool,
386231200Smm          void **root_baton)
387231200Smm{
388231200Smm  struct dir_baton *dirb;
389231200Smm  struct edit_baton *eb = edit_baton;
390231200Smm  svn_revnum_t youngest;
391231200Smm
392353377Smm  /* Ignore BASE_REVISION.  We always build our transaction against
393231200Smm     HEAD.  However, we will keep it in our dir baton for out of
394231200Smm     dateness checks.  */
395231200Smm  SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
396231200Smm
397231200Smm  /* Unless we've been instructed to use a specific transaction, we'll
398231200Smm     make our own. */
399231200Smm  if (eb->txn_owner)
400231200Smm    {
401231200Smm      SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
402231200Smm                                                 eb->repos,
403231200Smm                                                 youngest,
404231200Smm                                                 eb->revprop_table,
405231200Smm                                                 eb->pool));
406231200Smm    }
407231200Smm  else /* Even if we aren't the owner of the transaction, we might
408231200Smm          have been instructed to set some properties. */
409231200Smm    {
410231200Smm      apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
411231200Smm                                                         pool);
412231200Smm      SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
413231200Smm    }
414231200Smm  SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
415231200Smm  SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
416231200Smm
417231200Smm  /* Create a root dir baton.  The `base_path' field is an -absolute-
418231200Smm     path in the filesystem, upon which all further editor paths are
419231200Smm     based. */
420231200Smm  dirb = apr_pcalloc(pool, sizeof(*dirb));
421231200Smm  dirb->edit_baton = edit_baton;
422231200Smm  dirb->parent = NULL;
423231200Smm  dirb->pool = pool;
424231200Smm  dirb->was_copied = FALSE;
425231200Smm  dirb->path = apr_pstrdup(pool, eb->base_path);
426231200Smm  dirb->base_rev = base_revision;
427231200Smm
428231200Smm  *root_baton = dirb;
429231200Smm  return SVN_NO_ERROR;
430231200Smm}
431231200Smm
432231200Smm
433231200Smm
434231200Smmstatic svn_error_t *
435231200Smmdelete_entry(const char *path,
436231200Smm             svn_revnum_t revision,
437231200Smm             void *parent_baton,
438231200Smm             apr_pool_t *pool)
439231200Smm{
440231200Smm  struct dir_baton *parent = parent_baton;
441231200Smm  struct edit_baton *eb = parent->edit_baton;
442231200Smm  svn_node_kind_t kind;
443231200Smm  svn_revnum_t cr_rev;
444231200Smm  svn_repos_authz_access_t required = svn_authz_write;
445231200Smm  const char *full_path;
446231200Smm
447231200Smm  full_path = svn_fspath__join(eb->base_path,
448231200Smm                               svn_relpath_canonicalize(path, pool), pool);
449231200Smm
450231200Smm  /* Check PATH in our transaction.  */
451231200Smm  SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
452231200Smm
453231200Smm  /* Deletion requires a recursive write access, as well as write
454231200Smm     access to the parent directory. */
455231200Smm  if (kind == svn_node_dir)
456231200Smm    required |= svn_authz_recursive;
457231200Smm  SVN_ERR(check_authz(eb, full_path, eb->txn_root,
458231200Smm                      required, pool));
459231200Smm  SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
460231200Smm                      svn_authz_write, pool));
461231200Smm
462231200Smm  /* If PATH doesn't exist in the txn, the working copy is out of date. */
463231200Smm  if (kind == svn_node_none)
464231200Smm    return svn_error_trace(out_of_date(full_path, kind));
465231200Smm
466231200Smm  /* Now, make sure we're deleting the node we *think* we're
467231200Smm     deleting, else return an out-of-dateness error. */
468231200Smm  SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
469231200Smm  if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
470231200Smm    return svn_error_trace(out_of_date(full_path, kind));
471231200Smm
472231200Smm  /* This routine is a mindless wrapper.  We call svn_fs_delete()
473231200Smm     because that will delete files and recursively delete
474231200Smm     directories.  */
475231200Smm  return svn_fs_delete(eb->txn_root, full_path, pool);
476231200Smm}
477231200Smm
478231200Smm
479231200Smmstatic svn_error_t *
480231200Smmadd_directory(const char *path,
481231200Smm              void *parent_baton,
482231200Smm              const char *copy_path,
483231200Smm              svn_revnum_t copy_revision,
484231200Smm              apr_pool_t *pool,
485231200Smm              void **child_baton)
486231200Smm{
487231200Smm  return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
488231200Smm                               TRUE /* is_dir */, pool, child_baton);
489231200Smm}
490231200Smm
491231200Smm
492231200Smmstatic svn_error_t *
493231200Smmopen_directory(const char *path,
494231200Smm               void *parent_baton,
495231200Smm               svn_revnum_t base_revision,
496231200Smm               apr_pool_t *pool,
497231200Smm               void **child_baton)
498231200Smm{
499231200Smm  struct dir_baton *pb = parent_baton;
500358090Smm  struct edit_baton *eb = pb->edit_baton;
501353377Smm  svn_node_kind_t kind;
502358090Smm  const char *full_path;
503358090Smm
504358090Smm  full_path = svn_fspath__join(eb->base_path,
505358090Smm                               svn_relpath_canonicalize(path, pool), pool);
506358090Smm
507358090Smm  /* Check PATH in our transaction.  If it does not exist,
508358090Smm     return a 'Path not present' error. */
509358090Smm  SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
510358090Smm  if (kind == svn_node_none)
511358090Smm    return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
512358090Smm                             _("Path '%s' not present"),
513358090Smm                             path);
514358090Smm
515358090Smm  /* Build a new dir baton for this directory. */
516358090Smm  *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
517358090Smm                                base_revision, pool);
518358090Smm  return SVN_NO_ERROR;
519358090Smm}
520358090Smm
521358090Smm
522358090Smmstatic svn_error_t *
523358090Smmapply_textdelta(void *file_baton,
524358090Smm                const char *base_checksum,
525358090Smm                apr_pool_t *pool,
526358090Smm                svn_txdelta_window_handler_t *handler,
527353377Smm                void **handler_baton)
528358090Smm{
529353377Smm  struct file_baton *fb = file_baton;
530358090Smm
531358090Smm  /* Check for write authorization. */
532358090Smm  SVN_ERR(check_authz(fb->edit_baton, fb->path,
533358090Smm                      fb->edit_baton->txn_root,
534358090Smm                      svn_authz_write, pool));
535358090Smm
536358090Smm  return svn_fs_apply_textdelta(handler, handler_baton,
537358090Smm                                fb->edit_baton->txn_root,
538358090Smm                                fb->path,
539358090Smm                                base_checksum,
540358090Smm                                NULL,
541358090Smm                                pool);
542358090Smm}
543358090Smm
544358090Smm
545358090Smmstatic svn_error_t *
546358090Smmadd_file(const char *path,
547358090Smm         void *parent_baton,
548353377Smm         const char *copy_path,
549358090Smm         svn_revnum_t copy_revision,
550358090Smm         apr_pool_t *pool,
551358090Smm         void **file_baton)
552358090Smm{
553358090Smm  return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
554358090Smm                               FALSE /* is_dir */, pool, file_baton);
555358090Smm}
556358090Smm
557358090Smm
558358090Smmstatic svn_error_t *
559358090Smmopen_file(const char *path,
560358090Smm          void *parent_baton,
561358090Smm          svn_revnum_t base_revision,
562358090Smm          apr_pool_t *pool,
563358090Smm          void **file_baton)
564358090Smm{
565358090Smm  struct file_baton *new_fb;
566358090Smm  struct dir_baton *pb = parent_baton;
567358090Smm  struct edit_baton *eb = pb->edit_baton;
568358090Smm  svn_revnum_t cr_rev;
569358090Smm  apr_pool_t *subpool = svn_pool_create(pool);
570358090Smm  const char *full_path;
571358090Smm
572358090Smm  full_path = svn_fspath__join(eb->base_path,
573358090Smm                               svn_relpath_canonicalize(path, pool), pool);
574358090Smm
575358090Smm  /* Check for read authorization. */
576358090Smm  SVN_ERR(check_authz(eb, full_path, eb->txn_root,
577358090Smm                      svn_authz_read, subpool));
578358090Smm
579358090Smm  /* Get this node's creation revision (doubles as an existence check). */
580358090Smm  SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
581358090Smm                                  subpool));
582358090Smm
583358090Smm  /* If the node our caller has is an older revision number than the
584358090Smm     one in our transaction, return an out-of-dateness error. */
585358090Smm  if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
586358090Smm    return svn_error_trace(out_of_date(full_path, svn_node_file));
587358090Smm
588358090Smm  /* Build a new file baton */
589358090Smm  new_fb = apr_pcalloc(pool, sizeof(*new_fb));
590358090Smm  new_fb->edit_baton = eb;
591358090Smm  new_fb->path = full_path;
592358090Smm
593358090Smm  *file_baton = new_fb;
594358090Smm
595358090Smm  /* Destory the work subpool. */
596358090Smm  svn_pool_destroy(subpool);
597358090Smm
598358090Smm  return SVN_NO_ERROR;
599358090Smm}
600358090Smm
601358090Smm
602358090Smmstatic svn_error_t *
603358090Smmchange_file_prop(void *file_baton,
604358090Smm                 const char *name,
605302001Smm                 const svn_string_t *value,
606302001Smm                 apr_pool_t *pool)
607302001Smm{
608302001Smm  struct file_baton *fb = file_baton;
609302001Smm  struct edit_baton *eb = fb->edit_baton;
610302001Smm
611302001Smm  /* Check for write authorization. */
612302001Smm  SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
613302001Smm                      svn_authz_write, pool));
614302001Smm
615353377Smm  return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
616353377Smm                                       name, value, pool);
617353377Smm}
618353377Smm
619353377Smm
620353377Smmstatic svn_error_t *
621353377Smmclose_file(void *file_baton,
622353377Smm           const char *text_digest,
623353377Smm           apr_pool_t *pool)
624358090Smm{
625358090Smm  struct file_baton *fb = file_baton;
626358090Smm
627358090Smm  if (text_digest)
628358090Smm    {
629358090Smm      svn_checksum_t *checksum;
630358090Smm      svn_checksum_t *text_checksum;
631358090Smm
632358090Smm      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
633358090Smm                                   fb->edit_baton->txn_root, fb->path,
634358090Smm                                   TRUE, pool));
635358090Smm      SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
636358090Smm                                     text_digest, pool));
637358090Smm
638302001Smm      if (!svn_checksum_match(text_checksum, checksum))
639302001Smm        return svn_checksum_mismatch_err(text_checksum, checksum, pool,
640302001Smm                            _("Checksum mismatch for resulting fulltext\n(%s)"),
641302001Smm                            fb->path);
642302001Smm    }
643302001Smm
644302001Smm  return SVN_NO_ERROR;
645302001Smm}
646358090Smm
647358090Smm
648302001Smmstatic svn_error_t *
649302001Smmchange_dir_prop(void *dir_baton,
650302001Smm                const char *name,
651302001Smm                const svn_string_t *value,
652302001Smm                apr_pool_t *pool)
653302001Smm{
654302001Smm  struct dir_baton *db = dir_baton;
655302001Smm  struct edit_baton *eb = db->edit_baton;
656302001Smm
657302001Smm  /* Check for write authorization. */
658302001Smm  SVN_ERR(check_authz(eb, db->path, eb->txn_root,
659302001Smm                      svn_authz_write, pool));
660302001Smm
661302001Smm  if (SVN_IS_VALID_REVNUM(db->base_rev))
662302001Smm    {
663302001Smm      /* Subversion rule:  propchanges can only happen on a directory
664302001Smm         which is up-to-date. */
665302001Smm      svn_revnum_t created_rev;
666302001Smm      SVN_ERR(svn_fs_node_created_rev(&created_rev,
667302001Smm                                      eb->txn_root, db->path, pool));
668302001Smm
669302001Smm      if (db->base_rev < created_rev)
670231200Smm        return svn_error_trace(out_of_date(db->path, svn_node_dir));
671302001Smm    }
672231200Smm
673231200Smm  return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
674231200Smm                                       name, value, pool);
675231200Smm}
676231200Smm
677231200Smmconst char *
678231200Smmsvn_repos__post_commit_error_str(svn_error_t *err,
679231200Smm                                 apr_pool_t *pool)
680231200Smm{
681231200Smm  svn_error_t *hook_err1, *hook_err2;
682231200Smm  const char *msg;
683231200Smm
684231200Smm  if (! err)
685231200Smm    return _("(no error)");
686231200Smm
687231200Smm  err = svn_error_purge_tracing(err);
688302001Smm
689231200Smm  /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
690231200Smm     error from the post-commit script, if any, and hook_err2 should
691231200Smm     be the original error, but be defensive and handle a case where
692231200Smm     SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
693231200Smm  hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
694231200Smm  if (hook_err1 && hook_err1->child)
695231200Smm    hook_err2 = hook_err1->child;
696231200Smm  else
697353377Smm    hook_err2 = hook_err1;
698353377Smm
699231200Smm  /* This implementation counts on svn_repos_fs_commit_txn() and
700231200Smm     libsvn_repos/commit.c:complete_cb() returning
701231200Smm     svn_fs_commit_txn() as the parent error with a child
702231200Smm     SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error.  If the parent error
703231200Smm     is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
704231200Smm     in svn_fs_commit_txn().
705231200Smm
706231200Smm     The post-commit hook error message is already self describing, so
707231200Smm     it can be dropped into an error message without any additional
708231200Smm     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      /* Make sure a future abort doesn't perform
782         any work. This may occur if the commit
783         callback returns an error! */
784
785      eb->txn = NULL;
786      eb->txn_root = NULL;
787    }
788  else
789    {
790      /* ### todo: we should check whether it really was a conflict,
791         and return the conflict info if so? */
792
793      /* If the commit failed, it's *probably* due to a conflict --
794         that is, the txn being out-of-date.  The filesystem gives us
795         the ability to continue diddling the transaction and try
796         again; but let's face it: that's not how the cvs or svn works
797         from a user interface standpoint.  Thus we don't make use of
798         this fs feature (for now, at least.)
799
800         So, in a nutshell: svn commits are an all-or-nothing deal.
801         Each commit creates a new fs txn which either succeeds or is
802         aborted completely.  No second chances;  the user simply
803         needs to update and commit again  :) */
804
805      eb->txn_aborted = TRUE;
806
807      return svn_error_trace(
808                svn_error_compose_create(err,
809                                         svn_fs_abort_txn(eb->txn, pool)));
810    }
811
812  /* At this point, the post-commit error has been converted to a string.
813     That information will be passed to a callback, if provided. If the
814     callback invocation fails in some way, that failure is returned here.
815     IOW, the post-commit error information is low priority compared to
816     other gunk here.  */
817
818  /* Pass new revision information to the caller's callback. */
819  return svn_error_trace(invoke_commit_cb(eb->commit_callback,
820                                          eb->commit_callback_baton,
821                                          eb->repos->fs,
822                                          new_revision,
823                                          post_commit_err,
824                                          pool));
825}
826
827
828static svn_error_t *
829abort_edit(void *edit_baton,
830           apr_pool_t *pool)
831{
832  struct edit_baton *eb = edit_baton;
833  if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
834    return SVN_NO_ERROR;
835
836  eb->txn_aborted = TRUE;
837
838  /* Since abort_edit is supposed to release resources, do it now. */
839  if (eb->txn_root)
840    svn_fs_close_root(eb->txn_root);
841
842  return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
843}
844
845
846static svn_error_t *
847fetch_props_func(apr_hash_t **props,
848                 void *baton,
849                 const char *path,
850                 svn_revnum_t base_revision,
851                 apr_pool_t *result_pool,
852                 apr_pool_t *scratch_pool)
853{
854  struct edit_baton *eb = baton;
855  svn_fs_root_t *fs_root;
856  svn_error_t *err;
857
858  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
859                               svn_fs_txn_base_revision(eb->txn),
860                               scratch_pool));
861  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
862  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
863    {
864      svn_error_clear(err);
865      *props = apr_hash_make(result_pool);
866      return SVN_NO_ERROR;
867    }
868  else if (err)
869    return svn_error_trace(err);
870
871  return SVN_NO_ERROR;
872}
873
874static svn_error_t *
875fetch_kind_func(svn_node_kind_t *kind,
876                void *baton,
877                const char *path,
878                svn_revnum_t base_revision,
879                apr_pool_t *scratch_pool)
880{
881  struct edit_baton *eb = baton;
882  svn_fs_root_t *fs_root;
883
884  if (!SVN_IS_VALID_REVNUM(base_revision))
885    base_revision = svn_fs_txn_base_revision(eb->txn);
886
887  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
888
889  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
890
891  return SVN_NO_ERROR;
892}
893
894static svn_error_t *
895fetch_base_func(const char **filename,
896                void *baton,
897                const char *path,
898                svn_revnum_t base_revision,
899                apr_pool_t *result_pool,
900                apr_pool_t *scratch_pool)
901{
902  struct edit_baton *eb = baton;
903  svn_stream_t *contents;
904  svn_stream_t *file_stream;
905  const char *tmp_filename;
906  svn_fs_root_t *fs_root;
907  svn_error_t *err;
908
909  if (!SVN_IS_VALID_REVNUM(base_revision))
910    base_revision = svn_fs_txn_base_revision(eb->txn);
911
912  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
913
914  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
915  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
916    {
917      svn_error_clear(err);
918      *filename = NULL;
919      return SVN_NO_ERROR;
920    }
921  else if (err)
922    return svn_error_trace(err);
923  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
924                                 svn_io_file_del_on_pool_cleanup,
925                                 scratch_pool, scratch_pool));
926  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
927
928  *filename = apr_pstrdup(result_pool, tmp_filename);
929
930  return SVN_NO_ERROR;
931}
932
933
934
935/*** Public interfaces. ***/
936
937svn_error_t *
938svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
939                             void **edit_baton,
940                             svn_repos_t *repos,
941                             svn_fs_txn_t *txn,
942                             const char *repos_url,
943                             const char *base_path,
944                             apr_hash_t *revprop_table,
945                             svn_commit_callback2_t commit_callback,
946                             void *commit_baton,
947                             svn_repos_authz_callback_t authz_callback,
948                             void *authz_baton,
949                             apr_pool_t *pool)
950{
951  svn_delta_editor_t *e;
952  apr_pool_t *subpool = svn_pool_create(pool);
953  struct edit_baton *eb;
954  svn_delta_shim_callbacks_t *shim_callbacks =
955                                    svn_delta_shim_callbacks_default(pool);
956
957  /* Do a global authz access lookup.  Users with no write access
958     whatsoever to the repository don't get a commit editor. */
959  if (authz_callback)
960    {
961      svn_boolean_t allowed;
962
963      SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
964                             authz_baton, pool));
965      if (!allowed)
966        return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
967                                "Not authorized to open a commit editor.");
968    }
969
970  /* Allocate the structures. */
971  e = svn_delta_default_editor(pool);
972  eb = apr_pcalloc(subpool, sizeof(*eb));
973
974  /* Set up the editor. */
975  e->open_root         = open_root;
976  e->delete_entry      = delete_entry;
977  e->add_directory     = add_directory;
978  e->open_directory    = open_directory;
979  e->change_dir_prop   = change_dir_prop;
980  e->add_file          = add_file;
981  e->open_file         = open_file;
982  e->close_file        = close_file;
983  e->apply_textdelta   = apply_textdelta;
984  e->change_file_prop  = change_file_prop;
985  e->close_edit        = close_edit;
986  e->abort_edit        = abort_edit;
987
988  /* Set up the edit baton. */
989  eb->pool = subpool;
990  eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
991  eb->commit_callback = commit_callback;
992  eb->commit_callback_baton = commit_baton;
993  eb->authz_callback = authz_callback;
994  eb->authz_baton = authz_baton;
995  eb->base_path = svn_fspath__canonicalize(base_path, subpool);
996  eb->repos = repos;
997  eb->repos_url = repos_url;
998  eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
999                                       subpool);
1000  eb->fs = svn_repos_fs(repos);
1001  eb->txn = txn;
1002  eb->txn_owner = txn == NULL;
1003
1004  *edit_baton = eb;
1005  *editor = e;
1006
1007  shim_callbacks->fetch_props_func = fetch_props_func;
1008  shim_callbacks->fetch_kind_func = fetch_kind_func;
1009  shim_callbacks->fetch_base_func = fetch_base_func;
1010  shim_callbacks->fetch_baton = eb;
1011
1012  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1013                                   eb->repos_url, eb->base_path,
1014                                   shim_callbacks, pool, pool));
1015
1016  return SVN_NO_ERROR;
1017}
1018
1019
1020#if 0
1021static svn_error_t *
1022ev2_check_authz(const struct ev2_baton *eb,
1023                const char *relpath,
1024                svn_repos_authz_access_t required,
1025                apr_pool_t *scratch_pool)
1026{
1027  const char *fspath;
1028  svn_boolean_t allowed;
1029
1030  if (eb->authz == NULL)
1031    return SVN_NO_ERROR;
1032
1033  if (relpath)
1034    fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL);
1035  else
1036    fspath = NULL;
1037
1038  SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1039                                       eb->authz_user, required,
1040                                       &allowed, scratch_pool));
1041  if (!allowed)
1042    return svn_error_create(required & svn_authz_write
1043                            ? SVN_ERR_AUTHZ_UNWRITABLE
1044                            : SVN_ERR_AUTHZ_UNREADABLE,
1045                            NULL, "Access denied");
1046
1047  return SVN_NO_ERROR;
1048}
1049#endif
1050
1051
1052/* This implements svn_editor_cb_add_directory_t */
1053static svn_error_t *
1054add_directory_cb(void *baton,
1055                 const char *relpath,
1056                 const apr_array_header_t *children,
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_directory(eb->inner, relpath, children, props,
1064                                   replaces_rev));
1065  return SVN_NO_ERROR;
1066}
1067
1068
1069/* This implements svn_editor_cb_add_file_t */
1070static svn_error_t *
1071add_file_cb(void *baton,
1072            const char *relpath,
1073            const svn_checksum_t *checksum,
1074            svn_stream_t *contents,
1075            apr_hash_t *props,
1076            svn_revnum_t replaces_rev,
1077            apr_pool_t *scratch_pool)
1078{
1079  struct ev2_baton *eb = baton;
1080
1081  SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1082                              replaces_rev));
1083  return SVN_NO_ERROR;
1084}
1085
1086
1087/* This implements svn_editor_cb_add_symlink_t */
1088static svn_error_t *
1089add_symlink_cb(void *baton,
1090               const char *relpath,
1091               const char *target,
1092               apr_hash_t *props,
1093               svn_revnum_t replaces_rev,
1094               apr_pool_t *scratch_pool)
1095{
1096  struct ev2_baton *eb = baton;
1097
1098  SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1099                                 replaces_rev));
1100  return SVN_NO_ERROR;
1101}
1102
1103
1104/* This implements svn_editor_cb_add_absent_t */
1105static svn_error_t *
1106add_absent_cb(void *baton,
1107              const char *relpath,
1108              svn_node_kind_t kind,
1109              svn_revnum_t replaces_rev,
1110              apr_pool_t *scratch_pool)
1111{
1112  struct ev2_baton *eb = baton;
1113
1114  SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1115  return SVN_NO_ERROR;
1116}
1117
1118
1119/* This implements svn_editor_cb_alter_directory_t */
1120static svn_error_t *
1121alter_directory_cb(void *baton,
1122                   const char *relpath,
1123                   svn_revnum_t revision,
1124                   const apr_array_header_t *children,
1125                   apr_hash_t *props,
1126                   apr_pool_t *scratch_pool)
1127{
1128  struct ev2_baton *eb = baton;
1129
1130  SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1131                                     children, props));
1132  return SVN_NO_ERROR;
1133}
1134
1135
1136/* This implements svn_editor_cb_alter_file_t */
1137static svn_error_t *
1138alter_file_cb(void *baton,
1139              const char *relpath,
1140              svn_revnum_t revision,
1141              apr_hash_t *props,
1142              const svn_checksum_t *checksum,
1143              svn_stream_t *contents,
1144              apr_pool_t *scratch_pool)
1145{
1146  struct ev2_baton *eb = baton;
1147
1148  SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props,
1149                                checksum, contents));
1150  return SVN_NO_ERROR;
1151}
1152
1153
1154/* This implements svn_editor_cb_alter_symlink_t */
1155static svn_error_t *
1156alter_symlink_cb(void *baton,
1157                 const char *relpath,
1158                 svn_revnum_t revision,
1159                 apr_hash_t *props,
1160                 const char *target,
1161                 apr_pool_t *scratch_pool)
1162{
1163  struct ev2_baton *eb = baton;
1164
1165  SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props,
1166                                   target));
1167  return SVN_NO_ERROR;
1168}
1169
1170
1171/* This implements svn_editor_cb_delete_t */
1172static svn_error_t *
1173delete_cb(void *baton,
1174          const char *relpath,
1175          svn_revnum_t revision,
1176          apr_pool_t *scratch_pool)
1177{
1178  struct ev2_baton *eb = baton;
1179
1180  SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1181  return SVN_NO_ERROR;
1182}
1183
1184
1185/* This implements svn_editor_cb_copy_t */
1186static svn_error_t *
1187copy_cb(void *baton,
1188        const char *src_relpath,
1189        svn_revnum_t src_revision,
1190        const char *dst_relpath,
1191        svn_revnum_t replaces_rev,
1192        apr_pool_t *scratch_pool)
1193{
1194  struct ev2_baton *eb = baton;
1195
1196  SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1197                          replaces_rev));
1198  return SVN_NO_ERROR;
1199}
1200
1201
1202/* This implements svn_editor_cb_move_t */
1203static svn_error_t *
1204move_cb(void *baton,
1205        const char *src_relpath,
1206        svn_revnum_t src_revision,
1207        const char *dst_relpath,
1208        svn_revnum_t replaces_rev,
1209        apr_pool_t *scratch_pool)
1210{
1211  struct ev2_baton *eb = baton;
1212
1213  SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1214                          replaces_rev));
1215  return SVN_NO_ERROR;
1216}
1217
1218
1219/* This implements svn_editor_cb_rotate_t */
1220static svn_error_t *
1221rotate_cb(void *baton,
1222          const apr_array_header_t *relpaths,
1223          const apr_array_header_t *revisions,
1224          apr_pool_t *scratch_pool)
1225{
1226  struct ev2_baton *eb = baton;
1227
1228  SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions));
1229  return SVN_NO_ERROR;
1230}
1231
1232
1233/* This implements svn_editor_cb_complete_t */
1234static svn_error_t *
1235complete_cb(void *baton,
1236            apr_pool_t *scratch_pool)
1237{
1238  struct ev2_baton *eb = baton;
1239  svn_revnum_t revision;
1240  svn_error_t *post_commit_err;
1241  const char *conflict_path;
1242  svn_error_t *err;
1243  const char *post_commit_errstr;
1244  apr_hash_t *hooks_env;
1245
1246  /* Parse the hooks-env file (if any). */
1247  SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1248                                     scratch_pool, scratch_pool));
1249
1250  /* The transaction has been fully edited. Let the pre-commit hook
1251     have a look at the thing.  */
1252  SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1253                                      eb->txn_name, scratch_pool));
1254
1255  /* Hook is done. Let's do the actual commit.  */
1256  SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1257                                eb->inner, scratch_pool, scratch_pool));
1258
1259  /* Did a conflict occur during the commit process?  */
1260  if (conflict_path != NULL)
1261    return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1262                             _("Conflict at '%s'"), conflict_path);
1263
1264  /* Since did not receive an error during the commit process, and no
1265     conflict was specified... we committed a revision. Run the hooks.
1266     Other errors may have occurred within the FS (specified by the
1267     POST_COMMIT_ERR localvar), but we need to run the hooks.  */
1268  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1269  err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1270                                     eb->txn_name, scratch_pool);
1271  if (err)
1272    err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1273                           _("Commit succeeded, but post-commit hook failed"));
1274
1275  /* Combine the FS errors with the hook errors, and stringify.  */
1276  err = svn_error_compose_create(post_commit_err, err);
1277  if (err)
1278    {
1279      post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1280      svn_error_clear(err);
1281    }
1282  else
1283    {
1284      post_commit_errstr = NULL;
1285    }
1286
1287  return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1288                                          eb->repos->fs, revision,
1289                                          post_commit_errstr,
1290                                          scratch_pool));
1291}
1292
1293
1294/* This implements svn_editor_cb_abort_t */
1295static svn_error_t *
1296abort_cb(void *baton,
1297         apr_pool_t *scratch_pool)
1298{
1299  struct ev2_baton *eb = baton;
1300
1301  SVN_ERR(svn_editor_abort(eb->inner));
1302  return SVN_NO_ERROR;
1303}
1304
1305
1306static svn_error_t *
1307apply_revprops(svn_fs_t *fs,
1308               const char *txn_name,
1309               apr_hash_t *revprops,
1310               apr_pool_t *scratch_pool)
1311{
1312  svn_fs_txn_t *txn;
1313  const apr_array_header_t *revprops_array;
1314
1315  /* The FS editor has a TXN inside it, but we can't access it. Open another
1316     based on the TXN_NAME.  */
1317  SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1318
1319  /* Validate and apply the revision properties.  */
1320  revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1321  SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1322
1323  /* ### do we need to force the txn to close, or is it enough to wait
1324     ### for the pool to be cleared?  */
1325  return SVN_NO_ERROR;
1326}
1327
1328
1329svn_error_t *
1330svn_repos__get_commit_ev2(svn_editor_t **editor,
1331                          svn_repos_t *repos,
1332                          svn_authz_t *authz,
1333                          const char *authz_repos_name,
1334                          const char *authz_user,
1335                          apr_hash_t *revprops,
1336                          svn_commit_callback2_t commit_cb,
1337                          void *commit_baton,
1338                          svn_cancel_func_t cancel_func,
1339                          void *cancel_baton,
1340                          apr_pool_t *result_pool,
1341                          apr_pool_t *scratch_pool)
1342{
1343  static const svn_editor_cb_many_t editor_cbs = {
1344    add_directory_cb,
1345    add_file_cb,
1346    add_symlink_cb,
1347    add_absent_cb,
1348    alter_directory_cb,
1349    alter_file_cb,
1350    alter_symlink_cb,
1351    delete_cb,
1352    copy_cb,
1353    move_cb,
1354    rotate_cb,
1355    complete_cb,
1356    abort_cb
1357  };
1358  struct ev2_baton *eb;
1359  const svn_string_t *author;
1360  apr_hash_t *hooks_env;
1361
1362  /* Parse the hooks-env file (if any). */
1363  SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1364                                     scratch_pool, scratch_pool));
1365
1366  /* Can the user modify the repository at all?  */
1367  /* ### check against AUTHZ.  */
1368
1369  author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1370
1371  eb = apr_palloc(result_pool, sizeof(*eb));
1372  eb->repos = repos;
1373  eb->authz = authz;
1374  eb->authz_repos_name = authz_repos_name;
1375  eb->authz_user = authz_user;
1376  eb->commit_cb = commit_cb;
1377  eb->commit_baton = commit_baton;
1378
1379  SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1380                                repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1381                                cancel_func, cancel_baton,
1382                                result_pool, scratch_pool));
1383
1384  /* The TXN has been created. Go ahead and apply all revision properties.  */
1385  SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1386
1387  /* Okay... some access is allowed. Let's run the start-commit hook.  */
1388  SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1389                                        author ? author->data : NULL,
1390                                        repos->client_capabilities,
1391                                        eb->txn_name, scratch_pool));
1392
1393  /* Wrap the FS editor within our editor.  */
1394  SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1395                            result_pool, scratch_pool));
1396  SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1397
1398  return SVN_NO_ERROR;
1399}
1400