1251881Speter/* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
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
24299742Sdim#include <stdarg.h>
25299742Sdim
26251881Speter#include "svn_private_config.h"
27251881Speter#include "svn_pools.h"
28251881Speter#include "svn_error.h"
29251881Speter#include "svn_fs.h"
30251881Speter#include "svn_hash.h"
31251881Speter#include "svn_iter.h"
32251881Speter#include "svn_repos.h"
33251881Speter#include "svn_string.h"
34251881Speter#include "svn_dirent_uri.h"
35251881Speter#include "svn_path.h"
36251881Speter#include "svn_time.h"
37251881Speter#include "svn_checksum.h"
38251881Speter#include "svn_props.h"
39251881Speter#include "svn_sorts.h"
40251881Speter
41299742Sdim#include "private/svn_repos_private.h"
42251881Speter#include "private/svn_mergeinfo_private.h"
43251881Speter#include "private/svn_fs_private.h"
44299742Sdim#include "private/svn_sorts_private.h"
45299742Sdim#include "private/svn_utf_private.h"
46299742Sdim#include "private/svn_cache.h"
47251881Speter
48251881Speter#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
49251881Speter
50251881Speter/*----------------------------------------------------------------------*/
51251881Speter
52251881Speter
53299742Sdim/* To be able to check whether a path exists in the current revision
54299742Sdim   (as changes come in), we need to track the relevant tree changes.
55251881Speter
56299742Sdim   In particular, we remember deletions, additions and copies including
57299742Sdim   their copy-from info.  Since the dump performs a pre-order tree walk,
58299742Sdim   we only need to store the data for the stack of parent folders.
59299742Sdim
60299742Sdim   The problem that we are trying to solve is that the dump receives
61299742Sdim   transforming operations whose validity depends on previous operations
62299742Sdim   in the same revision but cannot be checked against the final state
63299742Sdim   as stored in the repository as that is the state *after* we applied
64299742Sdim   the respective tree changes.
65299742Sdim
66299742Sdim   Note that the tracker functions don't perform any sanity or validity
67299742Sdim   checks.  Those higher-level tests have to be done in the calling code.
68299742Sdim   However, there is no way to corrupt the data structure using the
69299742Sdim   provided functions.
70299742Sdim */
71299742Sdim
72299742Sdim/* Single entry in the path tracker.  Not all levels along the path
73299742Sdim   hierarchy do need to have an instance of this struct but only those
74299742Sdim   that got changed by a tree modification.
75299742Sdim
76299742Sdim   Please note that the path info in this struct is stored in re-usable
77299742Sdim   stringbuf objects such that we don't need to allocate more memory than
78299742Sdim   the longest path we encounter.
79299742Sdim */
80299742Sdimtypedef struct path_tracker_entry_t
81299742Sdim{
82299742Sdim  /* path in the current tree */
83299742Sdim  svn_stringbuf_t *path;
84299742Sdim
85299742Sdim  /* copy-from path (must be empty if COPYFROM_REV is SVN_INVALID_REVNUM) */
86299742Sdim  svn_stringbuf_t *copyfrom_path;
87299742Sdim
88299742Sdim  /* copy-from revision (SVN_INVALID_REVNUM for additions / replacements
89299742Sdim     that don't copy history, i.e. with no sub-tree) */
90299742Sdim  svn_revnum_t copyfrom_rev;
91299742Sdim
92299742Sdim  /* if FALSE, PATH has been deleted */
93299742Sdim  svn_boolean_t exists;
94299742Sdim} path_tracker_entry_t;
95299742Sdim
96299742Sdim/* Tracks all tree modifications above the current path.
97299742Sdim */
98299742Sdimtypedef struct path_tracker_t
99299742Sdim{
100299742Sdim  /* Container for all relevant tree changes in depth order.
101299742Sdim     May contain more entries than DEPTH to allow for reusing memory.
102299742Sdim     Only entries 0 .. DEPTH-1 are valid.
103299742Sdim   */
104299742Sdim  apr_array_header_t *stack;
105299742Sdim
106299742Sdim  /* Number of relevant entries in STACK.  May be 0 */
107299742Sdim  int depth;
108299742Sdim
109299742Sdim  /* Revision that we current track.  If DEPTH is 0, paths are exist in
110299742Sdim     REVISION exactly when they exist in REVISION-1.  This applies only
111299742Sdim     to the current state of our tree walk.
112299742Sdim   */
113299742Sdim  svn_revnum_t revision;
114299742Sdim
115299742Sdim  /* Allocate container entries here. */
116299742Sdim  apr_pool_t *pool;
117299742Sdim} path_tracker_t;
118299742Sdim
119299742Sdim/* Return a new path tracker object for REVISION, allocated in POOL.
120299742Sdim */
121299742Sdimstatic path_tracker_t *
122299742Sdimtracker_create(svn_revnum_t revision,
123299742Sdim               apr_pool_t *pool)
124299742Sdim{
125299742Sdim  path_tracker_t *result = apr_pcalloc(pool, sizeof(*result));
126299742Sdim  result->stack = apr_array_make(pool, 16, sizeof(path_tracker_entry_t));
127299742Sdim  result->revision = revision;
128299742Sdim  result->pool = pool;
129299742Sdim
130299742Sdim  return result;
131299742Sdim}
132299742Sdim
133299742Sdim/* Remove all entries from TRACKER that are not relevant to PATH anymore.
134299742Sdim * If ALLOW_EXACT_MATCH is FALSE, keep only entries that pertain to
135299742Sdim * parent folders but not to PATH itself.
136299742Sdim *
137299742Sdim * This internal function implicitly updates the tracker state during the
138299742Sdim * tree by removing "past" entries.  Other functions will add entries when
139299742Sdim * we encounter a new tree change.
140299742Sdim */
141299742Sdimstatic void
142299742Sdimtracker_trim(path_tracker_t *tracker,
143299742Sdim             const char *path,
144299742Sdim             svn_boolean_t allow_exact_match)
145299742Sdim{
146299742Sdim  /* remove everything that is unrelated to PATH.
147299742Sdim     Note that TRACKER->STACK is depth-ordered,
148299742Sdim     i.e. stack[N] is a (maybe indirect) parent of stack[N+1]
149299742Sdim     for N+1 < DEPTH.
150299742Sdim   */
151299742Sdim  for (; tracker->depth; --tracker->depth)
152299742Sdim    {
153299742Sdim      path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
154299742Sdim                                                    tracker->depth - 1,
155299742Sdim                                                    path_tracker_entry_t);
156299742Sdim      const char *rel_path
157299742Sdim        = svn_dirent_skip_ancestor(parent->path->data, path);
158299742Sdim
159299742Sdim      /* always keep parents.  Keep exact matches when allowed. */
160299742Sdim      if (rel_path && (allow_exact_match || *rel_path != '\0'))
161299742Sdim        break;
162299742Sdim    }
163299742Sdim}
164299742Sdim
165299742Sdim/* Using TRACKER, check what path at what revision in the repository must
166299742Sdim   be checked to decide that whether PATH exists.  Return the info in
167299742Sdim   *ORIG_PATH and *ORIG_REV, respectively.
168299742Sdim
169299742Sdim   If the path is known to not exist, *ORIG_PATH will be NULL and *ORIG_REV
170299742Sdim   will be SVN_INVALID_REVNUM.  If *ORIG_REV is SVN_INVALID_REVNUM, PATH
171299742Sdim   has just been added in the revision currently being tracked.
172299742Sdim
173299742Sdim   Use POOL for allocations.  Note that *ORIG_PATH may be allocated in POOL,
174299742Sdim   a reference to internal data with the same lifetime as TRACKER or just
175299742Sdim   PATH.
176299742Sdim */
177299742Sdimstatic void
178299742Sdimtracker_lookup(const char **orig_path,
179299742Sdim               svn_revnum_t *orig_rev,
180299742Sdim               path_tracker_t *tracker,
181299742Sdim               const char *path,
182299742Sdim               apr_pool_t *pool)
183299742Sdim{
184299742Sdim  tracker_trim(tracker, path, TRUE);
185299742Sdim  if (tracker->depth == 0)
186299742Sdim    {
187299742Sdim      /* no tree changes -> paths are the same as in the previous rev. */
188299742Sdim      *orig_path = path;
189299742Sdim      *orig_rev = tracker->revision - 1;
190299742Sdim    }
191299742Sdim  else
192299742Sdim    {
193299742Sdim      path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack,
194299742Sdim                                                    tracker->depth - 1,
195299742Sdim                                                    path_tracker_entry_t);
196299742Sdim      if (parent->exists)
197299742Sdim        {
198299742Sdim          const char *rel_path
199299742Sdim            = svn_dirent_skip_ancestor(parent->path->data, path);
200299742Sdim
201299742Sdim          if (parent->copyfrom_rev != SVN_INVALID_REVNUM)
202299742Sdim            {
203299742Sdim              /* parent is a copy with history. Translate path. */
204299742Sdim              *orig_path = svn_dirent_join(parent->copyfrom_path->data,
205299742Sdim                                           rel_path, pool);
206299742Sdim              *orig_rev = parent->copyfrom_rev;
207299742Sdim            }
208299742Sdim          else if (*rel_path == '\0')
209299742Sdim            {
210299742Sdim              /* added in this revision with no history */
211299742Sdim              *orig_path = path;
212299742Sdim              *orig_rev = tracker->revision;
213299742Sdim            }
214299742Sdim          else
215299742Sdim            {
216299742Sdim              /* parent got added but not this path */
217299742Sdim              *orig_path = NULL;
218299742Sdim              *orig_rev = SVN_INVALID_REVNUM;
219299742Sdim            }
220299742Sdim        }
221299742Sdim      else
222299742Sdim        {
223299742Sdim          /* (maybe parent) path has been deleted */
224299742Sdim          *orig_path = NULL;
225299742Sdim          *orig_rev = SVN_INVALID_REVNUM;
226299742Sdim        }
227299742Sdim    }
228299742Sdim}
229299742Sdim
230299742Sdim/* Return a reference to the stack entry in TRACKER for PATH.  If no
231299742Sdim   suitable entry exists, add one.  Implicitly updates the tracked tree
232299742Sdim   location.
233299742Sdim
234299742Sdim   Only the PATH member of the result is being updated.  All other members
235299742Sdim   will have undefined values.
236299742Sdim */
237299742Sdimstatic path_tracker_entry_t *
238299742Sdimtracker_add_entry(path_tracker_t *tracker,
239299742Sdim                  const char *path)
240299742Sdim{
241299742Sdim  path_tracker_entry_t *entry;
242299742Sdim  tracker_trim(tracker, path, FALSE);
243299742Sdim
244299742Sdim  if (tracker->depth == tracker->stack->nelts)
245299742Sdim    {
246299742Sdim      entry = apr_array_push(tracker->stack);
247299742Sdim      entry->path = svn_stringbuf_create_empty(tracker->pool);
248299742Sdim      entry->copyfrom_path = svn_stringbuf_create_empty(tracker->pool);
249299742Sdim    }
250299742Sdim  else
251299742Sdim    {
252299742Sdim      entry = &APR_ARRAY_IDX(tracker->stack, tracker->depth,
253299742Sdim                             path_tracker_entry_t);
254299742Sdim    }
255299742Sdim
256299742Sdim  svn_stringbuf_set(entry->path, path);
257299742Sdim  ++tracker->depth;
258299742Sdim
259299742Sdim  return entry;
260299742Sdim}
261299742Sdim
262299742Sdim/* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to
263299742Sdim   PATH in the tracked revision.
264299742Sdim */
265299742Sdimstatic void
266299742Sdimtracker_path_copy(path_tracker_t *tracker,
267299742Sdim                  const char *path,
268299742Sdim                  const char *copyfrom_path,
269299742Sdim                  svn_revnum_t copyfrom_rev)
270299742Sdim{
271299742Sdim  path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
272299742Sdim
273299742Sdim  svn_stringbuf_set(entry->copyfrom_path, copyfrom_path);
274299742Sdim  entry->copyfrom_rev = copyfrom_rev;
275299742Sdim  entry->exists = TRUE;
276299742Sdim}
277299742Sdim
278299742Sdim/* Update the TRACKER with a plain addition of PATH (without history).
279299742Sdim */
280299742Sdimstatic void
281299742Sdimtracker_path_add(path_tracker_t *tracker,
282299742Sdim                 const char *path)
283299742Sdim{
284299742Sdim  path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
285299742Sdim
286299742Sdim  svn_stringbuf_setempty(entry->copyfrom_path);
287299742Sdim  entry->copyfrom_rev = SVN_INVALID_REVNUM;
288299742Sdim  entry->exists = TRUE;
289299742Sdim}
290299742Sdim
291299742Sdim/* Update the TRACKER with a replacement of PATH with a plain addition
292299742Sdim   (without history).
293299742Sdim */
294299742Sdimstatic void
295299742Sdimtracker_path_replace(path_tracker_t *tracker,
296299742Sdim                     const char *path)
297299742Sdim{
298299742Sdim  /* this will implicitly purge all previous sub-tree info from STACK.
299299742Sdim     Thus, no need to tack the deletion explicitly. */
300299742Sdim  tracker_path_add(tracker, path);
301299742Sdim}
302299742Sdim
303299742Sdim/* Update the TRACKER with a deletion of PATH.
304299742Sdim */
305299742Sdimstatic void
306299742Sdimtracker_path_delete(path_tracker_t *tracker,
307299742Sdim                    const char *path)
308299742Sdim{
309299742Sdim  path_tracker_entry_t *entry = tracker_add_entry(tracker, path);
310299742Sdim
311299742Sdim  svn_stringbuf_setempty(entry->copyfrom_path);
312299742Sdim  entry->copyfrom_rev = SVN_INVALID_REVNUM;
313299742Sdim  entry->exists = FALSE;
314299742Sdim}
315299742Sdim
316299742Sdim
317251881Speter/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
318251881Speter   store it into a new temporary file *TEMPFILE.  OLDROOT may be NULL,
319251881Speter   in which case the delta will be computed against an empty file, as
320251881Speter   per the svn_fs_get_file_delta_stream docstring.  Record the length
321251881Speter   of the temporary file in *LEN, and rewind the file before
322251881Speter   returning. */
323251881Speterstatic svn_error_t *
324251881Speterstore_delta(apr_file_t **tempfile, svn_filesize_t *len,
325251881Speter            svn_fs_root_t *oldroot, const char *oldpath,
326251881Speter            svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
327251881Speter{
328251881Speter  svn_stream_t *temp_stream;
329251881Speter  apr_off_t offset = 0;
330251881Speter  svn_txdelta_stream_t *delta_stream;
331251881Speter  svn_txdelta_window_handler_t wh;
332251881Speter  void *whb;
333251881Speter
334251881Speter  /* Create a temporary file and open a stream to it. Note that we need
335251881Speter     the file handle in order to rewind it. */
336251881Speter  SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
337251881Speter                                   svn_io_file_del_on_pool_cleanup,
338251881Speter                                   pool, pool));
339251881Speter  temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
340251881Speter
341251881Speter  /* Compute the delta and send it to the temporary file. */
342251881Speter  SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
343251881Speter                                       newroot, newpath, pool));
344251881Speter  svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
345251881Speter                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
346251881Speter  SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
347251881Speter
348251881Speter  /* Get the length of the temporary file and rewind it. */
349251881Speter  SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
350251881Speter  *len = offset;
351251881Speter  offset = 0;
352251881Speter  return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
353251881Speter}
354251881Speter
355251881Speter
356299742Sdim/* Send a notification of type #svn_repos_notify_warning, subtype WARNING,
357299742Sdim   with message WARNING_FMT formatted with the remaining variable arguments.
358299742Sdim   Send it by calling NOTIFY_FUNC (if not null) with NOTIFY_BATON.
359299742Sdim */
360299742Sdim__attribute__((format(printf, 5, 6)))
361299742Sdimstatic void
362299742Sdimnotify_warning(apr_pool_t *scratch_pool,
363299742Sdim               svn_repos_notify_func_t notify_func,
364299742Sdim               void *notify_baton,
365299742Sdim               svn_repos_notify_warning_t warning,
366299742Sdim               const char *warning_fmt,
367299742Sdim               ...)
368299742Sdim{
369299742Sdim  va_list va;
370299742Sdim  svn_repos_notify_t *notify;
371299742Sdim
372299742Sdim  if (notify_func == NULL)
373299742Sdim    return;
374299742Sdim
375299742Sdim  notify = svn_repos_notify_create(svn_repos_notify_warning, scratch_pool);
376299742Sdim  notify->warning = warning;
377299742Sdim  va_start(va, warning_fmt);
378299742Sdim  notify->warning_str = apr_pvsprintf(scratch_pool, warning_fmt, va);
379299742Sdim  va_end(va);
380299742Sdim
381299742Sdim  notify_func(notify_baton, notify, scratch_pool);
382299742Sdim}
383299742Sdim
384299742Sdim
385251881Speter/*----------------------------------------------------------------------*/
386251881Speter
387299742Sdim/* Write to STREAM the header in HEADERS named KEY, if present.
388299742Sdim */
389299742Sdimstatic svn_error_t *
390299742Sdimwrite_header(svn_stream_t *stream,
391299742Sdim             apr_hash_t *headers,
392299742Sdim             const char *key,
393299742Sdim             apr_pool_t *scratch_pool)
394299742Sdim{
395299742Sdim  const char *val = svn_hash_gets(headers, key);
396299742Sdim
397299742Sdim  if (val)
398299742Sdim    {
399299742Sdim      SVN_ERR(svn_stream_printf(stream, scratch_pool,
400299742Sdim                                "%s: %s\n", key, val));
401299742Sdim    }
402299742Sdim  return SVN_NO_ERROR;
403299742Sdim}
404299742Sdim
405299742Sdim/* Write headers, in arbitrary order.
406299742Sdim * ### TODO: use a stable order
407299742Sdim * ### Modifies HEADERS.
408299742Sdim */
409299742Sdimstatic svn_error_t *
410299742Sdimwrite_revision_headers(svn_stream_t *stream,
411299742Sdim                       apr_hash_t *headers,
412299742Sdim                       apr_pool_t *scratch_pool)
413299742Sdim{
414299742Sdim  const char **h;
415299742Sdim  apr_hash_index_t *hi;
416299742Sdim
417299742Sdim  static const char *revision_headers_order[] =
418299742Sdim  {
419299742Sdim    SVN_REPOS_DUMPFILE_REVISION_NUMBER,  /* must be first */
420299742Sdim    NULL
421299742Sdim  };
422299742Sdim
423299742Sdim  /* Write some headers in a given order */
424299742Sdim  for (h = revision_headers_order; *h; h++)
425299742Sdim    {
426299742Sdim      SVN_ERR(write_header(stream, headers, *h, scratch_pool));
427299742Sdim      svn_hash_sets(headers, *h, NULL);
428299742Sdim    }
429299742Sdim
430299742Sdim  /* Write any and all remaining headers except Content-length.
431299742Sdim   * ### TODO: use a stable order
432299742Sdim   */
433299742Sdim  for (hi = apr_hash_first(scratch_pool, headers); hi; hi = apr_hash_next(hi))
434299742Sdim    {
435299742Sdim      const char *key = apr_hash_this_key(hi);
436299742Sdim
437299742Sdim      if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) != 0)
438299742Sdim        SVN_ERR(write_header(stream, headers, key, scratch_pool));
439299742Sdim    }
440299742Sdim
441299742Sdim  /* Content-length must be last */
442299742Sdim  SVN_ERR(write_header(stream, headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
443299742Sdim                       scratch_pool));
444299742Sdim
445299742Sdim  return SVN_NO_ERROR;
446299742Sdim}
447299742Sdim
448299742Sdim/* A header entry: the element type of the apr_array_header_t which is
449299742Sdim * the real type of svn_repos__dumpfile_headers_t.
450299742Sdim */
451299742Sdimtypedef struct svn_repos__dumpfile_header_entry_t {
452299742Sdim  const char *key, *val;
453299742Sdim} svn_repos__dumpfile_header_entry_t;
454299742Sdim
455299742Sdimsvn_repos__dumpfile_headers_t *
456299742Sdimsvn_repos__dumpfile_headers_create(apr_pool_t *pool)
457299742Sdim{
458299742Sdim  svn_repos__dumpfile_headers_t *headers
459299742Sdim    = apr_array_make(pool, 5, sizeof(svn_repos__dumpfile_header_entry_t));
460299742Sdim
461299742Sdim  return headers;
462299742Sdim}
463299742Sdim
464299742Sdimvoid
465299742Sdimsvn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers,
466299742Sdim                                const char *key,
467299742Sdim                                const char *val)
468299742Sdim{
469299742Sdim  svn_repos__dumpfile_header_entry_t *h
470299742Sdim    = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
471299742Sdim
472299742Sdim  h->key = apr_pstrdup(headers->pool, key);
473299742Sdim  h->val = apr_pstrdup(headers->pool, val);
474299742Sdim}
475299742Sdim
476299742Sdimvoid
477299742Sdimsvn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers,
478299742Sdim                                 const char *key,
479299742Sdim                                 const char *val_fmt,
480299742Sdim                                 ...)
481299742Sdim{
482299742Sdim  va_list ap;
483299742Sdim  svn_repos__dumpfile_header_entry_t *h
484299742Sdim    = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t);
485299742Sdim
486299742Sdim  h->key = apr_pstrdup(headers->pool, key);
487299742Sdim  va_start(ap, val_fmt);
488299742Sdim  h->val = apr_pvsprintf(headers->pool, val_fmt, ap);
489299742Sdim  va_end(ap);
490299742Sdim}
491299742Sdim
492299742Sdimsvn_error_t *
493299742Sdimsvn_repos__dump_headers(svn_stream_t *stream,
494299742Sdim                        svn_repos__dumpfile_headers_t *headers,
495299742Sdim                        apr_pool_t *scratch_pool)
496299742Sdim{
497299742Sdim  int i;
498299742Sdim
499299742Sdim  for (i = 0; i < headers->nelts; i++)
500299742Sdim    {
501299742Sdim      svn_repos__dumpfile_header_entry_t *h
502299742Sdim        = &APR_ARRAY_IDX(headers, i, svn_repos__dumpfile_header_entry_t);
503299742Sdim
504299742Sdim      SVN_ERR(svn_stream_printf(stream, scratch_pool,
505299742Sdim                                "%s: %s\n", h->key, h->val));
506299742Sdim    }
507299742Sdim
508299742Sdim  /* End of headers */
509299742Sdim  SVN_ERR(svn_stream_puts(stream, "\n"));
510299742Sdim
511299742Sdim  return SVN_NO_ERROR;
512299742Sdim}
513299742Sdim
514299742Sdimsvn_error_t *
515299742Sdimsvn_repos__dump_revision_record(svn_stream_t *dump_stream,
516299742Sdim                                svn_revnum_t revision,
517299742Sdim                                apr_hash_t *extra_headers,
518299742Sdim                                apr_hash_t *revprops,
519299742Sdim                                svn_boolean_t props_section_always,
520299742Sdim                                apr_pool_t *scratch_pool)
521299742Sdim{
522299742Sdim  svn_stringbuf_t *propstring = NULL;
523299742Sdim  apr_hash_t *headers;
524299742Sdim
525299742Sdim  if (extra_headers)
526299742Sdim    headers = apr_hash_copy(scratch_pool, extra_headers);
527299742Sdim  else
528299742Sdim    headers = apr_hash_make(scratch_pool);
529299742Sdim
530299742Sdim  /* ### someday write a revision-content-checksum */
531299742Sdim
532299742Sdim  svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER,
533299742Sdim                apr_psprintf(scratch_pool, "%ld", revision));
534299742Sdim
535299742Sdim  if (apr_hash_count(revprops) || props_section_always)
536299742Sdim    {
537299742Sdim      svn_stream_t *propstream;
538299742Sdim
539299742Sdim      propstring = svn_stringbuf_create_empty(scratch_pool);
540299742Sdim      propstream = svn_stream_from_stringbuf(propstring, scratch_pool);
541299742Sdim      SVN_ERR(svn_hash_write2(revprops, propstream, "PROPS-END", scratch_pool));
542299742Sdim      SVN_ERR(svn_stream_close(propstream));
543299742Sdim
544299742Sdim      svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
545299742Sdim                    apr_psprintf(scratch_pool,
546299742Sdim                                 "%" APR_SIZE_T_FMT, propstring->len));
547299742Sdim    }
548299742Sdim
549299742Sdim  /* Write out a regular Content-length header for the benefit of
550299742Sdim     non-Subversion RFC-822 parsers. */
551299742Sdim  svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
552299742Sdim                apr_psprintf(scratch_pool,
553299742Sdim                             "%" APR_SIZE_T_FMT, propstring->len));
554299742Sdim  SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool));
555299742Sdim
556299742Sdim  /* End of headers */
557299742Sdim  SVN_ERR(svn_stream_puts(dump_stream, "\n"));
558299742Sdim
559299742Sdim  /* Property data. */
560299742Sdim  if (propstring)
561299742Sdim    {
562299742Sdim      SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len));
563299742Sdim    }
564299742Sdim
565299742Sdim  /* put an end to revision */
566299742Sdim  SVN_ERR(svn_stream_puts(dump_stream, "\n"));
567299742Sdim
568299742Sdim  return SVN_NO_ERROR;
569299742Sdim}
570299742Sdim
571299742Sdimsvn_error_t *
572299742Sdimsvn_repos__dump_node_record(svn_stream_t *dump_stream,
573299742Sdim                            svn_repos__dumpfile_headers_t *headers,
574299742Sdim                            svn_stringbuf_t *props_str,
575299742Sdim                            svn_boolean_t has_text,
576299742Sdim                            svn_filesize_t text_content_length,
577299742Sdim                            svn_boolean_t content_length_always,
578299742Sdim                            apr_pool_t *scratch_pool)
579299742Sdim{
580299742Sdim  svn_filesize_t content_length = 0;
581299742Sdim
582299742Sdim  /* add content-length headers */
583299742Sdim  if (props_str)
584299742Sdim    {
585299742Sdim      svn_repos__dumpfile_header_pushf(
586299742Sdim        headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH,
587299742Sdim        "%" APR_SIZE_T_FMT, props_str->len);
588299742Sdim      content_length += props_str->len;
589299742Sdim    }
590299742Sdim  if (has_text)
591299742Sdim    {
592299742Sdim      svn_repos__dumpfile_header_pushf(
593299742Sdim        headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH,
594299742Sdim        "%" SVN_FILESIZE_T_FMT, text_content_length);
595299742Sdim      content_length += text_content_length;
596299742Sdim    }
597299742Sdim  if (content_length_always || props_str || has_text)
598299742Sdim    {
599299742Sdim      svn_repos__dumpfile_header_pushf(
600299742Sdim        headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH,
601299742Sdim        "%" SVN_FILESIZE_T_FMT, content_length);
602299742Sdim    }
603299742Sdim
604299742Sdim  /* write the headers */
605299742Sdim  SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool));
606299742Sdim
607299742Sdim  /* write the props */
608299742Sdim  if (props_str)
609299742Sdim    {
610299742Sdim      SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len));
611299742Sdim    }
612299742Sdim  return SVN_NO_ERROR;
613299742Sdim}
614299742Sdim
615299742Sdim/*----------------------------------------------------------------------*/
616299742Sdim
617251881Speter/** An editor which dumps node-data in 'dumpfile format' to a file. **/
618251881Speter
619251881Speter/* Look, mom!  No file batons! */
620251881Speter
621251881Speterstruct edit_baton
622251881Speter{
623251881Speter  /* The relpath which implicitly prepends all full paths coming into
624251881Speter     this editor.  This will almost always be "".  */
625251881Speter  const char *path;
626251881Speter
627251881Speter  /* The stream to dump to. */
628251881Speter  svn_stream_t *stream;
629251881Speter
630251881Speter  /* Send feedback here, if non-NULL */
631251881Speter  svn_repos_notify_func_t notify_func;
632251881Speter  void *notify_baton;
633251881Speter
634251881Speter  /* The fs revision root, so we can read the contents of paths. */
635251881Speter  svn_fs_root_t *fs_root;
636251881Speter  svn_revnum_t current_rev;
637251881Speter
638251881Speter  /* The fs, so we can grab historic information if needed. */
639251881Speter  svn_fs_t *fs;
640251881Speter
641251881Speter  /* True if dumped nodes should output deltas instead of full text. */
642251881Speter  svn_boolean_t use_deltas;
643251881Speter
644251881Speter  /* True if this "dump" is in fact a verify. */
645251881Speter  svn_boolean_t verify;
646251881Speter
647299742Sdim  /* True if checking UCS normalization during a verify. */
648299742Sdim  svn_boolean_t check_normalization;
649299742Sdim
650251881Speter  /* The first revision dumped in this dumpstream. */
651251881Speter  svn_revnum_t oldest_dumped_rev;
652251881Speter
653251881Speter  /* If not NULL, set to true if any references to revisions older than
654251881Speter     OLDEST_DUMPED_REV were found in the dumpstream. */
655251881Speter  svn_boolean_t *found_old_reference;
656251881Speter
657251881Speter  /* If not NULL, set to true if any mergeinfo was dumped which contains
658251881Speter     revisions older than OLDEST_DUMPED_REV. */
659251881Speter  svn_boolean_t *found_old_mergeinfo;
660251881Speter
661299742Sdim  /* Structure allows us to verify the paths currently being dumped.
662299742Sdim     If NULL, validity checks are being skipped. */
663299742Sdim  path_tracker_t *path_tracker;
664251881Speter};
665251881Speter
666251881Speterstruct dir_baton
667251881Speter{
668251881Speter  struct edit_baton *edit_baton;
669251881Speter
670251881Speter  /* has this directory been written to the output stream? */
671251881Speter  svn_boolean_t written_out;
672251881Speter
673251881Speter  /* the repository relpath associated with this directory */
674251881Speter  const char *path;
675251881Speter
676251881Speter  /* The comparison repository relpath and revision of this directory.
677251881Speter     If both of these are valid, use them as a source against which to
678251881Speter     compare the directory instead of the default comparison source of
679251881Speter     PATH in the previous revision. */
680251881Speter  const char *cmp_path;
681251881Speter  svn_revnum_t cmp_rev;
682251881Speter
683251881Speter  /* hash of paths that need to be deleted, though some -might- be
684251881Speter     replaced.  maps const char * paths to this dir_baton.  (they're
685251881Speter     full paths, because that's what the editor driver gives us.  but
686251881Speter     really, they're all within this directory.) */
687251881Speter  apr_hash_t *deleted_entries;
688251881Speter
689299742Sdim  /* A flag indicating that new entries have been added to this
690299742Sdim     directory in this revision. Used to optimize detection of UCS
691299742Sdim     representation collisions; we will only check for that in
692299742Sdim     revisions where new names appear in the directory. */
693299742Sdim  svn_boolean_t check_name_collision;
694299742Sdim
695251881Speter  /* pool to be used for deleting the hash items */
696251881Speter  apr_pool_t *pool;
697251881Speter};
698251881Speter
699251881Speter
700251881Speter/* Make a directory baton to represent the directory was path
701251881Speter   (relative to EDIT_BATON's path) is PATH.
702251881Speter
703251881Speter   CMP_PATH/CMP_REV are the path/revision against which this directory
704251881Speter   should be compared for changes.  If either is omitted (NULL for the
705251881Speter   path, SVN_INVALID_REVNUM for the rev), just compare this directory
706251881Speter   PATH against itself in the previous revision.
707251881Speter
708299742Sdim   PB is the directory baton of this directory's parent,
709299742Sdim   or NULL if this is the top-level directory of the edit.
710299742Sdim
711251881Speter   Perform all allocations in POOL.  */
712251881Speterstatic struct dir_baton *
713251881Spetermake_dir_baton(const char *path,
714251881Speter               const char *cmp_path,
715251881Speter               svn_revnum_t cmp_rev,
716251881Speter               void *edit_baton,
717299742Sdim               struct dir_baton *pb,
718251881Speter               apr_pool_t *pool)
719251881Speter{
720251881Speter  struct edit_baton *eb = edit_baton;
721251881Speter  struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
722251881Speter  const char *full_path;
723251881Speter
724251881Speter  /* A path relative to nothing?  I don't think so. */
725251881Speter  SVN_ERR_ASSERT_NO_RETURN(!path || pb);
726251881Speter
727251881Speter  /* Construct the full path of this node. */
728251881Speter  if (pb)
729251881Speter    full_path = svn_relpath_join(eb->path, path, pool);
730251881Speter  else
731251881Speter    full_path = apr_pstrdup(pool, eb->path);
732251881Speter
733251881Speter  /* Remove leading slashes from copyfrom paths. */
734251881Speter  if (cmp_path)
735251881Speter    cmp_path = svn_relpath_canonicalize(cmp_path, pool);
736251881Speter
737251881Speter  new_db->edit_baton = eb;
738251881Speter  new_db->path = full_path;
739251881Speter  new_db->cmp_path = cmp_path;
740251881Speter  new_db->cmp_rev = cmp_rev;
741251881Speter  new_db->written_out = FALSE;
742251881Speter  new_db->deleted_entries = apr_hash_make(pool);
743299742Sdim  new_db->check_name_collision = FALSE;
744251881Speter  new_db->pool = pool;
745251881Speter
746251881Speter  return new_db;
747251881Speter}
748251881Speter
749299742Sdimstatic svn_error_t *
750299742Sdimfetch_kind_func(svn_node_kind_t *kind,
751299742Sdim                void *baton,
752299742Sdim                const char *path,
753299742Sdim                svn_revnum_t base_revision,
754299742Sdim                apr_pool_t *scratch_pool);
755251881Speter
756299742Sdim/* Return an error when PATH in REVISION does not exist or is of a
757299742Sdim   different kind than EXPECTED_KIND.  If the latter is svn_node_unknown,
758299742Sdim   skip that check.  Use EB for context information.  If REVISION is the
759299742Sdim   current revision, use EB's path tracker to follow renames, deletions,
760299742Sdim   etc.
761299742Sdim
762299742Sdim   Use SCRATCH_POOL for temporary allocations.
763299742Sdim   No-op if EB's path tracker has not been initialized.
764299742Sdim */
765299742Sdimstatic svn_error_t *
766299742Sdimnode_must_exist(struct edit_baton *eb,
767299742Sdim                const char *path,
768299742Sdim                svn_revnum_t revision,
769299742Sdim                svn_node_kind_t expected_kind,
770299742Sdim                apr_pool_t *scratch_pool)
771299742Sdim{
772299742Sdim  svn_node_kind_t kind = svn_node_none;
773299742Sdim
774299742Sdim  /* in case the caller is trying something stupid ... */
775299742Sdim  if (eb->path_tracker == NULL)
776299742Sdim    return SVN_NO_ERROR;
777299742Sdim
778299742Sdim  /* paths pertaining to the revision currently being processed must
779299742Sdim     be translated / checked using our path tracker. */
780299742Sdim  if (revision == eb->path_tracker->revision)
781299742Sdim    tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
782299742Sdim
783299742Sdim  /* determine the node type (default: no such node) */
784299742Sdim  if (path)
785299742Sdim    SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
786299742Sdim
787299742Sdim  /* check results */
788299742Sdim  if (kind == svn_node_none)
789299742Sdim    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
790299742Sdim                             _("Path '%s' not found in r%ld."),
791299742Sdim                             path, revision);
792299742Sdim
793299742Sdim  if (expected_kind != kind && expected_kind != svn_node_unknown)
794299742Sdim    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
795299742Sdim                             _("Unexpected node kind %d for '%s' at r%ld. "
796299742Sdim                               "Expected kind was %d."),
797299742Sdim                             kind, path, revision, expected_kind);
798299742Sdim
799299742Sdim  return SVN_NO_ERROR;
800299742Sdim}
801299742Sdim
802299742Sdim/* Return an error when PATH exists in REVISION.  Use EB for context
803299742Sdim   information.  If REVISION is the current revision, use EB's path
804299742Sdim   tracker to follow renames, deletions, etc.
805299742Sdim
806299742Sdim   Use SCRATCH_POOL for temporary allocations.
807299742Sdim   No-op if EB's path tracker has not been initialized.
808299742Sdim */
809299742Sdimstatic svn_error_t *
810299742Sdimnode_must_not_exist(struct edit_baton *eb,
811299742Sdim                    const char *path,
812299742Sdim                    svn_revnum_t revision,
813299742Sdim                    apr_pool_t *scratch_pool)
814299742Sdim{
815299742Sdim  svn_node_kind_t kind = svn_node_none;
816299742Sdim
817299742Sdim  /* in case the caller is trying something stupid ... */
818299742Sdim  if (eb->path_tracker == NULL)
819299742Sdim    return SVN_NO_ERROR;
820299742Sdim
821299742Sdim  /* paths pertaining to the revision currently being processed must
822299742Sdim     be translated / checked using our path tracker. */
823299742Sdim  if (revision == eb->path_tracker->revision)
824299742Sdim    tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool);
825299742Sdim
826299742Sdim  /* determine the node type (default: no such node) */
827299742Sdim  if (path)
828299742Sdim    SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool));
829299742Sdim
830299742Sdim  /* check results */
831299742Sdim  if (kind != svn_node_none)
832299742Sdim    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
833299742Sdim                             _("Path '%s' exists in r%ld."),
834299742Sdim                             path, revision);
835299742Sdim
836299742Sdim  return SVN_NO_ERROR;
837299742Sdim}
838299742Sdim
839269847Speter/* If the mergeinfo in MERGEINFO_STR refers to any revisions older than
840269847Speter * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE,
841269847Speter * otherwise leave *FOUND_OLD_MERGEINFO unchanged.
842269847Speter */
843269847Speterstatic svn_error_t *
844269847Speterverify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo,
845269847Speter                           const char *mergeinfo_str,
846269847Speter                           svn_revnum_t oldest_dumped_rev,
847269847Speter                           svn_repos_notify_func_t notify_func,
848269847Speter                           void *notify_baton,
849269847Speter                           apr_pool_t *pool)
850269847Speter{
851269847Speter  svn_mergeinfo_t mergeinfo, old_mergeinfo;
852269847Speter
853269847Speter  SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool));
854269847Speter  SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
855269847Speter            &old_mergeinfo, mergeinfo,
856269847Speter            oldest_dumped_rev - 1, 0,
857269847Speter            TRUE, pool, pool));
858269847Speter
859269847Speter  if (apr_hash_count(old_mergeinfo))
860269847Speter    {
861299742Sdim      notify_warning(pool, notify_func, notify_baton,
862299742Sdim                     svn_repos_notify_warning_found_old_mergeinfo,
863299742Sdim                     _("Mergeinfo referencing revision(s) prior "
864299742Sdim                       "to the oldest dumped revision (r%ld). "
865299742Sdim                       "Loading this dump may result in invalid "
866299742Sdim                       "mergeinfo."),
867299742Sdim                     oldest_dumped_rev);
868269847Speter
869269847Speter      if (found_old_mergeinfo)
870269847Speter        *found_old_mergeinfo = TRUE;
871269847Speter    }
872269847Speter
873269847Speter  return SVN_NO_ERROR;
874269847Speter}
875269847Speter
876299742Sdim/* Unique string pointers used by verify_mergeinfo_normalization()
877299742Sdim   and check_name_collision() */
878299742Sdimstatic const char normalized_unique[] = "normalized_unique";
879299742Sdimstatic const char normalized_collision[] = "normalized_collision";
880269847Speter
881299742Sdim
882299742Sdim/* Baton for extract_mergeinfo_paths */
883299742Sdimstruct extract_mergeinfo_paths_baton
884299742Sdim{
885299742Sdim  apr_hash_t *result;
886299742Sdim  svn_boolean_t normalize;
887299742Sdim  svn_membuf_t buffer;
888299742Sdim};
889299742Sdim
890299742Sdim/* Hash iterator that uniquifies all keys into a single hash table,
891299742Sdim   optionally normalizing them first. */
892299742Sdimstatic svn_error_t *
893299742Sdimextract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
894299742Sdim                         void *val, apr_pool_t *iterpool)
895299742Sdim{
896299742Sdim  struct extract_mergeinfo_paths_baton *const xb = baton;
897299742Sdim  if (xb->normalize)
898299742Sdim    {
899299742Sdim      const char *normkey;
900299742Sdim      SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer));
901299742Sdim      svn_hash_sets(xb->result,
902299742Sdim                    apr_pstrdup(xb->buffer.pool, normkey),
903299742Sdim                    normalized_unique);
904299742Sdim    }
905299742Sdim  else
906299742Sdim    apr_hash_set(xb->result,
907299742Sdim                 apr_pmemdup(xb->buffer.pool, key, klen + 1), klen,
908299742Sdim                 normalized_unique);
909299742Sdim  return SVN_NO_ERROR;
910299742Sdim}
911299742Sdim
912299742Sdim/* Baton for filter_mergeinfo_paths */
913299742Sdimstruct filter_mergeinfo_paths_baton
914299742Sdim{
915299742Sdim  apr_hash_t *paths;
916299742Sdim};
917299742Sdim
918299742Sdim/* Compare two sets of denormalized paths from mergeinfo entries,
919299742Sdim   removing duplicates. */
920299742Sdimstatic svn_error_t *
921299742Sdimfilter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen,
922299742Sdim                       void *val, apr_pool_t *iterpool)
923299742Sdim{
924299742Sdim  struct filter_mergeinfo_paths_baton *const fb = baton;
925299742Sdim
926299742Sdim  if (apr_hash_get(fb->paths, key, klen))
927299742Sdim    apr_hash_set(fb->paths, key, klen, NULL);
928299742Sdim
929299742Sdim  return SVN_NO_ERROR;
930299742Sdim}
931299742Sdim
932299742Sdim/* Baton used by the check_mergeinfo_normalization hash iterator. */
933299742Sdimstruct verify_mergeinfo_normalization_baton
934299742Sdim{
935299742Sdim  const char* path;
936299742Sdim  apr_hash_t *normalized_paths;
937299742Sdim  svn_membuf_t buffer;
938299742Sdim  svn_repos_notify_func_t notify_func;
939299742Sdim  void *notify_baton;
940299742Sdim};
941299742Sdim
942299742Sdim/* Hash iterator that verifies normalization and collision of paths in
943299742Sdim   an svn:mergeinfo property. */
944299742Sdimstatic svn_error_t *
945299742Sdimverify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen,
946299742Sdim                               void *val, apr_pool_t *iterpool)
947299742Sdim{
948299742Sdim  struct verify_mergeinfo_normalization_baton *const vb = baton;
949299742Sdim
950299742Sdim  const char *const path = key;
951299742Sdim  const char *normpath;
952299742Sdim  const char *found;
953299742Sdim
954299742Sdim  SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer));
955299742Sdim  found = svn_hash_gets(vb->normalized_paths, normpath);
956299742Sdim  if (!found)
957299742Sdim      svn_hash_sets(vb->normalized_paths,
958299742Sdim                    apr_pstrdup(vb->buffer.pool, normpath),
959299742Sdim                    normalized_unique);
960299742Sdim  else if (found == normalized_collision)
961299742Sdim    /* Skip already reported collision */;
962299742Sdim  else
963299742Sdim    {
964299742Sdim      /* Report path collision in mergeinfo */
965299742Sdim      svn_hash_sets(vb->normalized_paths,
966299742Sdim                    apr_pstrdup(vb->buffer.pool, normpath),
967299742Sdim                    normalized_collision);
968299742Sdim
969299742Sdim      notify_warning(iterpool, vb->notify_func, vb->notify_baton,
970299742Sdim                     svn_repos_notify_warning_mergeinfo_collision,
971299742Sdim                     _("Duplicate representation of path '%s'"
972299742Sdim                       " in %s property of '%s'"),
973299742Sdim                     normpath, SVN_PROP_MERGEINFO, vb->path);
974299742Sdim    }
975299742Sdim  return SVN_NO_ERROR;
976299742Sdim}
977299742Sdim
978299742Sdim/* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the
979299742Sdim   svn:mergeinfo property value being set; OLD_MERGEINFO is the
980299742Sdim   previous property value, which may be NULL. Only the paths that
981299742Sdim   were added in are checked, including collision checks. This
982299742Sdim   minimizes the number of notifications we generate for a given
983299742Sdim   mergeinfo property. */
984299742Sdimstatic svn_error_t *
985299742Sdimcheck_mergeinfo_normalization(const char *path,
986299742Sdim                              const char *new_mergeinfo,
987299742Sdim                              const char *old_mergeinfo,
988299742Sdim                              svn_repos_notify_func_t notify_func,
989299742Sdim                              void *notify_baton,
990299742Sdim                              apr_pool_t *pool)
991299742Sdim{
992299742Sdim  svn_mergeinfo_t mergeinfo;
993299742Sdim  apr_hash_t *normalized_paths;
994299742Sdim  apr_hash_t *added_paths;
995299742Sdim  struct extract_mergeinfo_paths_baton extract_baton;
996299742Sdim  struct verify_mergeinfo_normalization_baton verify_baton;
997299742Sdim
998299742Sdim  SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool));
999299742Sdim
1000299742Sdim  extract_baton.result = apr_hash_make(pool);
1001299742Sdim  extract_baton.normalize = FALSE;
1002299742Sdim  svn_membuf__create(&extract_baton.buffer, 0, pool);
1003299742Sdim  SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo,
1004299742Sdim                            extract_mergeinfo_paths,
1005299742Sdim                            &extract_baton, pool));
1006299742Sdim  added_paths = extract_baton.result;
1007299742Sdim
1008299742Sdim  if (old_mergeinfo)
1009299742Sdim    {
1010299742Sdim      struct filter_mergeinfo_paths_baton filter_baton;
1011299742Sdim      svn_mergeinfo_t oldinfo;
1012299742Sdim
1013299742Sdim      extract_baton.result = apr_hash_make(pool);
1014299742Sdim      extract_baton.normalize = TRUE;
1015299742Sdim      SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool));
1016299742Sdim      SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1017299742Sdim                                extract_mergeinfo_paths,
1018299742Sdim                                &extract_baton, pool));
1019299742Sdim      normalized_paths = extract_baton.result;
1020299742Sdim
1021299742Sdim      filter_baton.paths = added_paths;
1022299742Sdim      SVN_ERR(svn_iter_apr_hash(NULL, oldinfo,
1023299742Sdim                                filter_mergeinfo_paths,
1024299742Sdim                                &filter_baton, pool));
1025299742Sdim    }
1026299742Sdim  else
1027299742Sdim      normalized_paths = apr_hash_make(pool);
1028299742Sdim
1029299742Sdim  verify_baton.path = path;
1030299742Sdim  verify_baton.normalized_paths = normalized_paths;
1031299742Sdim  verify_baton.buffer = extract_baton.buffer;
1032299742Sdim  verify_baton.notify_func = notify_func;
1033299742Sdim  verify_baton.notify_baton = notify_baton;
1034299742Sdim  SVN_ERR(svn_iter_apr_hash(NULL, added_paths,
1035299742Sdim                            verify_mergeinfo_normalization,
1036299742Sdim                            &verify_baton, pool));
1037299742Sdim
1038299742Sdim  return SVN_NO_ERROR;
1039299742Sdim}
1040299742Sdim
1041299742Sdim
1042299742Sdim/* A special case of dump_node(), for a delete record.
1043299742Sdim *
1044299742Sdim * The only thing special about this version is it only writes one blank
1045299742Sdim * line, not two, after the headers. Why? Historical precedent for the
1046299742Sdim * case where a delete record is used as part of a (delete + add-with-history)
1047299742Sdim * in implementing a replacement.
1048299742Sdim *
1049299742Sdim * Also it doesn't do a path-tracker check.
1050299742Sdim */
1051299742Sdimstatic svn_error_t *
1052299742Sdimdump_node_delete(svn_stream_t *stream,
1053299742Sdim                 const char *node_relpath,
1054299742Sdim                 apr_pool_t *pool)
1055299742Sdim{
1056299742Sdim  svn_repos__dumpfile_headers_t *headers
1057299742Sdim    = svn_repos__dumpfile_headers_create(pool);
1058299742Sdim
1059299742Sdim  /* Node-path: ... */
1060299742Sdim  svn_repos__dumpfile_header_push(
1061299742Sdim    headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
1062299742Sdim
1063299742Sdim  /* Node-action: delete */
1064299742Sdim  svn_repos__dumpfile_header_push(
1065299742Sdim    headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1066299742Sdim
1067299742Sdim  SVN_ERR(svn_repos__dump_headers(stream, headers, pool));
1068299742Sdim  return SVN_NO_ERROR;
1069299742Sdim}
1070299742Sdim
1071251881Speter/* This helper is the main "meat" of the editor -- it does all the
1072251881Speter   work of writing a node record.
1073251881Speter
1074251881Speter   Write out a node record for PATH of type KIND under EB->FS_ROOT.
1075251881Speter   ACTION describes what is happening to the node (see enum svn_node_action).
1076299742Sdim   Write record to writable EB->STREAM.
1077251881Speter
1078251881Speter   If the node was itself copied, IS_COPY is TRUE and the
1079251881Speter   path/revision of the copy source are in CMP_PATH/CMP_REV.  If
1080251881Speter   IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
1081251881Speter   of a copied subtree.
1082251881Speter  */
1083251881Speterstatic svn_error_t *
1084251881Speterdump_node(struct edit_baton *eb,
1085251881Speter          const char *path,
1086251881Speter          svn_node_kind_t kind,
1087251881Speter          enum svn_node_action action,
1088251881Speter          svn_boolean_t is_copy,
1089251881Speter          const char *cmp_path,
1090251881Speter          svn_revnum_t cmp_rev,
1091251881Speter          apr_pool_t *pool)
1092251881Speter{
1093251881Speter  svn_stringbuf_t *propstring;
1094251881Speter  apr_size_t len;
1095251881Speter  svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
1096251881Speter  const char *compare_path = path;
1097251881Speter  svn_revnum_t compare_rev = eb->current_rev - 1;
1098251881Speter  svn_fs_root_t *compare_root = NULL;
1099251881Speter  apr_file_t *delta_file = NULL;
1100299742Sdim  svn_repos__dumpfile_headers_t *headers
1101299742Sdim    = svn_repos__dumpfile_headers_create(pool);
1102299742Sdim  svn_filesize_t textlen;
1103251881Speter
1104251881Speter  /* Maybe validate the path. */
1105251881Speter  if (eb->verify || eb->notify_func)
1106251881Speter    {
1107251881Speter      svn_error_t *err = svn_fs__path_valid(path, pool);
1108251881Speter
1109251881Speter      if (err)
1110251881Speter        {
1111251881Speter          if (eb->notify_func)
1112251881Speter            {
1113251881Speter              char errbuf[512]; /* ### svn_strerror() magic number  */
1114251881Speter
1115299742Sdim              notify_warning(pool, eb->notify_func, eb->notify_baton,
1116299742Sdim                             svn_repos_notify_warning_invalid_fspath,
1117299742Sdim                             _("E%06d: While validating fspath '%s': %s"),
1118299742Sdim                             err->apr_err, path,
1119299742Sdim                             svn_err_best_message(err, errbuf, sizeof(errbuf)));
1120251881Speter            }
1121251881Speter
1122251881Speter          /* Return the error in addition to notifying about it. */
1123251881Speter          if (eb->verify)
1124251881Speter            return svn_error_trace(err);
1125251881Speter          else
1126251881Speter            svn_error_clear(err);
1127251881Speter        }
1128251881Speter    }
1129251881Speter
1130251881Speter  /* Write out metadata headers for this file node. */
1131299742Sdim  svn_repos__dumpfile_header_push(
1132299742Sdim    headers, SVN_REPOS_DUMPFILE_NODE_PATH, path);
1133251881Speter  if (kind == svn_node_file)
1134299742Sdim    svn_repos__dumpfile_header_push(
1135299742Sdim      headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
1136251881Speter  else if (kind == svn_node_dir)
1137299742Sdim    svn_repos__dumpfile_header_push(
1138299742Sdim      headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
1139251881Speter
1140251881Speter  /* Remove leading slashes from copyfrom paths. */
1141251881Speter  if (cmp_path)
1142251881Speter    cmp_path = svn_relpath_canonicalize(cmp_path, pool);
1143251881Speter
1144251881Speter  /* Validate the comparison path/rev. */
1145251881Speter  if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
1146251881Speter    {
1147251881Speter      compare_path = cmp_path;
1148251881Speter      compare_rev = cmp_rev;
1149251881Speter    }
1150251881Speter
1151299742Sdim  switch (action)
1152251881Speter    {
1153299742Sdim    case svn_node_action_change:
1154299742Sdim      if (eb->path_tracker)
1155299742Sdim        SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1156299742Sdim                  apr_psprintf(pool, _("Change invalid path '%s' in r%ld"),
1157299742Sdim                               path, eb->current_rev));
1158251881Speter
1159299742Sdim      svn_repos__dumpfile_header_push(
1160299742Sdim        headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
1161299742Sdim
1162251881Speter      /* either the text or props changed, or possibly both. */
1163251881Speter      SVN_ERR(svn_fs_revision_root(&compare_root,
1164251881Speter                                   svn_fs_root_fs(eb->fs_root),
1165251881Speter                                   compare_rev, pool));
1166251881Speter
1167251881Speter      SVN_ERR(svn_fs_props_changed(&must_dump_props,
1168251881Speter                                   compare_root, compare_path,
1169251881Speter                                   eb->fs_root, path, pool));
1170251881Speter      if (kind == svn_node_file)
1171251881Speter        SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1172251881Speter                                        compare_root, compare_path,
1173251881Speter                                        eb->fs_root, path, pool));
1174299742Sdim      break;
1175299742Sdim
1176299742Sdim    case svn_node_action_delete:
1177299742Sdim      if (eb->path_tracker)
1178299742Sdim        {
1179299742Sdim          SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool),
1180299742Sdim                    apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"),
1181299742Sdim                                 path, eb->current_rev));
1182299742Sdim          tracker_path_delete(eb->path_tracker, path);
1183299742Sdim        }
1184299742Sdim
1185299742Sdim      svn_repos__dumpfile_header_push(
1186299742Sdim        headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
1187299742Sdim
1188299742Sdim      /* we can leave this routine quietly now, don't need to dump
1189299742Sdim         any content. */
1190299742Sdim      must_dump_text = FALSE;
1191299742Sdim      must_dump_props = FALSE;
1192299742Sdim      break;
1193299742Sdim
1194299742Sdim    case svn_node_action_replace:
1195299742Sdim      if (eb->path_tracker)
1196299742Sdim        SVN_ERR_W(node_must_exist(eb, path, eb->current_rev,
1197299742Sdim                                  svn_node_unknown, pool),
1198299742Sdim                  apr_psprintf(pool,
1199299742Sdim                               _("Replacing non-existent path '%s' in r%ld"),
1200299742Sdim                               path, eb->current_rev));
1201299742Sdim
1202251881Speter      if (! is_copy)
1203251881Speter        {
1204299742Sdim          if (eb->path_tracker)
1205299742Sdim            tracker_path_replace(eb->path_tracker, path);
1206299742Sdim
1207251881Speter          /* a simple delete+add, implied by a single 'replace' action. */
1208299742Sdim          svn_repos__dumpfile_header_push(
1209299742Sdim            headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
1210251881Speter
1211251881Speter          /* definitely need to dump all content for a replace. */
1212251881Speter          if (kind == svn_node_file)
1213251881Speter            must_dump_text = TRUE;
1214251881Speter          must_dump_props = TRUE;
1215299742Sdim          break;
1216251881Speter        }
1217251881Speter      else
1218251881Speter        {
1219251881Speter          /* more complex:  delete original, then add-with-history.  */
1220299742Sdim          /* ### Why not write a 'replace' record? Don't know. */
1221251881Speter
1222299742Sdim          if (eb->path_tracker)
1223299742Sdim            {
1224299742Sdim              tracker_path_delete(eb->path_tracker, path);
1225299742Sdim            }
1226251881Speter
1227299742Sdim          /* ### Unusually, we end this 'delete' node record with only a single
1228299742Sdim                 blank line after the header block -- no extra blank line. */
1229299742Sdim          SVN_ERR(dump_node_delete(eb->stream, path, pool));
1230251881Speter
1231299742Sdim          /* The remaining action is a non-replacing add-with-history */
1232299742Sdim          /* action = svn_node_action_add; */
1233251881Speter        }
1234299742Sdim      /* FALL THROUGH to 'add' */
1235251881Speter
1236299742Sdim    case svn_node_action_add:
1237299742Sdim      if (eb->path_tracker)
1238299742Sdim        SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool),
1239299742Sdim                  apr_psprintf(pool,
1240299742Sdim                               _("Adding already existing path '%s' in r%ld"),
1241299742Sdim                               path, eb->current_rev));
1242251881Speter
1243299742Sdim      svn_repos__dumpfile_header_push(
1244299742Sdim        headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
1245299742Sdim
1246251881Speter      if (! is_copy)
1247251881Speter        {
1248299742Sdim          if (eb->path_tracker)
1249299742Sdim            tracker_path_add(eb->path_tracker, path);
1250299742Sdim
1251251881Speter          /* Dump all contents for a simple 'add'. */
1252251881Speter          if (kind == svn_node_file)
1253251881Speter            must_dump_text = TRUE;
1254251881Speter          must_dump_props = TRUE;
1255251881Speter        }
1256251881Speter      else
1257251881Speter        {
1258299742Sdim          if (eb->path_tracker)
1259299742Sdim            {
1260299742Sdim              SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev,
1261299742Sdim                                        kind, pool),
1262299742Sdim                        apr_psprintf(pool,
1263299742Sdim                                     _("Copying from invalid path to "
1264299742Sdim                                       "'%s' in r%ld"),
1265299742Sdim                                     path, eb->current_rev));
1266299742Sdim              tracker_path_copy(eb->path_tracker, path, compare_path,
1267299742Sdim                                compare_rev);
1268299742Sdim            }
1269299742Sdim
1270251881Speter          if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
1271251881Speter              && eb->notify_func)
1272251881Speter            {
1273299742Sdim              notify_warning(pool, eb->notify_func, eb->notify_baton,
1274299742Sdim                             svn_repos_notify_warning_found_old_reference,
1275299742Sdim                             _("Referencing data in revision %ld,"
1276299742Sdim                               " which is older than the oldest"
1277299742Sdim                               " dumped revision (r%ld).  Loading this dump"
1278299742Sdim                               " into an empty repository"
1279299742Sdim                               " will fail."),
1280299742Sdim                             cmp_rev, eb->oldest_dumped_rev);
1281251881Speter              if (eb->found_old_reference)
1282251881Speter                *eb->found_old_reference = TRUE;
1283251881Speter            }
1284251881Speter
1285299742Sdim          svn_repos__dumpfile_header_pushf(
1286299742Sdim            headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev);
1287299742Sdim          svn_repos__dumpfile_header_push(
1288299742Sdim            headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path);
1289251881Speter
1290251881Speter          SVN_ERR(svn_fs_revision_root(&compare_root,
1291251881Speter                                       svn_fs_root_fs(eb->fs_root),
1292251881Speter                                       compare_rev, pool));
1293251881Speter
1294251881Speter          /* Need to decide if the copied node had any extra textual or
1295251881Speter             property mods as well.  */
1296251881Speter          SVN_ERR(svn_fs_props_changed(&must_dump_props,
1297251881Speter                                       compare_root, compare_path,
1298251881Speter                                       eb->fs_root, path, pool));
1299251881Speter          if (kind == svn_node_file)
1300251881Speter            {
1301251881Speter              svn_checksum_t *checksum;
1302251881Speter              const char *hex_digest;
1303251881Speter              SVN_ERR(svn_fs_contents_changed(&must_dump_text,
1304251881Speter                                              compare_root, compare_path,
1305251881Speter                                              eb->fs_root, path, pool));
1306251881Speter
1307251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1308251881Speter                                           compare_root, compare_path,
1309251881Speter                                           FALSE, pool));
1310251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
1311251881Speter              if (hex_digest)
1312299742Sdim                svn_repos__dumpfile_header_push(
1313299742Sdim                  headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest);
1314251881Speter
1315251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1316251881Speter                                           compare_root, compare_path,
1317251881Speter                                           FALSE, pool));
1318251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
1319251881Speter              if (hex_digest)
1320299742Sdim                svn_repos__dumpfile_header_push(
1321299742Sdim                  headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest);
1322251881Speter            }
1323251881Speter        }
1324299742Sdim      break;
1325251881Speter    }
1326251881Speter
1327251881Speter  if ((! must_dump_text) && (! must_dump_props))
1328251881Speter    {
1329251881Speter      /* If we're not supposed to dump text or props, so be it, we can
1330251881Speter         just go home.  However, if either one needs to be dumped,
1331251881Speter         then our dumpstream format demands that at a *minimum*, we
1332251881Speter         see a lone "PROPS-END" as a divider between text and props
1333251881Speter         content within the content-block. */
1334299742Sdim      SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool));
1335299742Sdim      len = 1;
1336299742Sdim      return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */
1337251881Speter    }
1338251881Speter
1339251881Speter  /*** Start prepping content to dump... ***/
1340251881Speter
1341251881Speter  /* If we are supposed to dump properties, write out a property
1342251881Speter     length header and generate a stringbuf that contains those
1343251881Speter     property values here. */
1344251881Speter  if (must_dump_props)
1345251881Speter    {
1346251881Speter      apr_hash_t *prophash, *oldhash = NULL;
1347251881Speter      svn_stream_t *propstream;
1348251881Speter
1349251881Speter      SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
1350251881Speter
1351251881Speter      /* If this is a partial dump, then issue a warning if we dump mergeinfo
1352251881Speter         properties that refer to revisions older than the first revision
1353251881Speter         dumped. */
1354251881Speter      if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
1355251881Speter        {
1356251881Speter          svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1357251881Speter                                                      SVN_PROP_MERGEINFO);
1358251881Speter          if (mergeinfo_str)
1359251881Speter            {
1360269847Speter              /* An error in verifying the mergeinfo must not prevent dumping
1361269847Speter                 the data. Ignore any such error. */
1362269847Speter              svn_error_clear(verify_mergeinfo_revisions(
1363269847Speter                                eb->found_old_mergeinfo,
1364269847Speter                                mergeinfo_str->data, eb->oldest_dumped_rev,
1365269847Speter                                eb->notify_func, eb->notify_baton,
1366269847Speter                                pool));
1367251881Speter            }
1368251881Speter        }
1369251881Speter
1370299742Sdim      /* If we're checking UCS normalization, also parse any changed
1371299742Sdim         mergeinfo and warn about denormalized paths and name
1372299742Sdim         collisions there. */
1373299742Sdim      if (eb->verify && eb->check_normalization && eb->notify_func)
1374299742Sdim        {
1375299742Sdim          /* N.B.: This hash lookup happens only once; the conditions
1376299742Sdim             for verifying historic mergeinfo references and checking
1377299742Sdim             UCS normalization are mutually exclusive. */
1378299742Sdim          svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
1379299742Sdim                                                      SVN_PROP_MERGEINFO);
1380299742Sdim          if (mergeinfo_str)
1381299742Sdim            {
1382299742Sdim              svn_string_t *oldinfo_str = NULL;
1383299742Sdim              if (compare_root)
1384299742Sdim                {
1385299742Sdim                  SVN_ERR(svn_fs_node_proplist(&oldhash,
1386299742Sdim                                               compare_root, compare_path,
1387299742Sdim                                               pool));
1388299742Sdim                  oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO);
1389299742Sdim                }
1390299742Sdim              SVN_ERR(check_mergeinfo_normalization(
1391299742Sdim                          path, mergeinfo_str->data,
1392299742Sdim                          (oldinfo_str ? oldinfo_str->data : NULL),
1393299742Sdim                          eb->notify_func, eb->notify_baton, pool));
1394299742Sdim            }
1395299742Sdim        }
1396299742Sdim
1397251881Speter      if (eb->use_deltas && compare_root)
1398251881Speter        {
1399251881Speter          /* Fetch the old property hash to diff against and output a header
1400251881Speter             saying that our property contents are a delta. */
1401299742Sdim          if (!oldhash)         /* May have been set for normalization check */
1402299742Sdim            SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
1403299742Sdim                                         pool));
1404299742Sdim          svn_repos__dumpfile_header_push(
1405299742Sdim            headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
1406251881Speter        }
1407251881Speter      else
1408251881Speter        oldhash = apr_hash_make(pool);
1409251881Speter      propstring = svn_stringbuf_create_ensure(0, pool);
1410251881Speter      propstream = svn_stream_from_stringbuf(propstring, pool);
1411251881Speter      SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
1412251881Speter                                         "PROPS-END", pool));
1413251881Speter      SVN_ERR(svn_stream_close(propstream));
1414251881Speter    }
1415251881Speter
1416251881Speter  /* If we are supposed to dump text, write out a text length header
1417251881Speter     here, and an MD5 checksum (if available). */
1418251881Speter  if (must_dump_text && (kind == svn_node_file))
1419251881Speter    {
1420251881Speter      svn_checksum_t *checksum;
1421251881Speter      const char *hex_digest;
1422251881Speter
1423251881Speter      if (eb->use_deltas)
1424251881Speter        {
1425251881Speter          /* Compute the text delta now and write it into a temporary
1426251881Speter             file, so that we can find its length.  Output a header
1427251881Speter             saying our text contents are a delta. */
1428251881Speter          SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
1429251881Speter                              compare_path, eb->fs_root, path, pool));
1430299742Sdim          svn_repos__dumpfile_header_push(
1431299742Sdim            headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
1432251881Speter
1433251881Speter          if (compare_root)
1434251881Speter            {
1435251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1436251881Speter                                           compare_root, compare_path,
1437251881Speter                                           FALSE, pool));
1438251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
1439251881Speter              if (hex_digest)
1440299742Sdim                svn_repos__dumpfile_header_push(
1441299742Sdim                  headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest);
1442251881Speter
1443251881Speter              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1444251881Speter                                           compare_root, compare_path,
1445251881Speter                                           FALSE, pool));
1446251881Speter              hex_digest = svn_checksum_to_cstring(checksum, pool);
1447251881Speter              if (hex_digest)
1448299742Sdim                svn_repos__dumpfile_header_push(
1449299742Sdim                  headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest);
1450251881Speter            }
1451251881Speter        }
1452251881Speter      else
1453251881Speter        {
1454251881Speter          /* Just fetch the length of the file. */
1455251881Speter          SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
1456251881Speter        }
1457251881Speter
1458251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
1459251881Speter                                   eb->fs_root, path, FALSE, pool));
1460251881Speter      hex_digest = svn_checksum_to_cstring(checksum, pool);
1461251881Speter      if (hex_digest)
1462299742Sdim        svn_repos__dumpfile_header_push(
1463299742Sdim          headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest);
1464251881Speter
1465251881Speter      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
1466251881Speter                                   eb->fs_root, path, FALSE, pool));
1467251881Speter      hex_digest = svn_checksum_to_cstring(checksum, pool);
1468251881Speter      if (hex_digest)
1469299742Sdim        svn_repos__dumpfile_header_push(
1470299742Sdim          headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest);
1471251881Speter    }
1472251881Speter
1473251881Speter  /* 'Content-length:' is the last header before we dump the content,
1474251881Speter     and is the sum of the text and prop contents lengths.  We write
1475251881Speter     this only for the benefit of non-Subversion RFC-822 parsers. */
1476299742Sdim  SVN_ERR(svn_repos__dump_node_record(eb->stream, headers,
1477299742Sdim                                      must_dump_props ? propstring : NULL,
1478299742Sdim                                      must_dump_text,
1479299742Sdim                                      must_dump_text ? textlen : 0,
1480299742Sdim                                      TRUE /*content_length_always*/,
1481299742Sdim                                      pool));
1482251881Speter
1483251881Speter  /* Dump text content */
1484251881Speter  if (must_dump_text && (kind == svn_node_file))
1485251881Speter    {
1486251881Speter      svn_stream_t *contents;
1487251881Speter
1488251881Speter      if (delta_file)
1489251881Speter        {
1490251881Speter          /* Make sure to close the underlying file when the stream is
1491251881Speter             closed. */
1492251881Speter          contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
1493251881Speter        }
1494251881Speter      else
1495251881Speter        SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
1496251881Speter
1497251881Speter      SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
1498251881Speter                               NULL, NULL, pool));
1499251881Speter    }
1500251881Speter
1501251881Speter  len = 2;
1502251881Speter  return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
1503251881Speter}
1504251881Speter
1505251881Speter
1506251881Speterstatic svn_error_t *
1507251881Speteropen_root(void *edit_baton,
1508251881Speter          svn_revnum_t base_revision,
1509251881Speter          apr_pool_t *pool,
1510251881Speter          void **root_baton)
1511251881Speter{
1512251881Speter  *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
1513299742Sdim                               edit_baton, NULL, pool);
1514251881Speter  return SVN_NO_ERROR;
1515251881Speter}
1516251881Speter
1517251881Speter
1518251881Speterstatic svn_error_t *
1519251881Speterdelete_entry(const char *path,
1520251881Speter             svn_revnum_t revision,
1521251881Speter             void *parent_baton,
1522251881Speter             apr_pool_t *pool)
1523251881Speter{
1524251881Speter  struct dir_baton *pb = parent_baton;
1525251881Speter  const char *mypath = apr_pstrdup(pb->pool, path);
1526251881Speter
1527251881Speter  /* remember this path needs to be deleted. */
1528251881Speter  svn_hash_sets(pb->deleted_entries, mypath, pb);
1529251881Speter
1530251881Speter  return SVN_NO_ERROR;
1531251881Speter}
1532251881Speter
1533251881Speter
1534251881Speterstatic svn_error_t *
1535251881Speteradd_directory(const char *path,
1536251881Speter              void *parent_baton,
1537251881Speter              const char *copyfrom_path,
1538251881Speter              svn_revnum_t copyfrom_rev,
1539251881Speter              apr_pool_t *pool,
1540251881Speter              void **child_baton)
1541251881Speter{
1542251881Speter  struct dir_baton *pb = parent_baton;
1543251881Speter  struct edit_baton *eb = pb->edit_baton;
1544299742Sdim  void *was_deleted;
1545251881Speter  svn_boolean_t is_copy = FALSE;
1546251881Speter  struct dir_baton *new_db
1547299742Sdim    = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool);
1548251881Speter
1549251881Speter  /* This might be a replacement -- is the path already deleted? */
1550299742Sdim  was_deleted = svn_hash_gets(pb->deleted_entries, path);
1551251881Speter
1552251881Speter  /* Detect an add-with-history. */
1553251881Speter  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1554251881Speter
1555251881Speter  /* Dump the node. */
1556251881Speter  SVN_ERR(dump_node(eb, path,
1557251881Speter                    svn_node_dir,
1558299742Sdim                    was_deleted ? svn_node_action_replace : svn_node_action_add,
1559251881Speter                    is_copy,
1560251881Speter                    is_copy ? copyfrom_path : NULL,
1561251881Speter                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1562251881Speter                    pool));
1563251881Speter
1564299742Sdim  if (was_deleted)
1565251881Speter    /* Delete the path, it's now been dumped. */
1566251881Speter    svn_hash_sets(pb->deleted_entries, path, NULL);
1567251881Speter
1568299742Sdim  /* Check for normalized name clashes, but only if this is actually a
1569299742Sdim     new name in the parent, not a replacement. */
1570299742Sdim  if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1571299742Sdim    {
1572299742Sdim      pb->check_name_collision = TRUE;
1573299742Sdim    }
1574299742Sdim
1575251881Speter  new_db->written_out = TRUE;
1576251881Speter
1577251881Speter  *child_baton = new_db;
1578251881Speter  return SVN_NO_ERROR;
1579251881Speter}
1580251881Speter
1581251881Speter
1582251881Speterstatic svn_error_t *
1583251881Speteropen_directory(const char *path,
1584251881Speter               void *parent_baton,
1585251881Speter               svn_revnum_t base_revision,
1586251881Speter               apr_pool_t *pool,
1587251881Speter               void **child_baton)
1588251881Speter{
1589251881Speter  struct dir_baton *pb = parent_baton;
1590251881Speter  struct edit_baton *eb = pb->edit_baton;
1591251881Speter  struct dir_baton *new_db;
1592251881Speter  const char *cmp_path = NULL;
1593251881Speter  svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1594251881Speter
1595251881Speter  /* If the parent directory has explicit comparison path and rev,
1596251881Speter     record the same for this one. */
1597251881Speter  if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1598251881Speter    {
1599251881Speter      cmp_path = svn_relpath_join(pb->cmp_path,
1600251881Speter                                  svn_relpath_basename(path, pool), pool);
1601251881Speter      cmp_rev = pb->cmp_rev;
1602251881Speter    }
1603251881Speter
1604299742Sdim  new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool);
1605251881Speter  *child_baton = new_db;
1606251881Speter  return SVN_NO_ERROR;
1607251881Speter}
1608251881Speter
1609251881Speter
1610251881Speterstatic svn_error_t *
1611251881Speterclose_directory(void *dir_baton,
1612251881Speter                apr_pool_t *pool)
1613251881Speter{
1614251881Speter  struct dir_baton *db = dir_baton;
1615251881Speter  struct edit_baton *eb = db->edit_baton;
1616251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1617251881Speter  int i;
1618251881Speter  apr_array_header_t *sorted_entries;
1619251881Speter
1620251881Speter  /* Sort entries lexically instead of as paths. Even though the entries
1621251881Speter   * are full paths they're all in the same directory (see comment in struct
1622251881Speter   * dir_baton definition). So we really want to sort by basename, in which
1623251881Speter   * case the lexical sort function is more efficient. */
1624251881Speter  sorted_entries = svn_sort__hash(db->deleted_entries,
1625251881Speter                                  svn_sort_compare_items_lexically, pool);
1626251881Speter  for (i = 0; i < sorted_entries->nelts; i++)
1627251881Speter    {
1628251881Speter      const char *path = APR_ARRAY_IDX(sorted_entries, i,
1629251881Speter                                       svn_sort__item_t).key;
1630251881Speter
1631251881Speter      svn_pool_clear(subpool);
1632251881Speter
1633251881Speter      /* By sending 'svn_node_unknown', the Node-kind: header simply won't
1634251881Speter         be written out.  No big deal at all, really.  The loader
1635251881Speter         shouldn't care.  */
1636251881Speter      SVN_ERR(dump_node(eb, path,
1637251881Speter                        svn_node_unknown, svn_node_action_delete,
1638251881Speter                        FALSE, NULL, SVN_INVALID_REVNUM, subpool));
1639251881Speter    }
1640251881Speter
1641251881Speter  svn_pool_destroy(subpool);
1642251881Speter  return SVN_NO_ERROR;
1643251881Speter}
1644251881Speter
1645251881Speter
1646251881Speterstatic svn_error_t *
1647251881Speteradd_file(const char *path,
1648251881Speter         void *parent_baton,
1649251881Speter         const char *copyfrom_path,
1650251881Speter         svn_revnum_t copyfrom_rev,
1651251881Speter         apr_pool_t *pool,
1652251881Speter         void **file_baton)
1653251881Speter{
1654251881Speter  struct dir_baton *pb = parent_baton;
1655251881Speter  struct edit_baton *eb = pb->edit_baton;
1656299742Sdim  void *was_deleted;
1657251881Speter  svn_boolean_t is_copy = FALSE;
1658251881Speter
1659251881Speter  /* This might be a replacement -- is the path already deleted? */
1660299742Sdim  was_deleted = svn_hash_gets(pb->deleted_entries, path);
1661251881Speter
1662251881Speter  /* Detect add-with-history. */
1663251881Speter  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
1664251881Speter
1665251881Speter  /* Dump the node. */
1666251881Speter  SVN_ERR(dump_node(eb, path,
1667251881Speter                    svn_node_file,
1668299742Sdim                    was_deleted ? svn_node_action_replace : svn_node_action_add,
1669251881Speter                    is_copy,
1670251881Speter                    is_copy ? copyfrom_path : NULL,
1671251881Speter                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
1672251881Speter                    pool));
1673251881Speter
1674299742Sdim  if (was_deleted)
1675251881Speter    /* delete the path, it's now been dumped. */
1676251881Speter    svn_hash_sets(pb->deleted_entries, path, NULL);
1677251881Speter
1678299742Sdim  /* Check for normalized name clashes, but only if this is actually a
1679299742Sdim     new name in the parent, not a replacement. */
1680299742Sdim  if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func)
1681299742Sdim    {
1682299742Sdim      pb->check_name_collision = TRUE;
1683299742Sdim    }
1684299742Sdim
1685251881Speter  *file_baton = NULL;  /* muhahahaha */
1686251881Speter  return SVN_NO_ERROR;
1687251881Speter}
1688251881Speter
1689251881Speter
1690251881Speterstatic svn_error_t *
1691251881Speteropen_file(const char *path,
1692251881Speter          void *parent_baton,
1693251881Speter          svn_revnum_t ancestor_revision,
1694251881Speter          apr_pool_t *pool,
1695251881Speter          void **file_baton)
1696251881Speter{
1697251881Speter  struct dir_baton *pb = parent_baton;
1698251881Speter  struct edit_baton *eb = pb->edit_baton;
1699251881Speter  const char *cmp_path = NULL;
1700251881Speter  svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
1701251881Speter
1702251881Speter  /* If the parent directory has explicit comparison path and rev,
1703251881Speter     record the same for this one. */
1704251881Speter  if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
1705251881Speter    {
1706251881Speter      cmp_path = svn_relpath_join(pb->cmp_path,
1707251881Speter                                  svn_relpath_basename(path, pool), pool);
1708251881Speter      cmp_rev = pb->cmp_rev;
1709251881Speter    }
1710251881Speter
1711251881Speter  SVN_ERR(dump_node(eb, path,
1712251881Speter                    svn_node_file, svn_node_action_change,
1713251881Speter                    FALSE, cmp_path, cmp_rev, pool));
1714251881Speter
1715251881Speter  *file_baton = NULL;  /* muhahahaha again */
1716251881Speter  return SVN_NO_ERROR;
1717251881Speter}
1718251881Speter
1719251881Speter
1720251881Speterstatic svn_error_t *
1721251881Speterchange_dir_prop(void *parent_baton,
1722251881Speter                const char *name,
1723251881Speter                const svn_string_t *value,
1724251881Speter                apr_pool_t *pool)
1725251881Speter{
1726251881Speter  struct dir_baton *db = parent_baton;
1727251881Speter  struct edit_baton *eb = db->edit_baton;
1728251881Speter
1729251881Speter  /* This function is what distinguishes between a directory that is
1730251881Speter     opened to merely get somewhere, vs. one that is opened because it
1731299742Sdim     *actually* changed by itself.
1732299742Sdim
1733299742Sdim     Instead of recording the prop changes here, we just use this method
1734299742Sdim     to trigger writing the node; dump_node() finds all the changes. */
1735251881Speter  if (! db->written_out)
1736251881Speter    {
1737251881Speter      SVN_ERR(dump_node(eb, db->path,
1738251881Speter                        svn_node_dir, svn_node_action_change,
1739299742Sdim                        /* ### We pass is_copy=FALSE; this might be wrong
1740299742Sdim                           but the parameter isn't used when action=change. */
1741251881Speter                        FALSE, db->cmp_path, db->cmp_rev, pool));
1742251881Speter      db->written_out = TRUE;
1743251881Speter    }
1744251881Speter  return SVN_NO_ERROR;
1745251881Speter}
1746251881Speter
1747251881Speterstatic svn_error_t *
1748251881Speterfetch_props_func(apr_hash_t **props,
1749251881Speter                 void *baton,
1750251881Speter                 const char *path,
1751251881Speter                 svn_revnum_t base_revision,
1752251881Speter                 apr_pool_t *result_pool,
1753251881Speter                 apr_pool_t *scratch_pool)
1754251881Speter{
1755251881Speter  struct edit_baton *eb = baton;
1756251881Speter  svn_error_t *err;
1757251881Speter  svn_fs_root_t *fs_root;
1758251881Speter
1759251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
1760251881Speter    base_revision = eb->current_rev - 1;
1761251881Speter
1762251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1763251881Speter
1764251881Speter  err = svn_fs_node_proplist(props, fs_root, path, result_pool);
1765251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1766251881Speter    {
1767251881Speter      svn_error_clear(err);
1768251881Speter      *props = apr_hash_make(result_pool);
1769251881Speter      return SVN_NO_ERROR;
1770251881Speter    }
1771251881Speter  else if (err)
1772251881Speter    return svn_error_trace(err);
1773251881Speter
1774251881Speter  return SVN_NO_ERROR;
1775251881Speter}
1776251881Speter
1777251881Speterstatic svn_error_t *
1778251881Speterfetch_kind_func(svn_node_kind_t *kind,
1779251881Speter                void *baton,
1780251881Speter                const char *path,
1781251881Speter                svn_revnum_t base_revision,
1782251881Speter                apr_pool_t *scratch_pool)
1783251881Speter{
1784251881Speter  struct edit_baton *eb = baton;
1785251881Speter  svn_fs_root_t *fs_root;
1786251881Speter
1787251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
1788251881Speter    base_revision = eb->current_rev - 1;
1789251881Speter
1790251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1791251881Speter
1792251881Speter  SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
1793251881Speter
1794251881Speter  return SVN_NO_ERROR;
1795251881Speter}
1796251881Speter
1797251881Speterstatic svn_error_t *
1798251881Speterfetch_base_func(const char **filename,
1799251881Speter                void *baton,
1800251881Speter                const char *path,
1801251881Speter                svn_revnum_t base_revision,
1802251881Speter                apr_pool_t *result_pool,
1803251881Speter                apr_pool_t *scratch_pool)
1804251881Speter{
1805251881Speter  struct edit_baton *eb = baton;
1806251881Speter  svn_stream_t *contents;
1807251881Speter  svn_stream_t *file_stream;
1808251881Speter  const char *tmp_filename;
1809251881Speter  svn_error_t *err;
1810251881Speter  svn_fs_root_t *fs_root;
1811251881Speter
1812251881Speter  if (!SVN_IS_VALID_REVNUM(base_revision))
1813251881Speter    base_revision = eb->current_rev - 1;
1814251881Speter
1815251881Speter  SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
1816251881Speter
1817251881Speter  err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
1818251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1819251881Speter    {
1820251881Speter      svn_error_clear(err);
1821251881Speter      *filename = NULL;
1822251881Speter      return SVN_NO_ERROR;
1823251881Speter    }
1824251881Speter  else if (err)
1825251881Speter    return svn_error_trace(err);
1826251881Speter  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
1827251881Speter                                 svn_io_file_del_on_pool_cleanup,
1828251881Speter                                 scratch_pool, scratch_pool));
1829251881Speter  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
1830251881Speter
1831251881Speter  *filename = apr_pstrdup(result_pool, tmp_filename);
1832251881Speter
1833251881Speter  return SVN_NO_ERROR;
1834251881Speter}
1835251881Speter
1836251881Speter
1837251881Speterstatic svn_error_t *
1838251881Speterget_dump_editor(const svn_delta_editor_t **editor,
1839251881Speter                void **edit_baton,
1840251881Speter                svn_fs_t *fs,
1841251881Speter                svn_revnum_t to_rev,
1842251881Speter                const char *root_path,
1843251881Speter                svn_stream_t *stream,
1844251881Speter                svn_boolean_t *found_old_reference,
1845251881Speter                svn_boolean_t *found_old_mergeinfo,
1846251881Speter                svn_error_t *(*custom_close_directory)(void *dir_baton,
1847251881Speter                                  apr_pool_t *scratch_pool),
1848251881Speter                svn_repos_notify_func_t notify_func,
1849251881Speter                void *notify_baton,
1850251881Speter                svn_revnum_t oldest_dumped_rev,
1851251881Speter                svn_boolean_t use_deltas,
1852251881Speter                svn_boolean_t verify,
1853299742Sdim                svn_boolean_t check_normalization,
1854251881Speter                apr_pool_t *pool)
1855251881Speter{
1856251881Speter  /* Allocate an edit baton to be stored in every directory baton.
1857251881Speter     Set it up for the directory baton we create here, which is the
1858251881Speter     root baton. */
1859251881Speter  struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1860251881Speter  svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
1861251881Speter  svn_delta_shim_callbacks_t *shim_callbacks =
1862251881Speter                                svn_delta_shim_callbacks_default(pool);
1863251881Speter
1864251881Speter  /* Set up the edit baton. */
1865251881Speter  eb->stream = stream;
1866251881Speter  eb->notify_func = notify_func;
1867251881Speter  eb->notify_baton = notify_baton;
1868251881Speter  eb->oldest_dumped_rev = oldest_dumped_rev;
1869251881Speter  eb->path = apr_pstrdup(pool, root_path);
1870251881Speter  SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
1871251881Speter  eb->fs = fs;
1872251881Speter  eb->current_rev = to_rev;
1873251881Speter  eb->use_deltas = use_deltas;
1874251881Speter  eb->verify = verify;
1875299742Sdim  eb->check_normalization = check_normalization;
1876251881Speter  eb->found_old_reference = found_old_reference;
1877251881Speter  eb->found_old_mergeinfo = found_old_mergeinfo;
1878251881Speter
1879299742Sdim  /* In non-verification mode, we will allow anything to be dumped because
1880299742Sdim     it might be an incremental dump with possible manual intervention.
1881299742Sdim     Also, this might be the last resort when it comes to data recovery.
1882299742Sdim
1883299742Sdim     Else, make sure that all paths exists at their respective revisions.
1884299742Sdim  */
1885299742Sdim  eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL;
1886299742Sdim
1887251881Speter  /* Set up the editor. */
1888251881Speter  dump_editor->open_root = open_root;
1889251881Speter  dump_editor->delete_entry = delete_entry;
1890251881Speter  dump_editor->add_directory = add_directory;
1891251881Speter  dump_editor->open_directory = open_directory;
1892251881Speter  if (custom_close_directory)
1893251881Speter    dump_editor->close_directory = custom_close_directory;
1894251881Speter  else
1895251881Speter    dump_editor->close_directory = close_directory;
1896251881Speter  dump_editor->change_dir_prop = change_dir_prop;
1897251881Speter  dump_editor->add_file = add_file;
1898251881Speter  dump_editor->open_file = open_file;
1899251881Speter
1900251881Speter  *edit_baton = eb;
1901251881Speter  *editor = dump_editor;
1902251881Speter
1903251881Speter  shim_callbacks->fetch_kind_func = fetch_kind_func;
1904251881Speter  shim_callbacks->fetch_props_func = fetch_props_func;
1905251881Speter  shim_callbacks->fetch_base_func = fetch_base_func;
1906251881Speter  shim_callbacks->fetch_baton = eb;
1907251881Speter
1908251881Speter  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1909251881Speter                                   NULL, NULL, shim_callbacks, pool, pool));
1910251881Speter
1911251881Speter  return SVN_NO_ERROR;
1912251881Speter}
1913251881Speter
1914251881Speter/*----------------------------------------------------------------------*/
1915251881Speter
1916251881Speter/** The main dumping routine, svn_repos_dump_fs. **/
1917251881Speter
1918251881Speter
1919251881Speter/* Helper for svn_repos_dump_fs.
1920251881Speter
1921251881Speter   Write a revision record of REV in FS to writable STREAM, using POOL.
1922251881Speter */
1923251881Speterstatic svn_error_t *
1924251881Speterwrite_revision_record(svn_stream_t *stream,
1925251881Speter                      svn_fs_t *fs,
1926251881Speter                      svn_revnum_t rev,
1927251881Speter                      apr_pool_t *pool)
1928251881Speter{
1929251881Speter  apr_hash_t *props;
1930251881Speter  apr_time_t timetemp;
1931251881Speter  svn_string_t *datevalue;
1932251881Speter
1933251881Speter  SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
1934251881Speter
1935251881Speter  /* Run revision date properties through the time conversion to
1936251881Speter     canonicalize them. */
1937251881Speter  /* ### Remove this when it is no longer needed for sure. */
1938251881Speter  datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1939251881Speter  if (datevalue)
1940251881Speter    {
1941251881Speter      SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1942251881Speter      datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1943251881Speter                                    pool);
1944251881Speter      svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1945251881Speter    }
1946251881Speter
1947299742Sdim  SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
1948299742Sdim                                          TRUE /*props_section_always*/,
1949299742Sdim                                          pool));
1950299742Sdim  return SVN_NO_ERROR;
1951251881Speter}
1952251881Speter
1953251881Speter
1954251881Speter
1955251881Speter/* The main dumper. */
1956251881Spetersvn_error_t *
1957251881Spetersvn_repos_dump_fs3(svn_repos_t *repos,
1958251881Speter                   svn_stream_t *stream,
1959251881Speter                   svn_revnum_t start_rev,
1960251881Speter                   svn_revnum_t end_rev,
1961251881Speter                   svn_boolean_t incremental,
1962251881Speter                   svn_boolean_t use_deltas,
1963251881Speter                   svn_repos_notify_func_t notify_func,
1964251881Speter                   void *notify_baton,
1965251881Speter                   svn_cancel_func_t cancel_func,
1966251881Speter                   void *cancel_baton,
1967251881Speter                   apr_pool_t *pool)
1968251881Speter{
1969251881Speter  const svn_delta_editor_t *dump_editor;
1970251881Speter  void *dump_edit_baton = NULL;
1971299742Sdim  svn_revnum_t rev;
1972251881Speter  svn_fs_t *fs = svn_repos_fs(repos);
1973251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1974251881Speter  svn_revnum_t youngest;
1975251881Speter  const char *uuid;
1976251881Speter  int version;
1977251881Speter  svn_boolean_t found_old_reference = FALSE;
1978251881Speter  svn_boolean_t found_old_mergeinfo = FALSE;
1979251881Speter  svn_repos_notify_t *notify;
1980251881Speter
1981251881Speter  /* Determine the current youngest revision of the filesystem. */
1982251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1983251881Speter
1984251881Speter  /* Use default vals if necessary. */
1985251881Speter  if (! SVN_IS_VALID_REVNUM(start_rev))
1986251881Speter    start_rev = 0;
1987251881Speter  if (! SVN_IS_VALID_REVNUM(end_rev))
1988251881Speter    end_rev = youngest;
1989251881Speter  if (! stream)
1990251881Speter    stream = svn_stream_empty(pool);
1991251881Speter
1992251881Speter  /* Validate the revisions. */
1993251881Speter  if (start_rev > end_rev)
1994251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1995251881Speter                             _("Start revision %ld"
1996251881Speter                               " is greater than end revision %ld"),
1997251881Speter                             start_rev, end_rev);
1998251881Speter  if (end_rev > youngest)
1999251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2000251881Speter                             _("End revision %ld is invalid "
2001251881Speter                               "(youngest revision is %ld)"),
2002251881Speter                             end_rev, youngest);
2003251881Speter
2004251881Speter  /* Write out the UUID. */
2005251881Speter  SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2006251881Speter
2007251881Speter  /* If we're not using deltas, use the previous version, for
2008251881Speter     compatibility with svn 1.0.x. */
2009251881Speter  version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
2010251881Speter  if (!use_deltas)
2011251881Speter    version--;
2012251881Speter
2013251881Speter  /* Write out "general" metadata for the dumpfile.  In this case, a
2014251881Speter     magic header followed by a dumpfile format version. */
2015251881Speter  SVN_ERR(svn_stream_printf(stream, pool,
2016251881Speter                            SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
2017251881Speter                            version));
2018251881Speter  SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
2019251881Speter                            ": %s\n\n", uuid));
2020251881Speter
2021251881Speter  /* Create a notify object that we can reuse in the loop. */
2022251881Speter  if (notify_func)
2023251881Speter    notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
2024251881Speter                                     pool);
2025251881Speter
2026299742Sdim  /* Main loop:  we're going to dump revision REV.  */
2027299742Sdim  for (rev = start_rev; rev <= end_rev; rev++)
2028251881Speter    {
2029251881Speter      svn_fs_root_t *to_root;
2030251881Speter      svn_boolean_t use_deltas_for_rev;
2031251881Speter
2032251881Speter      svn_pool_clear(subpool);
2033251881Speter
2034251881Speter      /* Check for cancellation. */
2035251881Speter      if (cancel_func)
2036251881Speter        SVN_ERR(cancel_func(cancel_baton));
2037251881Speter
2038299742Sdim      /* Write the revision record. */
2039299742Sdim      SVN_ERR(write_revision_record(stream, fs, rev, subpool));
2040251881Speter
2041299742Sdim      /* When dumping revision 0, we just write out the revision record.
2042299742Sdim         The parser might want to use its properties. */
2043299742Sdim      if (rev == 0)
2044299742Sdim        goto loop_end;
2045251881Speter
2046251881Speter      /* Fetch the editor which dumps nodes to a file.  Regardless of
2047251881Speter         what we've been told, don't use deltas for the first rev of a
2048251881Speter         non-incremental dump. */
2049299742Sdim      use_deltas_for_rev = use_deltas && (incremental || rev != start_rev);
2050299742Sdim      SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev,
2051251881Speter                              "", stream, &found_old_reference,
2052251881Speter                              &found_old_mergeinfo, NULL,
2053251881Speter                              notify_func, notify_baton,
2054299742Sdim                              start_rev, use_deltas_for_rev, FALSE, FALSE,
2055299742Sdim                              subpool));
2056251881Speter
2057251881Speter      /* Drive the editor in one way or another. */
2058299742Sdim      SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool));
2059251881Speter
2060251881Speter      /* If this is the first revision of a non-incremental dump,
2061251881Speter         we're in for a full tree dump.  Otherwise, we want to simply
2062251881Speter         replay the revision.  */
2063299742Sdim      if ((rev == start_rev) && (! incremental))
2064251881Speter        {
2065299742Sdim          /* Compare against revision 0, so everything appears to be added. */
2066251881Speter          svn_fs_root_t *from_root;
2067299742Sdim          SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, subpool));
2068251881Speter          SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
2069251881Speter                                       to_root, "",
2070251881Speter                                       dump_editor, dump_edit_baton,
2071251881Speter                                       NULL,
2072251881Speter                                       NULL,
2073251881Speter                                       FALSE, /* don't send text-deltas */
2074251881Speter                                       svn_depth_infinity,
2075251881Speter                                       FALSE, /* don't send entry props */
2076251881Speter                                       FALSE, /* don't ignore ancestry */
2077251881Speter                                       subpool));
2078251881Speter        }
2079251881Speter      else
2080251881Speter        {
2081299742Sdim          /* The normal case: compare consecutive revs. */
2082251881Speter          SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2083251881Speter                                    dump_editor, dump_edit_baton,
2084251881Speter                                    NULL, NULL, subpool));
2085251881Speter
2086251881Speter          /* While our editor close_edit implementation is a no-op, we still
2087251881Speter             do this for completeness. */
2088251881Speter          SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
2089251881Speter        }
2090251881Speter
2091251881Speter    loop_end:
2092251881Speter      if (notify_func)
2093251881Speter        {
2094299742Sdim          notify->revision = rev;
2095251881Speter          notify_func(notify_baton, notify, subpool);
2096251881Speter        }
2097251881Speter    }
2098251881Speter
2099251881Speter  if (notify_func)
2100251881Speter    {
2101251881Speter      /* Did we issue any warnings about references to revisions older than
2102251881Speter         the oldest dumped revision?  If so, then issue a final generic
2103251881Speter         warning, since the inline warnings already issued might easily be
2104251881Speter         missed. */
2105251881Speter
2106251881Speter      notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
2107251881Speter      notify_func(notify_baton, notify, subpool);
2108251881Speter
2109251881Speter      if (found_old_reference)
2110251881Speter        {
2111299742Sdim          notify_warning(subpool, notify_func, notify_baton,
2112299742Sdim                         svn_repos_notify_warning_found_old_reference,
2113299742Sdim                         _("The range of revisions dumped "
2114299742Sdim                           "contained references to "
2115299742Sdim                           "copy sources outside that "
2116299742Sdim                           "range."));
2117251881Speter        }
2118251881Speter
2119251881Speter      /* Ditto if we issued any warnings about old revisions referenced
2120251881Speter         in dumped mergeinfo. */
2121251881Speter      if (found_old_mergeinfo)
2122251881Speter        {
2123299742Sdim          notify_warning(subpool, notify_func, notify_baton,
2124299742Sdim                         svn_repos_notify_warning_found_old_mergeinfo,
2125299742Sdim                         _("The range of revisions dumped "
2126299742Sdim                           "contained mergeinfo "
2127299742Sdim                           "which reference revisions outside "
2128299742Sdim                           "that range."));
2129251881Speter        }
2130251881Speter    }
2131251881Speter
2132251881Speter  svn_pool_destroy(subpool);
2133251881Speter
2134251881Speter  return SVN_NO_ERROR;
2135251881Speter}
2136251881Speter
2137251881Speter
2138251881Speter/*----------------------------------------------------------------------*/
2139251881Speter
2140251881Speter/* verify, based on dump */
2141251881Speter
2142251881Speter
2143251881Speter/* Creating a new revision that changes /A/B/E/bravo means creating new
2144251881Speter   directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
2145251881Speter   each entry not changed in the new revision a link back to the entry in a
2146251881Speter   previous revision.  svn_repos_replay()ing a revision does not verify that
2147251881Speter   those links are correct.
2148251881Speter
2149251881Speter   For paths actually changed in the revision we verify, we get directory
2150251881Speter   contents or file length twice: once in the dump editor, and once here.
2151251881Speter   We could create a new verify baton, store in it the changed paths, and
2152251881Speter   skip those here, but that means building an entire wrapper editor and
2153251881Speter   managing two levels of batons.  The impact from checking these entries
2154251881Speter   twice should be minimal, while the code to avoid it is not.
2155251881Speter*/
2156251881Speter
2157251881Speterstatic svn_error_t *
2158251881Speterverify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
2159251881Speter                       void *val, apr_pool_t *pool)
2160251881Speter{
2161251881Speter  struct dir_baton *db = baton;
2162251881Speter  svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
2163299742Sdim  char *path;
2164299742Sdim  svn_boolean_t right_kind;
2165251881Speter
2166299742Sdim  path = svn_relpath_join(db->path, (const char *)key, pool);
2167299742Sdim
2168251881Speter  /* since we can't access the directory entries directly by their ID,
2169251881Speter     we need to navigate from the FS_ROOT to them (relatively expensive
2170299742Sdim     because we may start at a never rev than the last change to node).
2171299742Sdim     We check that the node kind stored in the noderev matches the dir
2172299742Sdim     entry.  This also ensures that all entries point to valid noderevs.
2173299742Sdim   */
2174251881Speter  switch (dirent->kind) {
2175251881Speter  case svn_node_dir:
2176299742Sdim    SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool));
2177299742Sdim    if (!right_kind)
2178299742Sdim      return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2179299742Sdim                               _("Node '%s' is not a directory."),
2180299742Sdim                               path);
2181299742Sdim
2182251881Speter    break;
2183251881Speter  case svn_node_file:
2184299742Sdim    SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool));
2185299742Sdim    if (!right_kind)
2186299742Sdim      return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2187299742Sdim                               _("Node '%s' is not a file."),
2188299742Sdim                               path);
2189251881Speter    break;
2190251881Speter  default:
2191251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
2192251881Speter                             _("Unexpected node kind %d for '%s'"),
2193251881Speter                             dirent->kind, path);
2194251881Speter  }
2195251881Speter
2196251881Speter  return SVN_NO_ERROR;
2197251881Speter}
2198251881Speter
2199299742Sdim/* Baton used by the check_name_collision hash iterator. */
2200299742Sdimstruct check_name_collision_baton
2201299742Sdim{
2202299742Sdim  struct dir_baton *dir_baton;
2203299742Sdim  apr_hash_t *normalized;
2204299742Sdim  svn_membuf_t buffer;
2205299742Sdim};
2206299742Sdim
2207299742Sdim/* Scan the directory and report all entry names that differ only in
2208299742Sdim   Unicode character representation. */
2209251881Speterstatic svn_error_t *
2210299742Sdimcheck_name_collision(void *baton, const void *key, apr_ssize_t klen,
2211299742Sdim                     void *val, apr_pool_t *iterpool)
2212251881Speter{
2213299742Sdim  struct check_name_collision_baton *const cb = baton;
2214299742Sdim  const char *name;
2215299742Sdim  const char *found;
2216299742Sdim
2217299742Sdim  SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer));
2218299742Sdim
2219299742Sdim  found = svn_hash_gets(cb->normalized, name);
2220299742Sdim  if (!found)
2221299742Sdim    svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2222299742Sdim                  normalized_unique);
2223299742Sdim  else if (found == normalized_collision)
2224299742Sdim    /* Skip already reported collision */;
2225299742Sdim  else
2226299742Sdim    {
2227299742Sdim      struct dir_baton *const db = cb->dir_baton;
2228299742Sdim      struct edit_baton *const eb = db->edit_baton;
2229299742Sdim      const char* normpath;
2230299742Sdim
2231299742Sdim      svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name),
2232299742Sdim                    normalized_collision);
2233299742Sdim
2234299742Sdim      SVN_ERR(svn_utf__normalize(
2235299742Sdim                  &normpath, svn_relpath_join(db->path, name, iterpool),
2236299742Sdim                  SVN_UTF__UNKNOWN_LENGTH, &cb->buffer));
2237299742Sdim      notify_warning(iterpool, eb->notify_func, eb->notify_baton,
2238299742Sdim                     svn_repos_notify_warning_name_collision,
2239299742Sdim                     _("Duplicate representation of path '%s'"), normpath);
2240299742Sdim    }
2241299742Sdim  return SVN_NO_ERROR;
2242299742Sdim}
2243299742Sdim
2244299742Sdim
2245299742Sdimstatic svn_error_t *
2246299742Sdimverify_close_directory(void *dir_baton, apr_pool_t *pool)
2247299742Sdim{
2248251881Speter  struct dir_baton *db = dir_baton;
2249251881Speter  apr_hash_t *dirents;
2250251881Speter  SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
2251251881Speter                             db->path, pool));
2252251881Speter  SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
2253251881Speter                            dir_baton, pool));
2254299742Sdim
2255299742Sdim  if (db->check_name_collision)
2256299742Sdim    {
2257299742Sdim      struct check_name_collision_baton check_baton;
2258299742Sdim      check_baton.dir_baton = db;
2259299742Sdim      check_baton.normalized = apr_hash_make(pool);
2260299742Sdim      svn_membuf__create(&check_baton.buffer, 0, pool);
2261299742Sdim      SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision,
2262299742Sdim                                &check_baton, pool));
2263299742Sdim    }
2264299742Sdim
2265251881Speter  return close_directory(dir_baton, pool);
2266251881Speter}
2267251881Speter
2268299742Sdim/* Verify revision REV in file system FS. */
2269299742Sdimstatic svn_error_t *
2270299742Sdimverify_one_revision(svn_fs_t *fs,
2271299742Sdim                    svn_revnum_t rev,
2272299742Sdim                    svn_repos_notify_func_t notify_func,
2273299742Sdim                    void *notify_baton,
2274299742Sdim                    svn_revnum_t start_rev,
2275299742Sdim                    svn_boolean_t check_normalization,
2276299742Sdim                    svn_cancel_func_t cancel_func,
2277299742Sdim                    void *cancel_baton,
2278299742Sdim                    apr_pool_t *scratch_pool)
2279299742Sdim{
2280299742Sdim  const svn_delta_editor_t *dump_editor;
2281299742Sdim  void *dump_edit_baton;
2282299742Sdim  svn_fs_root_t *to_root;
2283299742Sdim  apr_hash_t *props;
2284299742Sdim  const svn_delta_editor_t *cancel_editor;
2285299742Sdim  void *cancel_edit_baton;
2286299742Sdim
2287299742Sdim  /* Get cancellable dump editor, but with our close_directory handler.*/
2288299742Sdim  SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
2289299742Sdim                          fs, rev, "",
2290299742Sdim                          svn_stream_empty(scratch_pool),
2291299742Sdim                          NULL, NULL,
2292299742Sdim                          verify_close_directory,
2293299742Sdim                          notify_func, notify_baton,
2294299742Sdim                          start_rev,
2295299742Sdim                          FALSE, TRUE, /* use_deltas, verify */
2296299742Sdim                          check_normalization,
2297299742Sdim                          scratch_pool));
2298299742Sdim  SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
2299299742Sdim                                            dump_editor, dump_edit_baton,
2300299742Sdim                                            &cancel_editor,
2301299742Sdim                                            &cancel_edit_baton,
2302299742Sdim                                            scratch_pool));
2303299742Sdim  SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool));
2304299742Sdim  SVN_ERR(svn_fs_verify_root(to_root, scratch_pool));
2305299742Sdim  SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
2306299742Sdim                            cancel_editor, cancel_edit_baton,
2307299742Sdim                            NULL, NULL, scratch_pool));
2308299742Sdim
2309299742Sdim  /* While our editor close_edit implementation is a no-op, we still
2310299742Sdim     do this for completeness. */
2311299742Sdim  SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
2312299742Sdim
2313299742Sdim  SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool));
2314299742Sdim
2315299742Sdim  return SVN_NO_ERROR;
2316299742Sdim}
2317299742Sdim
2318251881Speter/* Baton type used for forwarding notifications from FS API to REPOS API. */
2319299742Sdimstruct verify_fs_notify_func_baton_t
2320251881Speter{
2321251881Speter   /* notification function to call (must not be NULL) */
2322251881Speter   svn_repos_notify_func_t notify_func;
2323251881Speter
2324251881Speter   /* baton to use for it */
2325251881Speter   void *notify_baton;
2326251881Speter
2327251881Speter   /* type of notification to send (we will simply plug in the revision) */
2328251881Speter   svn_repos_notify_t *notify;
2329251881Speter};
2330251881Speter
2331251881Speter/* Forward the notification to BATON. */
2332251881Speterstatic void
2333299742Sdimverify_fs_notify_func(svn_revnum_t revision,
2334251881Speter                       void *baton,
2335251881Speter                       apr_pool_t *pool)
2336251881Speter{
2337299742Sdim  struct verify_fs_notify_func_baton_t *notify_baton = baton;
2338251881Speter
2339251881Speter  notify_baton->notify->revision = revision;
2340251881Speter  notify_baton->notify_func(notify_baton->notify_baton,
2341251881Speter                            notify_baton->notify, pool);
2342251881Speter}
2343251881Speter
2344299742Sdimstatic svn_error_t *
2345299742Sdimreport_error(svn_revnum_t revision,
2346299742Sdim             svn_error_t *verify_err,
2347299742Sdim             svn_repos_verify_callback_t verify_callback,
2348299742Sdim             void *verify_baton,
2349299742Sdim             apr_pool_t *pool)
2350299742Sdim{
2351299742Sdim  if (verify_callback)
2352299742Sdim    {
2353299742Sdim      svn_error_t *cb_err;
2354299742Sdim
2355299742Sdim      /* The caller provided us with a callback, so make him responsible
2356299742Sdim         for what's going to happen with the error. */
2357299742Sdim      cb_err = verify_callback(verify_baton, revision, verify_err, pool);
2358299742Sdim      svn_error_clear(verify_err);
2359299742Sdim      SVN_ERR(cb_err);
2360299742Sdim
2361299742Sdim      return SVN_NO_ERROR;
2362299742Sdim    }
2363299742Sdim  else
2364299742Sdim    {
2365299742Sdim      /* No callback -- no second guessing.  Just return the error. */
2366299742Sdim      return svn_error_trace(verify_err);
2367299742Sdim    }
2368299742Sdim}
2369299742Sdim
2370251881Spetersvn_error_t *
2371299742Sdimsvn_repos_verify_fs3(svn_repos_t *repos,
2372251881Speter                     svn_revnum_t start_rev,
2373251881Speter                     svn_revnum_t end_rev,
2374299742Sdim                     svn_boolean_t check_normalization,
2375299742Sdim                     svn_boolean_t metadata_only,
2376251881Speter                     svn_repos_notify_func_t notify_func,
2377251881Speter                     void *notify_baton,
2378299742Sdim                     svn_repos_verify_callback_t verify_callback,
2379299742Sdim                     void *verify_baton,
2380251881Speter                     svn_cancel_func_t cancel_func,
2381251881Speter                     void *cancel_baton,
2382251881Speter                     apr_pool_t *pool)
2383251881Speter{
2384251881Speter  svn_fs_t *fs = svn_repos_fs(repos);
2385251881Speter  svn_revnum_t youngest;
2386251881Speter  svn_revnum_t rev;
2387251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2388251881Speter  svn_repos_notify_t *notify;
2389251881Speter  svn_fs_progress_notify_func_t verify_notify = NULL;
2390299742Sdim  struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
2391299742Sdim  svn_error_t *err;
2392251881Speter
2393251881Speter  /* Determine the current youngest revision of the filesystem. */
2394251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2395251881Speter
2396251881Speter  /* Use default vals if necessary. */
2397251881Speter  if (! SVN_IS_VALID_REVNUM(start_rev))
2398251881Speter    start_rev = 0;
2399251881Speter  if (! SVN_IS_VALID_REVNUM(end_rev))
2400251881Speter    end_rev = youngest;
2401251881Speter
2402251881Speter  /* Validate the revisions. */
2403251881Speter  if (start_rev > end_rev)
2404251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2405251881Speter                             _("Start revision %ld"
2406251881Speter                               " is greater than end revision %ld"),
2407251881Speter                             start_rev, end_rev);
2408251881Speter  if (end_rev > youngest)
2409251881Speter    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
2410251881Speter                             _("End revision %ld is invalid "
2411251881Speter                               "(youngest revision is %ld)"),
2412251881Speter                             end_rev, youngest);
2413251881Speter
2414251881Speter  /* Create a notify object that we can reuse within the loop and a
2415251881Speter     forwarding structure for notifications from inside svn_fs_verify(). */
2416251881Speter  if (notify_func)
2417251881Speter    {
2418299742Sdim      notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool);
2419251881Speter
2420299742Sdim      verify_notify = verify_fs_notify_func;
2421251881Speter      verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
2422251881Speter      verify_notify_baton->notify_func = notify_func;
2423251881Speter      verify_notify_baton->notify_baton = notify_baton;
2424251881Speter      verify_notify_baton->notify
2425251881Speter        = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
2426251881Speter    }
2427251881Speter
2428251881Speter  /* Verify global metadata and backend-specific data first. */
2429299742Sdim  err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
2430299742Sdim                      start_rev, end_rev,
2431299742Sdim                      verify_notify, verify_notify_baton,
2432299742Sdim                      cancel_func, cancel_baton, pool);
2433251881Speter
2434299742Sdim  if (err && err->apr_err == SVN_ERR_CANCELLED)
2435251881Speter    {
2436299742Sdim      return svn_error_trace(err);
2437299742Sdim    }
2438299742Sdim  else if (err)
2439299742Sdim    {
2440299742Sdim      SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback,
2441299742Sdim                           verify_baton, iterpool));
2442299742Sdim    }
2443251881Speter
2444299742Sdim  if (!metadata_only)
2445299742Sdim    for (rev = start_rev; rev <= end_rev; rev++)
2446299742Sdim      {
2447299742Sdim        svn_pool_clear(iterpool);
2448251881Speter
2449299742Sdim        /* Wrapper function to catch the possible errors. */
2450299742Sdim        err = verify_one_revision(fs, rev, notify_func, notify_baton,
2451299742Sdim                                  start_rev, check_normalization,
2452299742Sdim                                  cancel_func, cancel_baton,
2453299742Sdim                                  iterpool);
2454251881Speter
2455299742Sdim        if (err && err->apr_err == SVN_ERR_CANCELLED)
2456299742Sdim          {
2457299742Sdim            return svn_error_trace(err);
2458299742Sdim          }
2459299742Sdim        else if (err)
2460299742Sdim          {
2461299742Sdim            SVN_ERR(report_error(rev, err, verify_callback, verify_baton,
2462299742Sdim                                 iterpool));
2463299742Sdim          }
2464299742Sdim        else if (notify_func)
2465299742Sdim          {
2466299742Sdim            /* Tell the caller that we're done with this revision. */
2467299742Sdim            notify->revision = rev;
2468299742Sdim            notify_func(notify_baton, notify, iterpool);
2469299742Sdim          }
2470299742Sdim      }
2471251881Speter
2472251881Speter  /* We're done. */
2473251881Speter  if (notify_func)
2474251881Speter    {
2475251881Speter      notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
2476251881Speter      notify_func(notify_baton, notify, iterpool);
2477251881Speter    }
2478251881Speter
2479251881Speter  svn_pool_destroy(iterpool);
2480251881Speter
2481251881Speter  return SVN_NO_ERROR;
2482251881Speter}
2483