source: valtobtest/subversion-1.6.2/subversion/libsvn_repos/dump.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 43.6 KB
Line 
1/* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
2 *
3 * ====================================================================
4 * Copyright (c) 2000-2006 CollabNet.  All rights reserved.
5 *
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution.  The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
11 *
12 * This software consists of voluntary contributions made by many
13 * individuals.  For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
16 */
17
18
19#include "svn_private_config.h"
20#include "svn_pools.h"
21#include "svn_error.h"
22#include "svn_fs.h"
23#include "svn_iter.h"
24#include "svn_repos.h"
25#include "svn_string.h"
26#include "svn_path.h"
27#include "svn_time.h"
28#include "svn_checksum.h"
29#include "svn_props.h"
30
31
32#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
33
34/*----------------------------------------------------------------------*/
35
36/** A variant of our hash-writing routine in libsvn_subr;  this one
37    writes to a stringbuf instead of a file, and outputs PROPS-END
38    instead of END.  If OLDHASH is not NULL, then only properties
39    which vary from OLDHASH will be written, and properties which
40    exist only in OLDHASH will be written out with "D" entries
41    (like "K" entries but with no corresponding value). **/
42
43static void
44write_hash_to_stringbuf(apr_hash_t *hash,
45                        apr_hash_t *oldhash,
46                        svn_stringbuf_t **strbuf,
47                        apr_pool_t *pool)
48{
49  apr_hash_index_t *this;      /* current hash entry */
50
51  *strbuf = svn_stringbuf_create("", pool);
52
53  for (this = apr_hash_first(pool, hash); this; this = apr_hash_next(this))
54    {
55      const void *key;
56      void *val;
57      apr_ssize_t keylen;
58      svn_string_t *value;
59
60      /* Get this key and val. */
61      apr_hash_this(this, &key, &keylen, &val);
62      value = val;
63
64      /* Don't output properties equal to the ones in oldhash, if present. */
65      if (oldhash)
66        {
67          svn_string_t *oldvalue = apr_hash_get(oldhash, key, keylen);
68
69          if (oldvalue && svn_string_compare(value, oldvalue))
70            continue;
71        }
72
73      /* Output name length, then name. */
74
75      svn_stringbuf_appendcstr(*strbuf,
76                               apr_psprintf(pool, "K %" APR_SSIZE_T_FMT "\n",
77                                            keylen));
78
79      svn_stringbuf_appendbytes(*strbuf, (const char *) key, keylen);
80      svn_stringbuf_appendbytes(*strbuf, "\n", 1);
81
82      /* Output value length, then value. */
83
84      svn_stringbuf_appendcstr(*strbuf,
85                               apr_psprintf(pool, "V %" APR_SIZE_T_FMT "\n",
86                                            value->len));
87
88      svn_stringbuf_appendbytes(*strbuf, value->data, value->len);
89      svn_stringbuf_appendbytes(*strbuf, "\n", 1);
90    }
91
92  if (oldhash)
93    {
94      /* Output a "D " entry for each property in oldhash but not hash. */
95      for (this = apr_hash_first(pool, oldhash); this;
96           this = apr_hash_next(this))
97        {
98          const void *key;
99          apr_ssize_t keylen;
100
101          /* Get this key. */
102          apr_hash_this(this, &key, &keylen, NULL);
103
104          /* Only output values deleted in hash. */
105          if (apr_hash_get(hash, key, keylen))
106            continue;
107
108          /* Output name length, then name. */
109
110          svn_stringbuf_appendcstr(*strbuf,
111                                   apr_psprintf(pool,
112                                                "D %" APR_SSIZE_T_FMT "\n",
113                                                keylen));
114
115          svn_stringbuf_appendbytes(*strbuf, (const char *) key, keylen);
116          svn_stringbuf_appendbytes(*strbuf, "\n", 1);
117        }
118    }
119  svn_stringbuf_appendbytes(*strbuf, "PROPS-END\n", 10);
120}
121
122
123/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
124   store it into a new temporary file *TEMPFILE.  OLDROOT may be NULL,
125   in which case the delta will be computed against an empty file, as
126   per the svn_fs_get_file_delta_stream docstring.  Record the length
127   of the temporary file in *LEN, and rewind the file before
128   returning. */
129static svn_error_t *
130store_delta(apr_file_t **tempfile, svn_filesize_t *len,
131            svn_fs_root_t *oldroot, const char *oldpath,
132            svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
133{
134  svn_stream_t *temp_stream;
135  apr_off_t offset = 0;
136  svn_txdelta_stream_t *delta_stream;
137  svn_txdelta_window_handler_t wh;
138  void *whb;
139
140  /* Create a temporary file and open a stream to it. Note that we need
141     the file handle in order to rewind it. */
142  SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
143                                   svn_io_file_del_on_pool_cleanup,
144                                   pool, pool));
145  temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
146
147  /* Compute the delta and send it to the temporary file. */
148  SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
149                                       newroot, newpath, pool));
150  svn_txdelta_to_svndiff2(&wh, &whb, temp_stream, 0, pool);
151  SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
152
153  /* Get the length of the temporary file and rewind it. */
154  SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
155  *len = offset;
156  offset = 0;
157  return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
158}
159
160
161/*----------------------------------------------------------------------*/
162
163/** An editor which dumps node-data in 'dumpfile format' to a file. **/
164
165/* Look, mom!  No file batons! */
166
167struct edit_baton
168{
169  /* The path which implicitly prepends all full paths coming into
170     this editor.  This will almost always be "" or "/".  */
171  const char *path;
172
173  /* The stream to dump to. */
174  svn_stream_t *stream;
175
176  /* Send feedback here, if non-NULL */
177  svn_stream_t *feedback_stream;
178
179  /* The fs revision root, so we can read the contents of paths. */
180  svn_fs_root_t *fs_root;
181  svn_revnum_t current_rev;
182
183  /* True if dumped nodes should output deltas instead of full text. */
184  svn_boolean_t use_deltas;
185
186  /* True if this "dump" is in fact a verify. */
187  svn_boolean_t verify;
188
189  /* The first revision dumped in this dumpstream. */
190  svn_revnum_t oldest_dumped_rev;
191
192  /* reusable buffer for writing file contents */
193  char buffer[SVN__STREAM_CHUNK_SIZE];
194  apr_size_t bufsize;
195};
196
197struct dir_baton
198{
199  struct edit_baton *edit_baton;
200  struct dir_baton *parent_dir_baton;
201
202  /* is this directory a new addition to this revision? */
203  svn_boolean_t added;
204
205  /* has this directory been written to the output stream? */
206  svn_boolean_t written_out;
207
208  /* the absolute path to this directory */
209  const char *path;
210
211  /* the comparison path and revision of this directory.  if both of
212     these are valid, use them as a source against which to compare
213     the directory instead of the default comparison source of PATH in
214     the previous revision. */
215  const char *cmp_path;
216  svn_revnum_t cmp_rev;
217
218  /* hash of paths that need to be deleted, though some -might- be
219     replaced.  maps const char * paths to this dir_baton.  (they're
220     full paths, because that's what the editor driver gives us.  but
221     really, they're all within this directory.) */
222  apr_hash_t *deleted_entries;
223
224  /* pool to be used for deleting the hash items */
225  apr_pool_t *pool;
226};
227
228
229/* Make a directory baton to represent the directory was path
230   (relative to EDIT_BATON's path) is PATH.
231
232   CMP_PATH/CMP_REV are the path/revision against which this directory
233   should be compared for changes.  If either is omitted (NULL for the
234   path, SVN_INVALID_REVNUM for the rev), just compare this directory
235   PATH against itself in the previous revision.
236
237   PARENT_DIR_BATON is the directory baton of this directory's parent,
238   or NULL if this is the top-level directory of the edit.  ADDED
239   indicated if this directory is newly added in this revision.
240   Perform all allocations in POOL.  */
241static struct dir_baton *
242make_dir_baton(const char *path,
243               const char *cmp_path,
244               svn_revnum_t cmp_rev,
245               void *edit_baton,
246               void *parent_dir_baton,
247               svn_boolean_t added,
248               apr_pool_t *pool)
249{
250  struct edit_baton *eb = edit_baton;
251  struct dir_baton *pb = parent_dir_baton;
252  struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
253  const char *full_path;
254
255  /* A path relative to nothing?  I don't think so. */
256  SVN_ERR_ASSERT_NO_RETURN(!path || pb);
257
258  /* Construct the full path of this node. */
259  if (pb)
260    full_path = svn_path_join(eb->path, path, pool);
261  else
262    full_path = apr_pstrdup(pool, eb->path);
263
264  /* Remove leading slashes from copyfrom paths. */
265  if (cmp_path)
266    cmp_path = ((*cmp_path == '/') ? cmp_path + 1 : cmp_path);
267
268  new_db->edit_baton = eb;
269  new_db->parent_dir_baton = pb;
270  new_db->path = full_path;
271  new_db->cmp_path = cmp_path ? apr_pstrdup(pool, cmp_path) : NULL;
272  new_db->cmp_rev = cmp_rev;
273  new_db->added = added;
274  new_db->written_out = FALSE;
275  new_db->deleted_entries = apr_hash_make(pool);
276  new_db->pool = pool;
277
278  return new_db;
279}
280
281
282/* This helper is the main "meat" of the editor -- it does all the
283   work of writing a node record.
284
285   Write out a node record for PATH of type KIND under EB->FS_ROOT.
286   ACTION describes what is happening to the node (see enum svn_node_action).
287   Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
288
289   If the node was itself copied, IS_COPY is TRUE and the
290   path/revision of the copy source are in CMP_PATH/CMP_REV.  If
291   IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
292   of a copied subtree.
293  */
294static svn_error_t *
295dump_node(struct edit_baton *eb,
296          const char *path,    /* an absolute path. */
297          svn_node_kind_t kind,
298          enum svn_node_action action,
299          svn_boolean_t is_copy,
300          const char *cmp_path,
301          svn_revnum_t cmp_rev,
302          apr_pool_t *pool)
303{
304  svn_stringbuf_t *propstring;
305  svn_filesize_t content_length = 0;
306  apr_size_t len;
307  svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
308  const char *compare_path = path;
309  svn_revnum_t compare_rev = eb->current_rev - 1;
310  svn_fs_root_t *compare_root = NULL;
311  apr_file_t *delta_file = NULL;
312
313  /* Write out metadata headers for this file node. */
314  SVN_ERR(svn_stream_printf(eb->stream, pool,
315                            SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
316                            (*path == '/') ? path + 1 : path));
317  if (kind == svn_node_file)
318    SVN_ERR(svn_stream_printf(eb->stream, pool,
319                              SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
320  else if (kind == svn_node_dir)
321    SVN_ERR(svn_stream_printf(eb->stream, pool,
322                              SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
323
324  /* Remove leading slashes from copyfrom paths. */
325  if (cmp_path)
326    cmp_path = ((*cmp_path == '/') ? cmp_path + 1 : cmp_path);
327
328  /* Validate the comparison path/rev. */
329  if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
330    {
331      compare_path = cmp_path;
332      compare_rev = cmp_rev;
333    }
334
335  if (action == svn_node_action_change)
336    {
337      SVN_ERR(svn_stream_printf(eb->stream, pool,
338                                SVN_REPOS_DUMPFILE_NODE_ACTION
339                                ": change\n"));
340
341      /* either the text or props changed, or possibly both. */
342      SVN_ERR(svn_fs_revision_root(&compare_root,
343                                   svn_fs_root_fs(eb->fs_root),
344                                   compare_rev, pool));
345
346      SVN_ERR(svn_fs_props_changed(&must_dump_props,
347                                   compare_root, compare_path,
348                                   eb->fs_root, path, pool));
349      if (kind == svn_node_file)
350        SVN_ERR(svn_fs_contents_changed(&must_dump_text,
351                                        compare_root, compare_path,
352                                        eb->fs_root, path, pool));
353    }
354  else if (action == svn_node_action_replace)
355    {
356      if (! is_copy)
357        {
358          /* a simple delete+add, implied by a single 'replace' action. */
359          SVN_ERR(svn_stream_printf(eb->stream, pool,
360                                    SVN_REPOS_DUMPFILE_NODE_ACTION
361                                    ": replace\n"));
362
363          /* definitely need to dump all content for a replace. */
364          if (kind == svn_node_file)
365            must_dump_text = TRUE;
366          must_dump_props = TRUE;
367        }
368      else
369        {
370          /* more complex:  delete original, then add-with-history.  */
371
372          /* the path & kind headers have already been printed;  just
373             add a delete action, and end the current record.*/
374          SVN_ERR(svn_stream_printf(eb->stream, pool,
375                                    SVN_REPOS_DUMPFILE_NODE_ACTION
376                                    ": delete\n\n"));
377
378          /* recurse:  print an additional add-with-history record. */
379          SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
380                            is_copy, compare_path, compare_rev, pool));
381
382          /* we can leave this routine quietly now, don't need to dump
383             any content;  that was already done in the second record. */
384          must_dump_text = FALSE;
385          must_dump_props = FALSE;
386        }
387    }
388  else if (action == svn_node_action_delete)
389    {
390      SVN_ERR(svn_stream_printf(eb->stream, pool,
391                                SVN_REPOS_DUMPFILE_NODE_ACTION
392                                ": delete\n"));
393
394      /* we can leave this routine quietly now, don't need to dump
395         any content. */
396      must_dump_text = FALSE;
397      must_dump_props = FALSE;
398    }
399  else if (action == svn_node_action_add)
400    {
401      SVN_ERR(svn_stream_printf(eb->stream, pool,
402                                SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
403
404      if (! is_copy)
405        {
406          /* Dump all contents for a simple 'add'. */
407          if (kind == svn_node_file)
408            must_dump_text = TRUE;
409          must_dump_props = TRUE;
410        }
411      else
412        {
413          if (!eb->verify && cmp_rev < eb->oldest_dumped_rev)
414            SVN_ERR(svn_stream_printf
415                    (eb->feedback_stream, pool,
416                     _("WARNING: Referencing data in revision %ld,"
417                       " which is older than the oldest\n"
418                       "WARNING: dumped revision (%ld).  Loading this dump"
419                       " into an empty repository\n"
420                       "WARNING: will fail.\n"),
421                     cmp_rev, eb->oldest_dumped_rev));
422
423          SVN_ERR(svn_stream_printf(eb->stream, pool,
424                                    SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
425                                    ": %ld\n"
426                                    SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
427                                    ": %s\n",
428                                    cmp_rev, cmp_path));
429
430          SVN_ERR(svn_fs_revision_root(&compare_root,
431                                       svn_fs_root_fs(eb->fs_root),
432                                       compare_rev, pool));
433
434          /* Need to decide if the copied node had any extra textual or
435             property mods as well.  */
436          SVN_ERR(svn_fs_props_changed(&must_dump_props,
437                                       compare_root, compare_path,
438                                       eb->fs_root, path, pool));
439          if (kind == svn_node_file)
440            {
441              svn_checksum_t *checksum;
442              const char *hex_digest;
443              SVN_ERR(svn_fs_contents_changed(&must_dump_text,
444                                              compare_root, compare_path,
445                                              eb->fs_root, path, pool));
446
447              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
448                                           compare_root, compare_path,
449                                           TRUE, pool));
450              hex_digest = svn_checksum_to_cstring(checksum, pool);
451              if (hex_digest)
452                SVN_ERR(svn_stream_printf(eb->stream, pool,
453                                      SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5
454                                      ": %s\n", hex_digest));
455
456              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
457                                           compare_root, compare_path,
458                                           TRUE, pool));
459              hex_digest = svn_checksum_to_cstring(checksum, pool);
460              if (hex_digest)
461                SVN_ERR(svn_stream_printf(eb->stream, pool,
462                                      SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1
463                                      ": %s\n", hex_digest));
464            }
465        }
466    }
467
468  if ((! must_dump_text) && (! must_dump_props))
469    {
470      /* If we're not supposed to dump text or props, so be it, we can
471         just go home.  However, if either one needs to be dumped,
472         then our dumpstream format demands that at a *minimum*, we
473         see a lone "PROPS-END" as a divider between text and props
474         content within the content-block. */
475      len = 2;
476      return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
477    }
478
479  /*** Start prepping content to dump... ***/
480
481  /* If we are supposed to dump properties, write out a property
482     length header and generate a stringbuf that contains those
483     property values here. */
484  if (must_dump_props)
485    {
486      apr_hash_t *prophash, *oldhash = NULL;
487      apr_size_t proplen;
488
489      SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
490      if (eb->use_deltas && compare_root)
491        {
492          /* Fetch the old property hash to diff against and output a header
493             saying that our property contents are a delta. */
494          SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
495                                       pool));
496          SVN_ERR(svn_stream_printf(eb->stream, pool,
497                                    SVN_REPOS_DUMPFILE_PROP_DELTA
498                                    ": true\n"));
499        }
500      write_hash_to_stringbuf(prophash, oldhash, &propstring, pool);
501      proplen = propstring->len;
502      content_length += proplen;
503      SVN_ERR(svn_stream_printf(eb->stream, pool,
504                                SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
505                                ": %" APR_SIZE_T_FMT "\n", proplen));
506    }
507
508  /* If we are supposed to dump text, write out a text length header
509     here, and an MD5 checksum (if available). */
510  if (must_dump_text && (kind == svn_node_file))
511    {
512      svn_checksum_t *checksum;
513      const char *hex_digest;
514      svn_filesize_t textlen;
515
516      if (eb->use_deltas)
517        {
518          /* Compute the text delta now and write it into a temporary
519             file, so that we can find its length.  Output a header
520             saying our text contents are a delta. */
521          SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
522                              compare_path, eb->fs_root, path, pool));
523          SVN_ERR(svn_stream_printf(eb->stream, pool,
524                                    SVN_REPOS_DUMPFILE_TEXT_DELTA
525                                    ": true\n"));
526
527          if (compare_root)
528            {
529              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
530                                           compare_root, compare_path,
531                                           TRUE, pool));
532              hex_digest = svn_checksum_to_cstring(checksum, pool);
533              if (hex_digest)
534                SVN_ERR(svn_stream_printf(eb->stream, pool,
535                                          SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
536                                          ": %s\n", hex_digest));
537
538              SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
539                                           compare_root, compare_path,
540                                           TRUE, pool));
541              hex_digest = svn_checksum_to_cstring(checksum, pool);
542              if (hex_digest)
543                SVN_ERR(svn_stream_printf(eb->stream, pool,
544                                      SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1
545                                      ": %s\n", hex_digest));
546            }
547        }
548      else
549        {
550          /* Just fetch the length of the file. */
551          SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
552        }
553
554      content_length += textlen;
555      SVN_ERR(svn_stream_printf(eb->stream, pool,
556                                SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
557                                ": %" SVN_FILESIZE_T_FMT "\n", textlen));
558
559      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
560                                   eb->fs_root, path, TRUE, pool));
561      hex_digest = svn_checksum_to_cstring(checksum, pool);
562      if (hex_digest)
563        SVN_ERR(svn_stream_printf(eb->stream, pool,
564                                  SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
565                                  ": %s\n", hex_digest));
566
567      SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
568                                   eb->fs_root, path, TRUE, pool));
569      hex_digest = svn_checksum_to_cstring(checksum, pool);
570      if (hex_digest)
571        SVN_ERR(svn_stream_printf(eb->stream, pool,
572                                  SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1
573                                  ": %s\n", hex_digest));
574    }
575
576  /* 'Content-length:' is the last header before we dump the content,
577     and is the sum of the text and prop contents lengths.  We write
578     this only for the benefit of non-Subversion RFC-822 parsers. */
579  SVN_ERR(svn_stream_printf(eb->stream, pool,
580                            SVN_REPOS_DUMPFILE_CONTENT_LENGTH
581                            ": %" SVN_FILESIZE_T_FMT "\n\n",
582                            content_length));
583
584  /* Dump property content if we're supposed to do so. */
585  if (must_dump_props)
586    {
587      len = propstring->len;
588      SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len));
589    }
590
591  /* Dump text content */
592  if (must_dump_text && (kind == svn_node_file))
593    {
594      svn_stream_t *contents;
595
596      if (delta_file)
597        {
598          /* Make sure to close the underlying file when the stream is
599             closed. */
600          contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
601        }
602      else
603        SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
604
605      SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
606                               NULL, NULL, pool));
607    }
608
609  len = 2;
610  return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
611}
612
613
614static svn_error_t *
615open_root(void *edit_baton,
616          svn_revnum_t base_revision,
617          apr_pool_t *pool,
618          void **root_baton)
619{
620  *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
621                               edit_baton, NULL, FALSE, pool);
622  return SVN_NO_ERROR;
623}
624
625
626static svn_error_t *
627delete_entry(const char *path,
628             svn_revnum_t revision,
629             void *parent_baton,
630             apr_pool_t *pool)
631{
632  struct dir_baton *pb = parent_baton;
633  const char *mypath = apr_pstrdup(pb->pool, path);
634
635  /* remember this path needs to be deleted. */
636  apr_hash_set(pb->deleted_entries, mypath, APR_HASH_KEY_STRING, pb);
637
638  return SVN_NO_ERROR;
639}
640
641
642static svn_error_t *
643add_directory(const char *path,
644              void *parent_baton,
645              const char *copyfrom_path,
646              svn_revnum_t copyfrom_rev,
647              apr_pool_t *pool,
648              void **child_baton)
649{
650  struct dir_baton *pb = parent_baton;
651  struct edit_baton *eb = pb->edit_baton;
652  void *val;
653  svn_boolean_t is_copy = FALSE;
654  struct dir_baton *new_db
655    = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
656
657  /* This might be a replacement -- is the path already deleted? */
658  val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
659
660  /* Detect an add-with-history. */
661  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
662
663  /* Dump the node. */
664  SVN_ERR(dump_node(eb, path,
665                    svn_node_dir,
666                    val ? svn_node_action_replace : svn_node_action_add,
667                    is_copy,
668                    is_copy ? copyfrom_path : NULL,
669                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
670                    pool));
671
672  if (val)
673    /* Delete the path, it's now been dumped. */
674    apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
675
676  new_db->written_out = TRUE;
677
678  *child_baton = new_db;
679  return SVN_NO_ERROR;
680}
681
682
683static svn_error_t *
684open_directory(const char *path,
685               void *parent_baton,
686               svn_revnum_t base_revision,
687               apr_pool_t *pool,
688               void **child_baton)
689{
690  struct dir_baton *pb = parent_baton;
691  struct edit_baton *eb = pb->edit_baton;
692  struct dir_baton *new_db;
693  const char *cmp_path = NULL;
694  svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
695
696  /* If the parent directory has explicit comparison path and rev,
697     record the same for this one. */
698  if (pb && ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
699    {
700      cmp_path = svn_path_join(pb->cmp_path,
701                               svn_path_basename(path, pool), pool);
702      cmp_rev = pb->cmp_rev;
703    }
704
705  new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool);
706  *child_baton = new_db;
707  return SVN_NO_ERROR;
708}
709
710
711static svn_error_t *
712close_directory(void *dir_baton,
713                apr_pool_t *pool)
714{
715  struct dir_baton *db = dir_baton;
716  struct edit_baton *eb = db->edit_baton;
717  apr_hash_index_t *hi;
718  apr_pool_t *subpool = svn_pool_create(pool);
719
720  for (hi = apr_hash_first(pool, db->deleted_entries);
721       hi;
722       hi = apr_hash_next(hi))
723    {
724      const void *key;
725      const char *path;
726      apr_hash_this(hi, &key, NULL, NULL);
727      path = key;
728
729      svn_pool_clear(subpool);
730
731      /* By sending 'svn_node_unknown', the Node-kind: header simply won't
732         be written out.  No big deal at all, really.  The loader
733         shouldn't care.  */
734      SVN_ERR(dump_node(eb, path,
735                        svn_node_unknown, svn_node_action_delete,
736                        FALSE, NULL, SVN_INVALID_REVNUM, subpool));
737    }
738
739  svn_pool_destroy(subpool);
740  return SVN_NO_ERROR;
741}
742
743
744static svn_error_t *
745add_file(const char *path,
746         void *parent_baton,
747         const char *copyfrom_path,
748         svn_revnum_t copyfrom_rev,
749         apr_pool_t *pool,
750         void **file_baton)
751{
752  struct dir_baton *pb = parent_baton;
753  struct edit_baton *eb = pb->edit_baton;
754  void *val;
755  svn_boolean_t is_copy = FALSE;
756
757  /* This might be a replacement -- is the path already deleted? */
758  val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
759
760  /* Detect add-with-history. */
761  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
762
763  /* Dump the node. */
764  SVN_ERR(dump_node(eb, path,
765                    svn_node_file,
766                    val ? svn_node_action_replace : svn_node_action_add,
767                    is_copy,
768                    is_copy ? copyfrom_path : NULL,
769                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
770                    pool));
771
772  if (val)
773    /* delete the path, it's now been dumped. */
774    apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
775
776  *file_baton = NULL;  /* muhahahaha */
777  return SVN_NO_ERROR;
778}
779
780
781static svn_error_t *
782open_file(const char *path,
783          void *parent_baton,
784          svn_revnum_t ancestor_revision,
785          apr_pool_t *pool,
786          void **file_baton)
787{
788  struct dir_baton *pb = parent_baton;
789  struct edit_baton *eb = pb->edit_baton;
790  const char *cmp_path = NULL;
791  svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
792
793  /* If the parent directory has explicit comparison path and rev,
794     record the same for this one. */
795  if (pb && ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
796    {
797      cmp_path = svn_path_join(pb->cmp_path,
798                               svn_path_basename(path, pool), pool);
799      cmp_rev = pb->cmp_rev;
800    }
801
802  SVN_ERR(dump_node(eb, path,
803                    svn_node_file, svn_node_action_change,
804                    FALSE, cmp_path, cmp_rev, pool));
805
806  *file_baton = NULL;  /* muhahahaha again */
807  return SVN_NO_ERROR;
808}
809
810
811static svn_error_t *
812change_dir_prop(void *parent_baton,
813                const char *name,
814                const svn_string_t *value,
815                apr_pool_t *pool)
816{
817  struct dir_baton *db = parent_baton;
818  struct edit_baton *eb = db->edit_baton;
819
820  /* This function is what distinguishes between a directory that is
821     opened to merely get somewhere, vs. one that is opened because it
822     *actually* changed by itself.  */
823  if (! db->written_out)
824    {
825      SVN_ERR(dump_node(eb, db->path,
826                        svn_node_dir, svn_node_action_change,
827                        FALSE, db->cmp_path, db->cmp_rev, pool));
828      db->written_out = TRUE;
829    }
830  return SVN_NO_ERROR;
831}
832
833
834
835static svn_error_t *
836get_dump_editor(const svn_delta_editor_t **editor,
837                void **edit_baton,
838                svn_fs_t *fs,
839                svn_revnum_t to_rev,
840                const char *root_path,
841                svn_stream_t *stream,
842                svn_stream_t *feedback_stream,
843                svn_revnum_t oldest_dumped_rev,
844                svn_boolean_t use_deltas,
845                svn_boolean_t verify,
846                apr_pool_t *pool)
847{
848  /* Allocate an edit baton to be stored in every directory baton.
849     Set it up for the directory baton we create here, which is the
850     root baton. */
851  struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
852  svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
853
854  /* Set up the edit baton. */
855  eb->stream = stream;
856  eb->feedback_stream = feedback_stream;
857  eb->oldest_dumped_rev = oldest_dumped_rev;
858  eb->bufsize = sizeof(eb->buffer);
859  eb->path = apr_pstrdup(pool, root_path);
860  SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
861  eb->current_rev = to_rev;
862  eb->use_deltas = use_deltas;
863  eb->verify = verify;
864
865  /* Set up the editor. */
866  dump_editor->open_root = open_root;
867  dump_editor->delete_entry = delete_entry;
868  dump_editor->add_directory = add_directory;
869  dump_editor->open_directory = open_directory;
870  dump_editor->close_directory = close_directory;
871  dump_editor->change_dir_prop = change_dir_prop;
872  dump_editor->add_file = add_file;
873  dump_editor->open_file = open_file;
874
875  *edit_baton = eb;
876  *editor = dump_editor;
877
878  return SVN_NO_ERROR;
879}
880
881/*----------------------------------------------------------------------*/
882
883/** The main dumping routine, svn_repos_dump_fs. **/
884
885
886/* Helper for svn_repos_dump_fs.
887
888   Write a revision record of REV in FS to writable STREAM, using POOL.
889 */
890static svn_error_t *
891write_revision_record(svn_stream_t *stream,
892                      svn_fs_t *fs,
893                      svn_revnum_t rev,
894                      apr_pool_t *pool)
895{
896  apr_size_t len;
897  apr_hash_t *props;
898  svn_stringbuf_t *encoded_prophash;
899  apr_time_t timetemp;
900  svn_string_t *datevalue;
901
902  /* Read the revision props even if we're aren't going to dump
903     them for verification purposes */
904  SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
905
906  /* Run revision date properties through the time conversion to
907     canonicalize them. */
908  /* ### Remove this when it is no longer needed for sure. */
909  datevalue = apr_hash_get(props, SVN_PROP_REVISION_DATE,
910                           APR_HASH_KEY_STRING);
911  if (datevalue)
912    {
913      SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
914      datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
915                                    pool);
916      apr_hash_set(props, SVN_PROP_REVISION_DATE, APR_HASH_KEY_STRING,
917                   datevalue);
918    }
919
920  write_hash_to_stringbuf(props, NULL, &encoded_prophash, pool);
921
922  /* ### someday write a revision-content-checksum */
923
924  SVN_ERR(svn_stream_printf(stream, pool,
925                            SVN_REPOS_DUMPFILE_REVISION_NUMBER
926                            ": %ld\n", rev));
927  SVN_ERR(svn_stream_printf(stream, pool,
928                            SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
929                            ": %" APR_SIZE_T_FMT "\n",
930                            encoded_prophash->len));
931
932  /* Write out a regular Content-length header for the benefit of
933     non-Subversion RFC-822 parsers. */
934  SVN_ERR(svn_stream_printf(stream, pool,
935                            SVN_REPOS_DUMPFILE_CONTENT_LENGTH
936                            ": %" APR_SIZE_T_FMT "\n\n",
937                            encoded_prophash->len));
938
939  len = encoded_prophash->len;
940  SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len));
941
942  len = 1;
943  return svn_stream_write(stream, "\n", &len);
944}
945
946
947
948/* The main dumper. */
949svn_error_t *
950svn_repos_dump_fs2(svn_repos_t *repos,
951                   svn_stream_t *stream,
952                   svn_stream_t *feedback_stream,
953                   svn_revnum_t start_rev,
954                   svn_revnum_t end_rev,
955                   svn_boolean_t incremental,
956                   svn_boolean_t use_deltas,
957                   svn_cancel_func_t cancel_func,
958                   void *cancel_baton,
959                   apr_pool_t *pool)
960{
961  const svn_delta_editor_t *dump_editor;
962  void *dump_edit_baton;
963  svn_revnum_t i;
964  svn_fs_t *fs = svn_repos_fs(repos);
965  apr_pool_t *subpool = svn_pool_create(pool);
966  svn_revnum_t youngest;
967  const char *uuid;
968  int version;
969  svn_boolean_t dumping = (stream != NULL);
970
971  /* Determine the current youngest revision of the filesystem. */
972  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
973
974  /* Use default vals if necessary. */
975  if (! SVN_IS_VALID_REVNUM(start_rev))
976    start_rev = 0;
977  if (! SVN_IS_VALID_REVNUM(end_rev))
978    end_rev = youngest;
979  if (! stream)
980    stream = svn_stream_empty(pool);
981  if (! feedback_stream)
982    feedback_stream = svn_stream_empty(pool);
983
984  /* Validate the revisions. */
985  if (start_rev > end_rev)
986    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
987                             _("Start revision %ld"
988                               " is greater than end revision %ld"),
989                             start_rev, end_rev);
990  if (end_rev > youngest)
991    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
992                             _("End revision %ld is invalid "
993                               "(youngest revision is %ld)"),
994                             end_rev, youngest);
995  if ((start_rev == 0) && incremental)
996    incremental = FALSE; /* revision 0 looks the same regardless of
997                            whether or not this is an incremental
998                            dump, so just simplify things. */
999
1000  /* Write out the UUID. */
1001  SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
1002
1003  /* If we're not using deltas, use the previous version, for
1004     compatibility with svn 1.0.x. */
1005  version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
1006  if (!use_deltas)
1007    version--;
1008
1009  /* Write out "general" metadata for the dumpfile.  In this case, a
1010     magic header followed by a dumpfile format version. */
1011  SVN_ERR(svn_stream_printf(stream, pool,
1012                            SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
1013                            version));
1014  SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
1015                            ": %s\n\n", uuid));
1016
1017  /* Main loop:  we're going to dump revision i.  */
1018  for (i = start_rev; i <= end_rev; i++)
1019    {
1020      svn_revnum_t from_rev, to_rev;
1021      svn_fs_root_t *to_root;
1022      svn_boolean_t use_deltas_for_rev;
1023
1024      svn_pool_clear(subpool);
1025
1026      /* Check for cancellation. */
1027      if (cancel_func)
1028        SVN_ERR(cancel_func(cancel_baton));
1029
1030      /* Special-case the initial revision dump: it needs to contain
1031         *all* nodes, because it's the foundation of all future
1032         revisions in the dumpfile. */
1033      if ((i == start_rev) && (! incremental))
1034        {
1035          /* Special-special-case a dump of revision 0. */
1036          if (i == 0)
1037            {
1038              /* Just write out the one revision 0 record and move on.
1039                 The parser might want to use its properties. */
1040              SVN_ERR(write_revision_record(stream, fs, 0, subpool));
1041              to_rev = 0;
1042              goto loop_end;
1043            }
1044
1045          /* Compare START_REV to revision 0, so that everything
1046             appears to be added.  */
1047          from_rev = 0;
1048          to_rev = i;
1049        }
1050      else
1051        {
1052          /* In the normal case, we want to compare consecutive revs. */
1053          from_rev = i - 1;
1054          to_rev = i;
1055        }
1056
1057      /* Write the revision record. */
1058      SVN_ERR(write_revision_record(stream, fs, to_rev, subpool));
1059
1060      /* Fetch the editor which dumps nodes to a file.  Regardless of
1061         what we've been told, don't use deltas for the first rev of a
1062         non-incremental dump. */
1063      use_deltas_for_rev = use_deltas && (incremental || i != start_rev);
1064      SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev,
1065                              "/", stream, feedback_stream, start_rev,
1066                              use_deltas_for_rev, FALSE, subpool));
1067
1068      /* Drive the editor in one way or another. */
1069      SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool));
1070
1071      /* If this is the first revision of a non-incremental dump,
1072         we're in for a full tree dump.  Otherwise, we want to simply
1073         replay the revision.  */
1074      if ((i == start_rev) && (! incremental))
1075        {
1076          svn_fs_root_t *from_root;
1077          SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool));
1078          SVN_ERR(svn_repos_dir_delta2(from_root, "/", "",
1079                                       to_root, "/",
1080                                       dump_editor, dump_edit_baton,
1081                                       NULL,
1082                                       NULL,
1083                                       FALSE, /* don't send text-deltas */
1084                                       svn_depth_infinity,
1085                                       FALSE, /* don't send entry props */
1086                                       FALSE, /* don't ignore ancestry */
1087                                       subpool));
1088        }
1089      else
1090        {
1091          SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1092                                    dump_editor, dump_edit_baton,
1093                                    NULL, NULL, subpool));
1094        }
1095
1096    loop_end:
1097      SVN_ERR(svn_stream_printf(feedback_stream, pool,
1098                                dumping
1099                                ? _("* Dumped revision %ld.\n")
1100                                : _("* Verified revision %ld.\n"),
1101                                to_rev));
1102    }
1103
1104  svn_pool_destroy(subpool);
1105
1106  return SVN_NO_ERROR;
1107}
1108
1109
1110/*----------------------------------------------------------------------*/
1111
1112/* verify, based on dump */
1113
1114
1115/* Creating a new revision that changes /A/B/E/bravo means creating new
1116   directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
1117   each entry not changed in the new revision a link back to the entry in a
1118   previous revision.  svn_repos_replay()ing a revision does not verify that
1119   those links are correct.
1120
1121   For paths actually changed in the revision we verify, we get directory
1122   contents or file length twice: once in the dump editor, and once here.
1123   We could create a new verify baton, store in it the changed paths, and
1124   skip those here, but that means building an entire wrapper editor and
1125   managing two levels of batons.  The impact from checking these entries
1126   twice should be minimal, while the code to avoid it is not.
1127*/
1128
1129static svn_error_t *
1130verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
1131                       void *val, apr_pool_t *pool)
1132{
1133  struct dir_baton *db = baton;
1134  char *path = svn_path_join(db->path, (const char *)key, pool);
1135  svn_node_kind_t kind;
1136  apr_hash_t *dirents;
1137  svn_filesize_t len;
1138
1139  SVN_ERR(svn_fs_check_path(&kind, db->edit_baton->fs_root, path, pool));
1140  switch (kind) {
1141  case svn_node_dir:
1142    /* Getting this directory's contents is enough to ensure that our
1143       link to it is correct. */
1144    SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool));
1145    break;
1146  case svn_node_file:
1147    /* Getting this file's size is enough to ensure that our link to it
1148       is correct. */
1149    SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool));
1150    break;
1151  default:
1152    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1153                             _("Unexpected node kind %d for '%s'"), kind, path);
1154  }
1155
1156  return SVN_NO_ERROR;
1157}
1158
1159static svn_error_t *
1160verify_close_directory(void *dir_baton,
1161                apr_pool_t *pool)
1162{
1163  struct dir_baton *db = dir_baton;
1164  apr_hash_t *dirents;
1165  SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
1166                             db->path, pool));
1167  SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
1168                            dir_baton, pool));
1169  return close_directory(dir_baton, pool);
1170}
1171
1172svn_error_t *
1173svn_repos_verify_fs(svn_repos_t *repos,
1174                    svn_stream_t *feedback_stream,
1175                    svn_revnum_t start_rev,
1176                    svn_revnum_t end_rev,
1177                    svn_cancel_func_t cancel_func,
1178                    void *cancel_baton,
1179                    apr_pool_t *pool)
1180{
1181  svn_fs_t *fs = svn_repos_fs(repos);
1182  svn_revnum_t youngest;
1183  svn_revnum_t rev;
1184  apr_pool_t *iterpool = svn_pool_create(pool);
1185
1186  /* Determine the current youngest revision of the filesystem. */
1187  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1188
1189  /* Use default vals if necessary. */
1190  if (! SVN_IS_VALID_REVNUM(start_rev))
1191    start_rev = 0;
1192  if (! SVN_IS_VALID_REVNUM(end_rev))
1193    end_rev = youngest;
1194  if (! feedback_stream)
1195    feedback_stream = svn_stream_empty(pool);
1196
1197  /* Validate the revisions. */
1198  if (start_rev > end_rev)
1199    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1200                             _("Start revision %ld"
1201                               " is greater than end revision %ld"),
1202                             start_rev, end_rev);
1203  if (end_rev > youngest)
1204    return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1205                             _("End revision %ld is invalid "
1206                               "(youngest revision is %ld)"),
1207                             end_rev, youngest);
1208
1209  for (rev = start_rev; rev <= end_rev; rev++)
1210    {
1211      svn_delta_editor_t *dump_editor;
1212      void *dump_edit_baton;
1213      const svn_delta_editor_t *cancel_editor;
1214      void *cancel_edit_baton;
1215      svn_fs_root_t *to_root;
1216
1217      svn_pool_clear(iterpool);
1218
1219      /* Get cancellable dump editor, but with our close_directory handler. */
1220      SVN_ERR(get_dump_editor((const svn_delta_editor_t **)&dump_editor,
1221                              &dump_edit_baton, fs, rev, "",
1222                              svn_stream_empty(pool), feedback_stream,
1223                              start_rev,
1224                              FALSE, TRUE, /* use_deltas, verify */
1225                              iterpool));
1226      dump_editor->close_directory = verify_close_directory;
1227      SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1228                                                dump_editor, dump_edit_baton,
1229                                                &cancel_editor,
1230                                                &cancel_edit_baton,
1231                                                iterpool));
1232
1233      SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
1234      SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1235                                cancel_editor, cancel_edit_baton,
1236                                NULL, NULL, iterpool));
1237      SVN_ERR(svn_stream_printf(feedback_stream, iterpool,
1238                                _("* Verified revision %ld.\n"),
1239                                rev));
1240    }
1241
1242  svn_pool_destroy(iterpool);
1243
1244  return SVN_NO_ERROR;
1245}
Note: See TracBrowser for help on using the repository browser.