1251881Speter/* commit.c --- editor for committing changes to a filesystem.
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter
24251881Speter#include <string.h>
25251881Speter
26251881Speter#include <apr_pools.h>
27251881Speter#include <apr_file_io.h>
28251881Speter
29251881Speter#include "svn_hash.h"
30251881Speter#include "svn_compat.h"
31251881Speter#include "svn_pools.h"
32251881Speter#include "svn_error.h"
33251881Speter#include "svn_dirent_uri.h"
34251881Speter#include "svn_path.h"
35251881Speter#include "svn_delta.h"
36251881Speter#include "svn_fs.h"
37251881Speter#include "svn_repos.h"
38251881Speter#include "svn_checksum.h"
39251881Speter#include "svn_ctype.h"
40251881Speter#include "svn_props.h"
41251881Speter#include "svn_mergeinfo.h"
42251881Speter#include "svn_private_config.h"
43251881Speter
44251881Speter#include "repos.h"
45251881Speter
46251881Speter#include "private/svn_fspath.h"
47251881Speter#include "private/svn_fs_private.h"
48251881Speter#include "private/svn_repos_private.h"
49251881Speter#include "private/svn_editor.h"
50251881Speter
51251881Speter
52251881Speter
53251881Speter/*** Editor batons. ***/
54251881Speter
55251881Speterstruct edit_baton
56251881Speter{
57251881Speter  apr_pool_t *pool;
58251881Speter
59251881Speter  /** Supplied when the editor is created: **/
60251881Speter
61251881Speter  /* Revision properties to set for this commit. */
62251881Speter  apr_hash_t *revprop_table;
63251881Speter
64251881Speter  /* Callback to run when the commit is done. */
65251881Speter  svn_commit_callback2_t commit_callback;
66251881Speter  void *commit_callback_baton;
67251881Speter
68251881Speter  /* Callback to check authorizations on paths. */
69251881Speter  svn_repos_authz_callback_t authz_callback;
70251881Speter  void *authz_baton;
71251881Speter
72251881Speter  /* The already-open svn repository to commit to. */
73251881Speter  svn_repos_t *repos;
74251881Speter
75251881Speter  /* URL to the root of the open repository. */
76251881Speter  const char *repos_url;
77251881Speter
78251881Speter  /* The name of the repository (here for convenience). */
79251881Speter  const char *repos_name;
80251881Speter
81251881Speter  /* The filesystem associated with the REPOS above (here for
82251881Speter     convenience). */
83251881Speter  svn_fs_t *fs;
84251881Speter
85251881Speter  /* Location in fs where the edit will begin. */
86251881Speter  const char *base_path;
87251881Speter
88251881Speter  /* Does this set of interfaces 'own' the commit transaction? */
89251881Speter  svn_boolean_t txn_owner;
90251881Speter
91251881Speter  /* svn transaction associated with this edit (created in
92251881Speter     open_root, or supplied by the public API caller). */
93251881Speter  svn_fs_txn_t *txn;
94251881Speter
95251881Speter  /** Filled in during open_root: **/
96251881Speter
97251881Speter  /* The name of the transaction. */
98251881Speter  const char *txn_name;
99251881Speter
100251881Speter  /* The object representing the root directory of the svn txn. */
101251881Speter  svn_fs_root_t *txn_root;
102251881Speter
103251881Speter  /* Avoid aborting an fs transaction more than once */
104251881Speter  svn_boolean_t txn_aborted;
105251881Speter
106251881Speter  /** Filled in when the edit is closed: **/
107251881Speter
108251881Speter  /* The new revision created by this commit. */
109251881Speter  svn_revnum_t *new_rev;
110251881Speter
111251881Speter  /* The date (according to the repository) of this commit. */
112251881Speter  const char **committed_date;
113251881Speter
114251881Speter  /* The author (also according to the repository) of this commit. */
115251881Speter  const char **committed_author;
116251881Speter};
117251881Speter
118251881Speter
119251881Speterstruct dir_baton
120251881Speter{
121251881Speter  struct edit_baton *edit_baton;
122251881Speter  struct dir_baton *parent;
123251881Speter  const char *path; /* the -absolute- path to this dir in the fs */
124251881Speter  svn_revnum_t base_rev;        /* the revision I'm based on  */
125251881Speter  svn_boolean_t was_copied; /* was this directory added with history? */
126251881Speter  apr_pool_t *pool; /* my personal pool, in which I am allocated. */
127251881Speter};
128251881Speter
129251881Speter
130251881Speterstruct file_baton
131251881Speter{
132251881Speter  struct edit_baton *edit_baton;
133251881Speter  const char *path; /* the -absolute- path to this file in the fs */
134251881Speter};
135251881Speter
136251881Speter
137251881Speterstruct ev2_baton
138251881Speter{
139251881Speter  /* The repository we are editing.  */
140251881Speter  svn_repos_t *repos;
141251881Speter
142251881Speter  /* The authz baton for checks; NULL to skip authz.  */
143251881Speter  svn_authz_t *authz;
144251881Speter
145251881Speter  /* The repository name and user for performing authz checks.  */
146251881Speter  const char *authz_repos_name;
147251881Speter  const char *authz_user;
148251881Speter
149251881Speter  /* Callback to provide info about the committed revision.  */
150251881Speter  svn_commit_callback2_t commit_cb;
151251881Speter  void *commit_baton;
152251881Speter
153251881Speter  /* The FS txn editor  */
154251881Speter  svn_editor_t *inner;
155251881Speter
156251881Speter  /* The name of the open transaction (so we know what to commit)  */
157251881Speter  const char *txn_name;
158251881Speter};
159251881Speter
160251881Speter
161251881Speter/* Create and return a generic out-of-dateness error. */
162251881Speterstatic svn_error_t *
163251881Speterout_of_date(const char *path, svn_node_kind_t kind)
164251881Speter{
165251881Speter  return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
166251881Speter                           (kind == svn_node_dir
167251881Speter                            ? _("Directory '%s' is out of date")
168251881Speter                            : kind == svn_node_file
169251881Speter                            ? _("File '%s' is out of date")
170251881Speter                            : _("'%s' is out of date")),
171251881Speter                           path);
172251881Speter}
173251881Speter
174251881Speter
175251881Speterstatic svn_error_t *
176251881Speterinvoke_commit_cb(svn_commit_callback2_t commit_cb,
177251881Speter                 void *commit_baton,
178251881Speter                 svn_fs_t *fs,
179251881Speter                 svn_revnum_t revision,
180251881Speter                 const char *post_commit_errstr,
181251881Speter                 apr_pool_t *scratch_pool)
182251881Speter{
183251881Speter  /* FS interface returns non-const values.  */
184251881Speter  /* const */ svn_string_t *date;
185251881Speter  /* const */ svn_string_t *author;
186251881Speter  svn_commit_info_t *commit_info;
187251881Speter
188251881Speter  if (commit_cb == NULL)
189251881Speter    return SVN_NO_ERROR;
190251881Speter
191251881Speter  SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE,
192251881Speter                               scratch_pool));
193251881Speter  SVN_ERR(svn_fs_revision_prop(&author, fs, revision,
194251881Speter                               SVN_PROP_REVISION_AUTHOR,
195251881Speter                               scratch_pool));
196251881Speter
197251881Speter  commit_info = svn_create_commit_info(scratch_pool);
198251881Speter
199251881Speter  /* fill up the svn_commit_info structure */
200251881Speter  commit_info->revision = revision;
201251881Speter  commit_info->date = date ? date->data : NULL;
202251881Speter  commit_info->author = author ? author->data : NULL;
203251881Speter  commit_info->post_commit_err = post_commit_errstr;
204251881Speter
205251881Speter  return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
206251881Speter}
207251881Speter
208251881Speter
209251881Speter
210251881Speter/* If EDITOR_BATON contains a valid authz callback, verify that the
211251881Speter   REQUIRED access to PATH in ROOT is authorized.  Return an error
212251881Speter   appropriate for throwing out of the commit editor with SVN_ERR.  If
213251881Speter   no authz callback is present in EDITOR_BATON, then authorize all
214251881Speter   paths.  Use POOL for temporary allocation only. */
215251881Speterstatic svn_error_t *
216251881Spetercheck_authz(struct edit_baton *editor_baton, const char *path,
217251881Speter            svn_fs_root_t *root, svn_repos_authz_access_t required,
218251881Speter            apr_pool_t *pool)
219251881Speter{
220251881Speter  if (editor_baton->authz_callback)
221251881Speter    {
222251881Speter      svn_boolean_t allowed;
223251881Speter
224251881Speter      SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
225251881Speter                                           editor_baton->authz_baton, pool));
226251881Speter      if (!allowed)
227251881Speter        return svn_error_create(required & svn_authz_write ?
228251881Speter                                SVN_ERR_AUTHZ_UNWRITABLE :
229251881Speter                                SVN_ERR_AUTHZ_UNREADABLE,
230251881Speter                                NULL, "Access denied");
231251881Speter    }
232251881Speter
233251881Speter  return SVN_NO_ERROR;
234251881Speter}
235251881Speter
236251881Speter
237251881Speter/* Return a directory baton allocated in POOL which represents
238251881Speter   FULL_PATH, which is the immediate directory child of the directory
239251881Speter   represented by PARENT_BATON.  EDIT_BATON is the commit editor
240251881Speter   baton.  WAS_COPIED reveals whether or not this directory is the
241251881Speter   result of a copy operation.  BASE_REVISION is the base revision of
242251881Speter   the directory. */
243251881Speterstatic struct dir_baton *
244251881Spetermake_dir_baton(struct edit_baton *edit_baton,
245251881Speter               struct dir_baton *parent_baton,
246251881Speter               const char *full_path,
247251881Speter               svn_boolean_t was_copied,
248251881Speter               svn_revnum_t base_revision,
249251881Speter               apr_pool_t *pool)
250251881Speter{
251251881Speter  struct dir_baton *db;
252251881Speter  db = apr_pcalloc(pool, sizeof(*db));
253251881Speter  db->edit_baton = edit_baton;
254251881Speter  db->parent = parent_baton;
255251881Speter  db->pool = pool;
256251881Speter  db->path = full_path;
257251881Speter  db->was_copied = was_copied;
258251881Speter  db->base_rev = base_revision;
259251881Speter  return db;
260251881Speter}
261251881Speter
262251881Speter/* This function is the shared guts of add_file() and add_directory(),
263251881Speter   which see for the meanings of the parameters.  The only extra
264251881Speter   parameter here is IS_DIR, which is TRUE when adding a directory,
265251881Speter   and FALSE when adding a file.  */
266251881Speterstatic svn_error_t *
267251881Speteradd_file_or_directory(const char *path,
268251881Speter                      void *parent_baton,
269251881Speter                      const char *copy_path,
270251881Speter                      svn_revnum_t copy_revision,
271251881Speter                      svn_boolean_t is_dir,
272251881Speter                      apr_pool_t *pool,
273251881Speter                      void **return_baton)
274251881Speter{
275251881Speter  struct dir_baton *pb = parent_baton;
276251881Speter  struct edit_baton *eb = pb->edit_baton;
277251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
278251881Speter  svn_boolean_t was_copied = FALSE;
279251881Speter  const char *full_path;
280251881Speter
281251881Speter  /* Reject paths which contain control characters (related to issue #4340). */
282251881Speter  SVN_ERR(svn_path_check_valid(path, pool));
283251881Speter
284251881Speter  full_path = svn_fspath__join(eb->base_path,
285251881Speter                               svn_relpath_canonicalize(path, pool), pool);
286251881Speter
287251881Speter  /* Sanity check. */
288251881Speter  if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
289251881Speter    return svn_error_createf
290251881Speter      (SVN_ERR_FS_GENERAL, NULL,
291251881Speter       _("Got source path but no source revision for '%s'"), full_path);
292251881Speter
293251881Speter  if (copy_path)
294251881Speter    {
295251881Speter      const char *fs_path;
296251881Speter      svn_fs_root_t *copy_root;
297251881Speter      svn_node_kind_t kind;
298251881Speter      size_t repos_url_len;
299251881Speter      svn_repos_authz_access_t required;
300251881Speter
301251881Speter      /* Copy requires recursive write access to the destination path
302251881Speter         and write access to the parent path. */
303251881Speter      required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
304251881Speter      SVN_ERR(check_authz(eb, full_path, eb->txn_root,
305251881Speter                          required, subpool));
306251881Speter      SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
307251881Speter                          svn_authz_write, subpool));
308251881Speter
309251881Speter      /* Check PATH in our transaction.  Make sure it does not exist
310251881Speter         unless its parent directory was copied (in which case, the
311251881Speter         thing might have been copied in as well), else return an
312251881Speter         out-of-dateness error. */
313251881Speter      SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
314251881Speter      if ((kind != svn_node_none) && (! pb->was_copied))
315251881Speter        return svn_error_trace(out_of_date(full_path, kind));
316251881Speter
317251881Speter      /* For now, require that the url come from the same repository
318251881Speter         that this commit is operating on. */
319251881Speter      copy_path = svn_path_uri_decode(copy_path, subpool);
320251881Speter      repos_url_len = strlen(eb->repos_url);
321251881Speter      if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0)
322251881Speter        return svn_error_createf
323251881Speter          (SVN_ERR_FS_GENERAL, NULL,
324251881Speter           _("Source url '%s' is from different repository"), copy_path);
325251881Speter
326251881Speter      fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
327251881Speter
328251881Speter      /* Now use the "fs_path" as an absolute path within the
329251881Speter         repository to make the copy from. */
330251881Speter      SVN_ERR(svn_fs_revision_root(&copy_root, eb->fs,
331251881Speter                                   copy_revision, subpool));
332251881Speter
333251881Speter      /* Copy also requires (recursive) read access to the source */
334251881Speter      required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
335251881Speter      SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
336251881Speter
337251881Speter      SVN_ERR(svn_fs_copy(copy_root, fs_path,
338251881Speter                          eb->txn_root, full_path, subpool));
339251881Speter      was_copied = TRUE;
340251881Speter    }
341251881Speter  else
342251881Speter    {
343251881Speter      /* No ancestry given, just make a new directory or empty file.
344251881Speter         Note that we don't perform an existence check here like the
345251881Speter         copy-from case does -- that's because svn_fs_make_*()
346251881Speter         already errors out if the file already exists.  Verify write
347251881Speter         access to the full path and to the parent. */
348251881Speter      SVN_ERR(check_authz(eb, full_path, eb->txn_root,
349251881Speter                          svn_authz_write, subpool));
350251881Speter      SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
351251881Speter                          svn_authz_write, subpool));
352251881Speter      if (is_dir)
353251881Speter        SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
354251881Speter      else
355251881Speter        SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
356251881Speter    }
357251881Speter
358251881Speter  /* Cleanup our temporary subpool. */
359251881Speter  svn_pool_destroy(subpool);
360251881Speter
361251881Speter  /* Build a new child baton. */
362251881Speter  if (is_dir)
363251881Speter    {
364251881Speter      *return_baton = make_dir_baton(eb, pb, full_path, was_copied,
365251881Speter                                     SVN_INVALID_REVNUM, pool);
366251881Speter    }
367251881Speter  else
368251881Speter    {
369251881Speter      struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
370251881Speter      new_fb->edit_baton = eb;
371251881Speter      new_fb->path = full_path;
372251881Speter      *return_baton = new_fb;
373251881Speter    }
374251881Speter
375251881Speter  return SVN_NO_ERROR;
376251881Speter}
377251881Speter
378251881Speter
379251881Speter
380251881Speter/*** Editor functions ***/
381251881Speter
382251881Speterstatic svn_error_t *
383251881Speteropen_root(void *edit_baton,
384251881Speter          svn_revnum_t base_revision,
385251881Speter          apr_pool_t *pool,
386251881Speter          void **root_baton)
387251881Speter{
388251881Speter  struct dir_baton *dirb;
389251881Speter  struct edit_baton *eb = edit_baton;
390251881Speter  svn_revnum_t youngest;
391251881Speter
392251881Speter  /* Ignore BASE_REVISION.  We always build our transaction against
393251881Speter     HEAD.  However, we will keep it in our dir baton for out of
394251881Speter     dateness checks.  */
395251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
396251881Speter
397251881Speter  /* Unless we've been instructed to use a specific transaction, we'll
398251881Speter     make our own. */
399251881Speter  if (eb->txn_owner)
400251881Speter    {
401251881Speter      SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
402251881Speter                                                 eb->repos,
403251881Speter                                                 youngest,
404251881Speter                                                 eb->revprop_table,
405251881Speter                                                 eb->pool));
406251881Speter    }
407251881Speter  else /* Even if we aren't the owner of the transaction, we might
408251881Speter          have been instructed to set some properties. */
409251881Speter    {
410251881Speter      apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
411251881Speter                                                         pool);
412251881Speter      SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
413251881Speter    }
414251881Speter  SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
415251881Speter  SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
416251881Speter
417251881Speter  /* Create a root dir baton.  The `base_path' field is an -absolute-
418251881Speter     path in the filesystem, upon which all further editor paths are
419251881Speter     based. */
420251881Speter  dirb = apr_pcalloc(pool, sizeof(*dirb));
421251881Speter  dirb->edit_baton = edit_baton;
422251881Speter  dirb->parent = NULL;
423251881Speter  dirb->pool = pool;
424251881Speter  dirb->was_copied = FALSE;
425251881Speter  dirb->path = apr_pstrdup(pool, eb->base_path);
426251881Speter  dirb->base_rev = base_revision;
427251881Speter
428251881Speter  *root_baton = dirb;
429251881Speter  return SVN_NO_ERROR;
430251881Speter}
431251881Speter
432251881Speter
433251881Speter
434251881Speterstatic svn_error_t *
435251881Speterdelete_entry(const char *path,
436251881Speter             svn_revnum_t revision,
437251881Speter             void *parent_baton,
438251881Speter             apr_pool_t *pool)
439251881Speter{
440251881Speter  struct dir_baton *parent = parent_baton;
441251881Speter  struct edit_baton *eb = parent->edit_baton;
442251881Speter  svn_node_kind_t kind;
443251881Speter  svn_revnum_t cr_rev;
444251881Speter  svn_repos_authz_access_t required = svn_authz_write;
445251881Speter  const char *full_path;
446251881Speter
447251881Speter  full_path = svn_fspath__join(eb->base_path,
448251881Speter                               svn_relpath_canonicalize(path, pool), pool);
449251881Speter
450251881Speter  /* Check PATH in our transaction.  */
451251881Speter  SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
452251881Speter
453251881Speter  /* Deletion requires a recursive write access, as well as write
454251881Speter     access to the parent directory. */
455251881Speter  if (kind == svn_node_dir)
456251881Speter    required |= svn_authz_recursive;
457251881Speter  SVN_ERR(check_authz(eb, full_path, eb->txn_root,
458251881Speter                      required, pool));
459251881Speter  SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
460251881Speter                      svn_authz_write, pool));
461251881Speter
462251881Speter  /* If PATH doesn't exist in the txn, the working copy is out of date. */
463251881Speter  if (kind == svn_node_none)
464251881Speter    return svn_error_trace(out_of_date(full_path, kind));
465251881Speter
466251881Speter  /* Now, make sure we're deleting the node we *think* we're
467251881Speter     deleting, else return an out-of-dateness error. */
468251881Speter  SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
469251881Speter  if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
470251881Speter    return svn_error_trace(out_of_date(full_path, kind));
471251881Speter
472251881Speter  /* This routine is a mindless wrapper.  We call svn_fs_delete()
473251881Speter     because that will delete files and recursively delete
474251881Speter     directories.  */
475251881Speter  return svn_fs_delete(eb->txn_root, full_path, pool);
476251881Speter}
477251881Speter
478251881Speter
479251881Speterstatic svn_error_t *
480251881Speteradd_directory(const char *path,
481251881Speter              void *parent_baton,
482251881Speter              const char *copy_path,
483251881Speter              svn_revnum_t copy_revision,
484251881Speter              apr_pool_t *pool,
485251881Speter              void **child_baton)
486251881Speter{
487251881Speter  return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
488251881Speter                               TRUE /* is_dir */, pool, child_baton);
489251881Speter}
490251881Speter
491251881Speter
492251881Speterstatic svn_error_t *
493251881Speteropen_directory(const char *path,
494251881Speter               void *parent_baton,
495251881Speter               svn_revnum_t base_revision,
496251881Speter               apr_pool_t *pool,
497251881Speter               void **child_baton)
498251881Speter{
499251881Speter  struct dir_baton *pb = parent_baton;
500251881Speter  struct edit_baton *eb = pb->edit_baton;
501251881Speter  svn_node_kind_t kind;
502251881Speter  const char *full_path;
503251881Speter
504251881Speter  full_path = svn_fspath__join(eb->base_path,
505251881Speter                               svn_relpath_canonicalize(path, pool), pool);
506251881Speter
507251881Speter  /* Check PATH in our transaction.  If it does not exist,
508251881Speter     return a 'Path not present' error. */
509251881Speter  SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
510251881Speter  if (kind == svn_node_none)
511251881Speter    return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
512251881Speter                             _("Path '%s' not present"),
513251881Speter                             path);
514251881Speter
515251881Speter  /* Build a new dir baton for this directory. */
516251881Speter  *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
517251881Speter                                base_revision, pool);
518251881Speter  return SVN_NO_ERROR;
519251881Speter}
520251881Speter
521251881Speter
522251881Speterstatic svn_error_t *
523251881Speterapply_textdelta(void *file_baton,
524251881Speter                const char *base_checksum,
525251881Speter                apr_pool_t *pool,
526251881Speter                svn_txdelta_window_handler_t *handler,
527251881Speter                void **handler_baton)
528251881Speter{
529251881Speter  struct file_baton *fb = file_baton;
530251881Speter
531251881Speter  /* Check for write authorization. */
532251881Speter  SVN_ERR(check_authz(fb->edit_baton, fb->path,
533251881Speter                      fb->edit_baton->txn_root,
534251881Speter                      svn_authz_write, pool));
535251881Speter
536251881Speter  return svn_fs_apply_textdelta(handler, handler_baton,
537251881Speter                                fb->edit_baton->txn_root,
538251881Speter                                fb->path,
539251881Speter                                base_checksum,
540251881Speter                                NULL,
541251881Speter                                pool);
542251881Speter}
543251881Speter
544251881Speter
545251881Speterstatic svn_error_t *
546251881Speteradd_file(const char *path,
547251881Speter         void *parent_baton,
548251881Speter         const char *copy_path,
549251881Speter         svn_revnum_t copy_revision,
550251881Speter         apr_pool_t *pool,
551251881Speter         void **file_baton)
552251881Speter{
553251881Speter  return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
554251881Speter                               FALSE /* is_dir */, pool, file_baton);
555251881Speter}
556251881Speter
557251881Speter
558251881Speterstatic svn_error_t *
559251881Speteropen_file(const char *path,
560251881Speter          void *parent_baton,
561251881Speter          svn_revnum_t base_revision,
562251881Speter          apr_pool_t *pool,
563251881Speter          void **file_baton)
564251881Speter{
565251881Speter  struct file_baton *new_fb;
566251881Speter  struct dir_baton *pb = parent_baton;
567251881Speter  struct edit_baton *eb = pb->edit_baton;
568251881Speter  svn_revnum_t cr_rev;
569251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
570251881Speter  const char *full_path;
571251881Speter
572251881Speter  full_path = svn_fspath__join(eb->base_path,
573251881Speter                               svn_relpath_canonicalize(path, pool), pool);
574251881Speter
575251881Speter  /* Check for read authorization. */
576251881Speter  SVN_ERR(check_authz(eb, full_path, eb->txn_root,
577251881Speter                      svn_authz_read, subpool));
578251881Speter
579251881Speter  /* Get this node's creation revision (doubles as an existence check). */
580251881Speter  SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
581251881Speter                                  subpool));
582251881Speter
583251881Speter  /* If the node our caller has is an older revision number than the
584251881Speter     one in our transaction, return an out-of-dateness error. */
585251881Speter  if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
586251881Speter    return svn_error_trace(out_of_date(full_path, svn_node_file));
587251881Speter
588251881Speter  /* Build a new file baton */
589251881Speter  new_fb = apr_pcalloc(pool, sizeof(*new_fb));
590251881Speter  new_fb->edit_baton = eb;
591251881Speter  new_fb->path = full_path;
592251881Speter
593251881Speter  *file_baton = new_fb;
594251881Speter
595251881Speter  /* Destory the work subpool. */
596251881Speter  svn_pool_destroy(subpool);
597251881Speter
598251881Speter  return SVN_NO_ERROR;
599251881Speter}
600251881Speter
601251881Speter
602251881Speterstatic svn_error_t *
603251881Speterchange_file_prop(void *file_baton,
604251881Speter                 const char *name,
605251881Speter                 const svn_string_t *value,
606251881Speter                 apr_pool_t *pool)
607251881Speter{
608251881Speter  struct file_baton *fb = file_baton;
609251881Speter  struct edit_baton *eb = fb->edit_baton;
610251881Speter
611251881Speter  /* Check for write authorization. */
612251881Speter  SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
613251881Speter                      svn_authz_write, pool));
614251881Speter
615251881Speter  return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
616251881Speter                                       name, value, pool);
617251881Speter}
618251881Speter
619251881Speter
620251881Speterstatic svn_error_t *
621251881Speterclose_file(void *file_baton,
622251881Speter           const char *text_digest,
623251881Speter           apr_pool_t *pool)
624251881Speter{
625251881Speter  struct file_baton *fb = file_baton;
626251881Speter
627251881Speter  if (text_digest)
628251881Speter    {
629251881Speter      svn_checksum_t *checksum;
630251881Speter      svn_checksum_t *text_checksum;
631251881Speter
632251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
633251881Speter                                   fb->edit_baton->txn_root, fb->path,
634251881Speter                                   TRUE, pool));
635251881Speter      SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
636251881Speter                                     text_digest, pool));
637251881Speter
638251881Speter      if (!svn_checksum_match(text_checksum, checksum))
639251881Speter        return svn_checksum_mismatch_err(text_checksum, checksum, pool,
640251881Speter                            _("Checksum mismatch for resulting fulltext\n(%s)"),
641251881Speter                            fb->path);
642251881Speter    }
643251881Speter
644251881Speter  return SVN_NO_ERROR;
645251881Speter}
646251881Speter
647251881Speter
648251881Speterstatic svn_error_t *
649251881Speterchange_dir_prop(void *dir_baton,
650251881Speter                const char *name,
651251881Speter                const svn_string_t *value,
652251881Speter                apr_pool_t *pool)
653251881Speter{
654251881Speter  struct dir_baton *db = dir_baton;
655251881Speter  struct edit_baton *eb = db->edit_baton;
656251881Speter
657251881Speter  /* Check for write authorization. */
658251881Speter  SVN_ERR(check_authz(eb, db->path, eb->txn_root,
659251881Speter                      svn_authz_write, pool));
660251881Speter
661251881Speter  if (SVN_IS_VALID_REVNUM(db->base_rev))
662251881Speter    {
663251881Speter      /* Subversion rule:  propchanges can only happen on a directory
664251881Speter         which is up-to-date. */
665251881Speter      svn_revnum_t created_rev;
666251881Speter      SVN_ERR(svn_fs_node_created_rev(&created_rev,
667251881Speter                                      eb->txn_root, db->path, pool));
668251881Speter
669251881Speter      if (db->base_rev < created_rev)
670251881Speter        return svn_error_trace(out_of_date(db->path, svn_node_dir));
671251881Speter    }
672251881Speter
673251881Speter  return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
674251881Speter                                       name, value, pool);
675251881Speter}
676251881Speter
677251881Speterconst char *
678251881Spetersvn_repos__post_commit_error_str(svn_error_t *err,
679251881Speter                                 apr_pool_t *pool)
680251881Speter{
681251881Speter  svn_error_t *hook_err1, *hook_err2;
682251881Speter  const char *msg;
683251881Speter
684251881Speter  if (! err)
685251881Speter    return _("(no error)");
686251881Speter
687251881Speter  err = svn_error_purge_tracing(err);
688251881Speter
689251881Speter  /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
690251881Speter     error from the post-commit script, if any, and hook_err2 should
691251881Speter     be the original error, but be defensive and handle a case where
692251881Speter     SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
693251881Speter  hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
694251881Speter  if (hook_err1 && hook_err1->child)
695251881Speter    hook_err2 = hook_err1->child;
696251881Speter  else
697251881Speter    hook_err2 = hook_err1;
698251881Speter
699251881Speter  /* This implementation counts on svn_repos_fs_commit_txn() and
700251881Speter     libsvn_repos/commit.c:complete_cb() returning
701251881Speter     svn_fs_commit_txn() as the parent error with a child
702251881Speter     SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error.  If the parent error
703251881Speter     is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
704251881Speter     in svn_fs_commit_txn().
705251881Speter
706251881Speter     The post-commit hook error message is already self describing, so
707251881Speter     it can be dropped into an error message without any additional
708251881Speter     text. */
709251881Speter  if (hook_err1)
710251881Speter    {
711251881Speter      if (err == hook_err1)
712251881Speter        {
713251881Speter          if (hook_err2->message)
714251881Speter            msg = apr_pstrdup(pool, hook_err2->message);
715251881Speter          else
716251881Speter            msg = _("post-commit hook failed with no error message.");
717251881Speter        }
718251881Speter      else
719251881Speter        {
720251881Speter          msg = hook_err2->message
721251881Speter                  ? apr_pstrdup(pool, hook_err2->message)
722251881Speter                  : _("post-commit hook failed with no error message.");
723251881Speter          msg = apr_psprintf(
724251881Speter                  pool,
725251881Speter                  _("post commit FS processing had error:\n%s\n%s"),
726251881Speter                  err->message ? err->message : _("(no error message)"),
727251881Speter                  msg);
728251881Speter        }
729251881Speter    }
730251881Speter  else
731251881Speter    {
732251881Speter      msg = apr_psprintf(pool,
733251881Speter                         _("post commit FS processing had error:\n%s"),
734251881Speter                         err->message ? err->message
735251881Speter                                      : _("(no error message)"));
736251881Speter    }
737251881Speter
738251881Speter  return msg;
739251881Speter}
740251881Speter
741251881Speterstatic svn_error_t *
742251881Speterclose_edit(void *edit_baton,
743251881Speter           apr_pool_t *pool)
744251881Speter{
745251881Speter  struct edit_baton *eb = edit_baton;
746251881Speter  svn_revnum_t new_revision = SVN_INVALID_REVNUM;
747251881Speter  svn_error_t *err;
748251881Speter  const char *conflict;
749251881Speter  const char *post_commit_err = NULL;
750251881Speter
751251881Speter  /* If no transaction has been created (ie. if open_root wasn't
752251881Speter     called before close_edit), abort the operation here with an
753251881Speter     error. */
754251881Speter  if (! eb->txn)
755251881Speter    return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
756251881Speter                            "No valid transaction supplied to close_edit");
757251881Speter
758251881Speter  /* Commit. */
759251881Speter  err = svn_repos_fs_commit_txn(&conflict, eb->repos,
760251881Speter                                &new_revision, eb->txn, pool);
761251881Speter
762251881Speter  if (SVN_IS_VALID_REVNUM(new_revision))
763251881Speter    {
764251881Speter      if (err)
765251881Speter        {
766251881Speter          /* If the error was in post-commit, then the commit itself
767251881Speter             succeeded.  In which case, save the post-commit warning
768251881Speter             (to be reported back to the client, who will probably
769251881Speter             display it as a warning) and clear the error. */
770251881Speter          post_commit_err = svn_repos__post_commit_error_str(err, pool);
771251881Speter          svn_error_clear(err);
772251881Speter        }
773251881Speter    }
774251881Speter  else
775251881Speter    {
776251881Speter      /* ### todo: we should check whether it really was a conflict,
777251881Speter         and return the conflict info if so? */
778251881Speter
779251881Speter      /* If the commit failed, it's *probably* due to a conflict --
780251881Speter         that is, the txn being out-of-date.  The filesystem gives us
781251881Speter         the ability to continue diddling the transaction and try
782251881Speter         again; but let's face it: that's not how the cvs or svn works
783251881Speter         from a user interface standpoint.  Thus we don't make use of
784251881Speter         this fs feature (for now, at least.)
785251881Speter
786251881Speter         So, in a nutshell: svn commits are an all-or-nothing deal.
787251881Speter         Each commit creates a new fs txn which either succeeds or is
788251881Speter         aborted completely.  No second chances;  the user simply
789251881Speter         needs to update and commit again  :) */
790251881Speter
791251881Speter      eb->txn_aborted = TRUE;
792251881Speter
793251881Speter      return svn_error_trace(
794251881Speter                svn_error_compose_create(err,
795251881Speter                                         svn_fs_abort_txn(eb->txn, pool)));
796251881Speter    }
797251881Speter
798251881Speter  /* At this point, the post-commit error has been converted to a string.
799251881Speter     That information will be passed to a callback, if provided. If the
800251881Speter     callback invocation fails in some way, that failure is returned here.
801251881Speter     IOW, the post-commit error information is low priority compared to
802251881Speter     other gunk here.  */
803251881Speter
804251881Speter  /* Pass new revision information to the caller's callback. */
805251881Speter  return svn_error_trace(invoke_commit_cb(eb->commit_callback,
806251881Speter                                          eb->commit_callback_baton,
807251881Speter                                          eb->repos->fs,
808251881Speter                                          new_revision,
809251881Speter                                          post_commit_err,
810251881Speter                                          pool));
811251881Speter}
812251881Speter
813251881Speter
814251881Speterstatic svn_error_t *
815251881Speterabort_edit(void *edit_baton,
816251881Speter           apr_pool_t *pool)
817251881Speter{
818251881Speter  struct edit_baton *eb = edit_baton;
819251881Speter  if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
820251881Speter    return SVN_NO_ERROR;
821251881Speter
822251881Speter  eb->txn_aborted = TRUE;
823251881Speter
824251881Speter  return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
825251881Speter}
826251881Speter
827251881Speter
828251881Speterstatic svn_error_t *
829251881Speterfetch_props_func(apr_hash_t **props,
830251881Speter                 void *baton,
831251881Speter                 const char *path,
832251881Speter                 svn_revnum_t base_revision,
833251881Speter                 apr_pool_t *result_pool,
834251881Speter                 apr_pool_t *scratch_pool)
835251881Speter{
836251881Speter  struct edit_baton *eb = baton;
837251881Speter  svn_fs_root_t *fs_root;
838251881Speter  svn_error_t *err;
839251881Speter
840251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
841251881Speter                               svn_fs_txn_base_revision(eb->txn),
842251881Speter                               scratch_pool));
843251881Speter  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
844251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
845251881Speter    {
846251881Speter      svn_error_clear(err);
847251881Speter      *props = apr_hash_make(result_pool);
848251881Speter      return SVN_NO_ERROR;
849251881Speter    }
850251881Speter  else if (err)
851251881Speter    return svn_error_trace(err);
852251881Speter
853251881Speter  return SVN_NO_ERROR;
854251881Speter}
855251881Speter
856251881Speterstatic svn_error_t *
857251881Speterfetch_kind_func(svn_node_kind_t *kind,
858251881Speter                void *baton,
859251881Speter                const char *path,
860251881Speter                svn_revnum_t base_revision,
861251881Speter                apr_pool_t *scratch_pool)
862251881Speter{
863251881Speter  struct edit_baton *eb = baton;
864251881Speter  svn_fs_root_t *fs_root;
865251881Speter
866251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
867251881Speter    base_revision = svn_fs_txn_base_revision(eb->txn);
868251881Speter
869251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
870251881Speter
871251881Speter  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
872251881Speter
873251881Speter  return SVN_NO_ERROR;
874251881Speter}
875251881Speter
876251881Speterstatic svn_error_t *
877251881Speterfetch_base_func(const char **filename,
878251881Speter                void *baton,
879251881Speter                const char *path,
880251881Speter                svn_revnum_t base_revision,
881251881Speter                apr_pool_t *result_pool,
882251881Speter                apr_pool_t *scratch_pool)
883251881Speter{
884251881Speter  struct edit_baton *eb = baton;
885251881Speter  svn_stream_t *contents;
886251881Speter  svn_stream_t *file_stream;
887251881Speter  const char *tmp_filename;
888251881Speter  svn_fs_root_t *fs_root;
889251881Speter  svn_error_t *err;
890251881Speter
891251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
892251881Speter    base_revision = svn_fs_txn_base_revision(eb->txn);
893251881Speter
894251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
895251881Speter
896251881Speter  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
897251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
898251881Speter    {
899251881Speter      svn_error_clear(err);
900251881Speter      *filename = NULL;
901251881Speter      return SVN_NO_ERROR;
902251881Speter    }
903251881Speter  else if (err)
904251881Speter    return svn_error_trace(err);
905251881Speter  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
906251881Speter                                 svn_io_file_del_on_pool_cleanup,
907251881Speter                                 scratch_pool, scratch_pool));
908251881Speter  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
909251881Speter
910251881Speter  *filename = apr_pstrdup(result_pool, tmp_filename);
911251881Speter
912251881Speter  return SVN_NO_ERROR;
913251881Speter}
914251881Speter
915251881Speter
916251881Speter
917251881Speter/*** Public interfaces. ***/
918251881Speter
919251881Spetersvn_error_t *
920251881Spetersvn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
921251881Speter                             void **edit_baton,
922251881Speter                             svn_repos_t *repos,
923251881Speter                             svn_fs_txn_t *txn,
924251881Speter                             const char *repos_url,
925251881Speter                             const char *base_path,
926251881Speter                             apr_hash_t *revprop_table,
927251881Speter                             svn_commit_callback2_t commit_callback,
928251881Speter                             void *commit_baton,
929251881Speter                             svn_repos_authz_callback_t authz_callback,
930251881Speter                             void *authz_baton,
931251881Speter                             apr_pool_t *pool)
932251881Speter{
933251881Speter  svn_delta_editor_t *e;
934251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
935251881Speter  struct edit_baton *eb;
936251881Speter  svn_delta_shim_callbacks_t *shim_callbacks =
937251881Speter                                    svn_delta_shim_callbacks_default(pool);
938251881Speter
939251881Speter  /* Do a global authz access lookup.  Users with no write access
940251881Speter     whatsoever to the repository don't get a commit editor. */
941251881Speter  if (authz_callback)
942251881Speter    {
943251881Speter      svn_boolean_t allowed;
944251881Speter
945251881Speter      SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
946251881Speter                             authz_baton, pool));
947251881Speter      if (!allowed)
948251881Speter        return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
949251881Speter                                "Not authorized to open a commit editor.");
950251881Speter    }
951251881Speter
952251881Speter  /* Allocate the structures. */
953251881Speter  e = svn_delta_default_editor(pool);
954251881Speter  eb = apr_pcalloc(subpool, sizeof(*eb));
955251881Speter
956251881Speter  /* Set up the editor. */
957251881Speter  e->open_root         = open_root;
958251881Speter  e->delete_entry      = delete_entry;
959251881Speter  e->add_directory     = add_directory;
960251881Speter  e->open_directory    = open_directory;
961251881Speter  e->change_dir_prop   = change_dir_prop;
962251881Speter  e->add_file          = add_file;
963251881Speter  e->open_file         = open_file;
964251881Speter  e->close_file        = close_file;
965251881Speter  e->apply_textdelta   = apply_textdelta;
966251881Speter  e->change_file_prop  = change_file_prop;
967251881Speter  e->close_edit        = close_edit;
968251881Speter  e->abort_edit        = abort_edit;
969251881Speter
970251881Speter  /* Set up the edit baton. */
971251881Speter  eb->pool = subpool;
972251881Speter  eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
973251881Speter  eb->commit_callback = commit_callback;
974251881Speter  eb->commit_callback_baton = commit_baton;
975251881Speter  eb->authz_callback = authz_callback;
976251881Speter  eb->authz_baton = authz_baton;
977251881Speter  eb->base_path = svn_fspath__canonicalize(base_path, subpool);
978251881Speter  eb->repos = repos;
979251881Speter  eb->repos_url = repos_url;
980251881Speter  eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
981251881Speter                                       subpool);
982251881Speter  eb->fs = svn_repos_fs(repos);
983251881Speter  eb->txn = txn;
984251881Speter  eb->txn_owner = txn == NULL;
985251881Speter
986251881Speter  *edit_baton = eb;
987251881Speter  *editor = e;
988251881Speter
989251881Speter  shim_callbacks->fetch_props_func = fetch_props_func;
990251881Speter  shim_callbacks->fetch_kind_func = fetch_kind_func;
991251881Speter  shim_callbacks->fetch_base_func = fetch_base_func;
992251881Speter  shim_callbacks->fetch_baton = eb;
993251881Speter
994251881Speter  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
995251881Speter                                   eb->repos_url, eb->base_path,
996251881Speter                                   shim_callbacks, pool, pool));
997251881Speter
998251881Speter  return SVN_NO_ERROR;
999251881Speter}
1000251881Speter
1001251881Speter
1002251881Speter#if 0
1003251881Speterstatic svn_error_t *
1004251881Speterev2_check_authz(const struct ev2_baton *eb,
1005251881Speter                const char *relpath,
1006251881Speter                svn_repos_authz_access_t required,
1007251881Speter                apr_pool_t *scratch_pool)
1008251881Speter{
1009251881Speter  const char *fspath;
1010251881Speter  svn_boolean_t allowed;
1011251881Speter
1012251881Speter  if (eb->authz == NULL)
1013251881Speter    return SVN_NO_ERROR;
1014251881Speter
1015251881Speter  if (relpath)
1016251881Speter    fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL);
1017251881Speter  else
1018251881Speter    fspath = NULL;
1019251881Speter
1020251881Speter  SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
1021251881Speter                                       eb->authz_user, required,
1022251881Speter                                       &allowed, scratch_pool));
1023251881Speter  if (!allowed)
1024251881Speter    return svn_error_create(required & svn_authz_write
1025251881Speter                            ? SVN_ERR_AUTHZ_UNWRITABLE
1026251881Speter                            : SVN_ERR_AUTHZ_UNREADABLE,
1027251881Speter                            NULL, "Access denied");
1028251881Speter
1029251881Speter  return SVN_NO_ERROR;
1030251881Speter}
1031251881Speter#endif
1032251881Speter
1033251881Speter
1034251881Speter/* This implements svn_editor_cb_add_directory_t */
1035251881Speterstatic svn_error_t *
1036251881Speteradd_directory_cb(void *baton,
1037251881Speter                 const char *relpath,
1038251881Speter                 const apr_array_header_t *children,
1039251881Speter                 apr_hash_t *props,
1040251881Speter                 svn_revnum_t replaces_rev,
1041251881Speter                 apr_pool_t *scratch_pool)
1042251881Speter{
1043251881Speter  struct ev2_baton *eb = baton;
1044251881Speter
1045251881Speter  SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
1046251881Speter                                   replaces_rev));
1047251881Speter  return SVN_NO_ERROR;
1048251881Speter}
1049251881Speter
1050251881Speter
1051251881Speter/* This implements svn_editor_cb_add_file_t */
1052251881Speterstatic svn_error_t *
1053251881Speteradd_file_cb(void *baton,
1054251881Speter            const char *relpath,
1055251881Speter            const svn_checksum_t *checksum,
1056251881Speter            svn_stream_t *contents,
1057251881Speter            apr_hash_t *props,
1058251881Speter            svn_revnum_t replaces_rev,
1059251881Speter            apr_pool_t *scratch_pool)
1060251881Speter{
1061251881Speter  struct ev2_baton *eb = baton;
1062251881Speter
1063251881Speter  SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
1064251881Speter                              replaces_rev));
1065251881Speter  return SVN_NO_ERROR;
1066251881Speter}
1067251881Speter
1068251881Speter
1069251881Speter/* This implements svn_editor_cb_add_symlink_t */
1070251881Speterstatic svn_error_t *
1071251881Speteradd_symlink_cb(void *baton,
1072251881Speter               const char *relpath,
1073251881Speter               const char *target,
1074251881Speter               apr_hash_t *props,
1075251881Speter               svn_revnum_t replaces_rev,
1076251881Speter               apr_pool_t *scratch_pool)
1077251881Speter{
1078251881Speter  struct ev2_baton *eb = baton;
1079251881Speter
1080251881Speter  SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
1081251881Speter                                 replaces_rev));
1082251881Speter  return SVN_NO_ERROR;
1083251881Speter}
1084251881Speter
1085251881Speter
1086251881Speter/* This implements svn_editor_cb_add_absent_t */
1087251881Speterstatic svn_error_t *
1088251881Speteradd_absent_cb(void *baton,
1089251881Speter              const char *relpath,
1090251881Speter              svn_node_kind_t kind,
1091251881Speter              svn_revnum_t replaces_rev,
1092251881Speter              apr_pool_t *scratch_pool)
1093251881Speter{
1094251881Speter  struct ev2_baton *eb = baton;
1095251881Speter
1096251881Speter  SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
1097251881Speter  return SVN_NO_ERROR;
1098251881Speter}
1099251881Speter
1100251881Speter
1101251881Speter/* This implements svn_editor_cb_alter_directory_t */
1102251881Speterstatic svn_error_t *
1103251881Speteralter_directory_cb(void *baton,
1104251881Speter                   const char *relpath,
1105251881Speter                   svn_revnum_t revision,
1106251881Speter                   const apr_array_header_t *children,
1107251881Speter                   apr_hash_t *props,
1108251881Speter                   apr_pool_t *scratch_pool)
1109251881Speter{
1110251881Speter  struct ev2_baton *eb = baton;
1111251881Speter
1112251881Speter  SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
1113251881Speter                                     children, props));
1114251881Speter  return SVN_NO_ERROR;
1115251881Speter}
1116251881Speter
1117251881Speter
1118251881Speter/* This implements svn_editor_cb_alter_file_t */
1119251881Speterstatic svn_error_t *
1120251881Speteralter_file_cb(void *baton,
1121251881Speter              const char *relpath,
1122251881Speter              svn_revnum_t revision,
1123251881Speter              apr_hash_t *props,
1124251881Speter              const svn_checksum_t *checksum,
1125251881Speter              svn_stream_t *contents,
1126251881Speter              apr_pool_t *scratch_pool)
1127251881Speter{
1128251881Speter  struct ev2_baton *eb = baton;
1129251881Speter
1130251881Speter  SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props,
1131251881Speter                                checksum, contents));
1132251881Speter  return SVN_NO_ERROR;
1133251881Speter}
1134251881Speter
1135251881Speter
1136251881Speter/* This implements svn_editor_cb_alter_symlink_t */
1137251881Speterstatic svn_error_t *
1138251881Speteralter_symlink_cb(void *baton,
1139251881Speter                 const char *relpath,
1140251881Speter                 svn_revnum_t revision,
1141251881Speter                 apr_hash_t *props,
1142251881Speter                 const char *target,
1143251881Speter                 apr_pool_t *scratch_pool)
1144251881Speter{
1145251881Speter  struct ev2_baton *eb = baton;
1146251881Speter
1147251881Speter  SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props,
1148251881Speter                                   target));
1149251881Speter  return SVN_NO_ERROR;
1150251881Speter}
1151251881Speter
1152251881Speter
1153251881Speter/* This implements svn_editor_cb_delete_t */
1154251881Speterstatic svn_error_t *
1155251881Speterdelete_cb(void *baton,
1156251881Speter          const char *relpath,
1157251881Speter          svn_revnum_t revision,
1158251881Speter          apr_pool_t *scratch_pool)
1159251881Speter{
1160251881Speter  struct ev2_baton *eb = baton;
1161251881Speter
1162251881Speter  SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
1163251881Speter  return SVN_NO_ERROR;
1164251881Speter}
1165251881Speter
1166251881Speter
1167251881Speter/* This implements svn_editor_cb_copy_t */
1168251881Speterstatic svn_error_t *
1169251881Spetercopy_cb(void *baton,
1170251881Speter        const char *src_relpath,
1171251881Speter        svn_revnum_t src_revision,
1172251881Speter        const char *dst_relpath,
1173251881Speter        svn_revnum_t replaces_rev,
1174251881Speter        apr_pool_t *scratch_pool)
1175251881Speter{
1176251881Speter  struct ev2_baton *eb = baton;
1177251881Speter
1178251881Speter  SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
1179251881Speter                          replaces_rev));
1180251881Speter  return SVN_NO_ERROR;
1181251881Speter}
1182251881Speter
1183251881Speter
1184251881Speter/* This implements svn_editor_cb_move_t */
1185251881Speterstatic svn_error_t *
1186251881Spetermove_cb(void *baton,
1187251881Speter        const char *src_relpath,
1188251881Speter        svn_revnum_t src_revision,
1189251881Speter        const char *dst_relpath,
1190251881Speter        svn_revnum_t replaces_rev,
1191251881Speter        apr_pool_t *scratch_pool)
1192251881Speter{
1193251881Speter  struct ev2_baton *eb = baton;
1194251881Speter
1195251881Speter  SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
1196251881Speter                          replaces_rev));
1197251881Speter  return SVN_NO_ERROR;
1198251881Speter}
1199251881Speter
1200251881Speter
1201251881Speter/* This implements svn_editor_cb_rotate_t */
1202251881Speterstatic svn_error_t *
1203251881Speterrotate_cb(void *baton,
1204251881Speter          const apr_array_header_t *relpaths,
1205251881Speter          const apr_array_header_t *revisions,
1206251881Speter          apr_pool_t *scratch_pool)
1207251881Speter{
1208251881Speter  struct ev2_baton *eb = baton;
1209251881Speter
1210251881Speter  SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions));
1211251881Speter  return SVN_NO_ERROR;
1212251881Speter}
1213251881Speter
1214251881Speter
1215251881Speter/* This implements svn_editor_cb_complete_t */
1216251881Speterstatic svn_error_t *
1217251881Spetercomplete_cb(void *baton,
1218251881Speter            apr_pool_t *scratch_pool)
1219251881Speter{
1220251881Speter  struct ev2_baton *eb = baton;
1221251881Speter  svn_revnum_t revision;
1222251881Speter  svn_error_t *post_commit_err;
1223251881Speter  const char *conflict_path;
1224251881Speter  svn_error_t *err;
1225251881Speter  const char *post_commit_errstr;
1226251881Speter  apr_hash_t *hooks_env;
1227251881Speter
1228251881Speter  /* Parse the hooks-env file (if any). */
1229251881Speter  SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
1230251881Speter                                     scratch_pool, scratch_pool));
1231251881Speter
1232251881Speter  /* The transaction has been fully edited. Let the pre-commit hook
1233251881Speter     have a look at the thing.  */
1234251881Speter  SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
1235251881Speter                                      eb->txn_name, scratch_pool));
1236251881Speter
1237251881Speter  /* Hook is done. Let's do the actual commit.  */
1238251881Speter  SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
1239251881Speter                                eb->inner, scratch_pool, scratch_pool));
1240251881Speter
1241251881Speter  /* Did a conflict occur during the commit process?  */
1242251881Speter  if (conflict_path != NULL)
1243251881Speter    return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1244251881Speter                             _("Conflict at '%s'"), conflict_path);
1245251881Speter
1246251881Speter  /* Since did not receive an error during the commit process, and no
1247251881Speter     conflict was specified... we committed a revision. Run the hooks.
1248251881Speter     Other errors may have occurred within the FS (specified by the
1249251881Speter     POST_COMMIT_ERR localvar), but we need to run the hooks.  */
1250251881Speter  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
1251251881Speter  err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
1252251881Speter                                     eb->txn_name, scratch_pool);
1253251881Speter  if (err)
1254251881Speter    err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1255251881Speter                           _("Commit succeeded, but post-commit hook failed"));
1256251881Speter
1257251881Speter  /* Combine the FS errors with the hook errors, and stringify.  */
1258251881Speter  err = svn_error_compose_create(post_commit_err, err);
1259251881Speter  if (err)
1260251881Speter    {
1261251881Speter      post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
1262251881Speter      svn_error_clear(err);
1263251881Speter    }
1264251881Speter  else
1265251881Speter    {
1266251881Speter      post_commit_errstr = NULL;
1267251881Speter    }
1268251881Speter
1269251881Speter  return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
1270251881Speter                                          eb->repos->fs, revision,
1271251881Speter                                          post_commit_errstr,
1272251881Speter                                          scratch_pool));
1273251881Speter}
1274251881Speter
1275251881Speter
1276251881Speter/* This implements svn_editor_cb_abort_t */
1277251881Speterstatic svn_error_t *
1278251881Speterabort_cb(void *baton,
1279251881Speter         apr_pool_t *scratch_pool)
1280251881Speter{
1281251881Speter  struct ev2_baton *eb = baton;
1282251881Speter
1283251881Speter  SVN_ERR(svn_editor_abort(eb->inner));
1284251881Speter  return SVN_NO_ERROR;
1285251881Speter}
1286251881Speter
1287251881Speter
1288251881Speterstatic svn_error_t *
1289251881Speterapply_revprops(svn_fs_t *fs,
1290251881Speter               const char *txn_name,
1291251881Speter               apr_hash_t *revprops,
1292251881Speter               apr_pool_t *scratch_pool)
1293251881Speter{
1294251881Speter  svn_fs_txn_t *txn;
1295251881Speter  const apr_array_header_t *revprops_array;
1296251881Speter
1297251881Speter  /* The FS editor has a TXN inside it, but we can't access it. Open another
1298251881Speter     based on the TXN_NAME.  */
1299251881Speter  SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
1300251881Speter
1301251881Speter  /* Validate and apply the revision properties.  */
1302251881Speter  revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
1303251881Speter  SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
1304251881Speter
1305251881Speter  /* ### do we need to force the txn to close, or is it enough to wait
1306251881Speter     ### for the pool to be cleared?  */
1307251881Speter  return SVN_NO_ERROR;
1308251881Speter}
1309251881Speter
1310251881Speter
1311251881Spetersvn_error_t *
1312251881Spetersvn_repos__get_commit_ev2(svn_editor_t **editor,
1313251881Speter                          svn_repos_t *repos,
1314251881Speter                          svn_authz_t *authz,
1315251881Speter                          const char *authz_repos_name,
1316251881Speter                          const char *authz_user,
1317251881Speter                          apr_hash_t *revprops,
1318251881Speter                          svn_commit_callback2_t commit_cb,
1319251881Speter                          void *commit_baton,
1320251881Speter                          svn_cancel_func_t cancel_func,
1321251881Speter                          void *cancel_baton,
1322251881Speter                          apr_pool_t *result_pool,
1323251881Speter                          apr_pool_t *scratch_pool)
1324251881Speter{
1325251881Speter  static const svn_editor_cb_many_t editor_cbs = {
1326251881Speter    add_directory_cb,
1327251881Speter    add_file_cb,
1328251881Speter    add_symlink_cb,
1329251881Speter    add_absent_cb,
1330251881Speter    alter_directory_cb,
1331251881Speter    alter_file_cb,
1332251881Speter    alter_symlink_cb,
1333251881Speter    delete_cb,
1334251881Speter    copy_cb,
1335251881Speter    move_cb,
1336251881Speter    rotate_cb,
1337251881Speter    complete_cb,
1338251881Speter    abort_cb
1339251881Speter  };
1340251881Speter  struct ev2_baton *eb;
1341251881Speter  const svn_string_t *author;
1342251881Speter  apr_hash_t *hooks_env;
1343251881Speter
1344251881Speter  /* Parse the hooks-env file (if any). */
1345251881Speter  SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
1346251881Speter                                     scratch_pool, scratch_pool));
1347251881Speter
1348251881Speter  /* Can the user modify the repository at all?  */
1349251881Speter  /* ### check against AUTHZ.  */
1350251881Speter
1351251881Speter  author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
1352251881Speter
1353251881Speter  eb = apr_palloc(result_pool, sizeof(*eb));
1354251881Speter  eb->repos = repos;
1355251881Speter  eb->authz = authz;
1356251881Speter  eb->authz_repos_name = authz_repos_name;
1357251881Speter  eb->authz_user = authz_user;
1358251881Speter  eb->commit_cb = commit_cb;
1359251881Speter  eb->commit_baton = commit_baton;
1360251881Speter
1361251881Speter  SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
1362251881Speter                                repos->fs, SVN_FS_TXN_CHECK_LOCKS,
1363251881Speter                                cancel_func, cancel_baton,
1364251881Speter                                result_pool, scratch_pool));
1365251881Speter
1366251881Speter  /* The TXN has been created. Go ahead and apply all revision properties.  */
1367251881Speter  SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
1368251881Speter
1369251881Speter  /* Okay... some access is allowed. Let's run the start-commit hook.  */
1370251881Speter  SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
1371251881Speter                                        author ? author->data : NULL,
1372251881Speter                                        repos->client_capabilities,
1373251881Speter                                        eb->txn_name, scratch_pool));
1374251881Speter
1375251881Speter  /* Wrap the FS editor within our editor.  */
1376251881Speter  SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
1377251881Speter                            result_pool, scratch_pool));
1378251881Speter  SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
1379251881Speter
1380251881Speter  return SVN_NO_ERROR;
1381251881Speter}
1382