1 /* load.c --- parsing a 'dumpfile'-formatted stream.
3 * ====================================================================
4 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
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.
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 * ====================================================================
19 #include "svn_private_config.h"
20 #include "svn_pools.h"
21 #include "svn_error.h"
23 #include "svn_repos.h"
24 #include "svn_string.h"
26 #include "svn_props.h"
28 #include "svn_private_config.h"
29 #include "svn_mergeinfo.h"
35 /*----------------------------------------------------------------------*/
37 /** Batons used herein **/
44 svn_boolean_t use_history
;
45 svn_boolean_t use_pre_commit_hook
;
46 svn_boolean_t use_post_commit_hook
;
47 svn_stream_t
*outstream
;
48 enum svn_repos_load_uuid uuid_action
;
49 const char *parent_dir
;
59 svn_fs_root_t
*txn_root
;
61 const svn_string_t
*datestamp
;
63 apr_int32_t rev_offset
;
65 struct parse_baton
*pb
;
73 enum svn_node_action action
;
74 const char *base_checksum
; /* null, if not available */
75 const char *result_checksum
; /* null, if not available */
76 const char *copy_source_checksum
; /* null, if not available */
78 svn_revnum_t copyfrom_rev
;
79 const char *copyfrom_path
;
81 struct revision_baton
*rb
;
87 /*----------------------------------------------------------------------*/
89 /** A conversion function between the two vtable types. **/
90 static svn_repos_parse_fns2_t
*
91 fns2_from_fns(const svn_repos_parser_fns_t
*fns
,
94 svn_repos_parse_fns2_t
*fns2
;
96 fns2
= apr_palloc(pool
, sizeof(*fns2
));
97 fns2
->new_revision_record
= fns
->new_revision_record
;
98 fns2
->uuid_record
= fns
->uuid_record
;
99 fns2
->new_node_record
= fns
->new_node_record
;
100 fns2
->set_revision_property
= fns
->set_revision_property
;
101 fns2
->set_node_property
= fns
->set_node_property
;
102 fns2
->remove_node_props
= fns
->remove_node_props
;
103 fns2
->set_fulltext
= fns
->set_fulltext
;
104 fns2
->close_node
= fns
->close_node
;
105 fns2
->close_revision
= fns
->close_revision
;
106 fns2
->delete_node_property
= NULL
;
107 fns2
->apply_textdelta
= NULL
;
112 /*----------------------------------------------------------------------*/
114 /** The parser and related helper funcs **/
120 return svn_error_create(SVN_ERR_INCOMPLETE_DATA
, NULL
,
121 _("Premature end of content data in dumpstream"));
125 stream_malformed(void)
127 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
128 _("Dumpstream data appears to be malformed"));
131 /* Allocate a new hash *HEADERS in POOL, and read a series of
132 RFC822-style headers from STREAM. Duplicate each header's name and
133 value into POOL and store in hash as a const char * ==> const char *.
135 The headers are assumed to be terminated by a single blank line,
136 which will be permanently sucked from the stream and tossed.
138 If the caller has already read in the first header line, it should
139 be passed in as FIRST_HEADER. If not, pass NULL instead.
142 read_header_block(svn_stream_t
*stream
,
143 svn_stringbuf_t
*first_header
,
144 apr_hash_t
**headers
,
147 *headers
= apr_hash_make(pool
);
151 svn_stringbuf_t
*header_str
;
152 const char *name
, *value
;
156 if (first_header
!= NULL
)
158 header_str
= first_header
;
159 first_header
= NULL
; /* so we never visit this block again. */
164 /* Read the next line into a stringbuf. */
165 SVN_ERR(svn_stream_readline(stream
, &header_str
, "\n", &eof
, pool
));
167 if (svn_stringbuf_isempty(header_str
))
168 break; /* end of header block */
170 return stream_ran_dry();
172 /* Find the next colon in the stringbuf. */
173 while (header_str
->data
[i
] != ':')
175 if (header_str
->data
[i
] == '\0')
176 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
177 _("Dump stream contains a malformed "
178 "header (with no ':') at '%.20s'"),
182 /* Create a 'name' string and point to it. */
183 header_str
->data
[i
] = '\0';
184 name
= header_str
->data
;
186 /* Skip over the NULL byte and the space following it. */
188 if (i
> header_str
->len
)
189 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
190 _("Dump stream contains a malformed "
191 "header (with no value) at '%.20s'"),
194 /* Point to the 'value' string. */
195 value
= header_str
->data
+ i
;
197 /* Store name/value in hash. */
198 apr_hash_set(*headers
, name
, APR_HASH_KEY_STRING
, value
);
205 /* Set *PBUF to a string of length LEN, allocated in POOL, read from STREAM.
206 Also read a newline from STREAM and increase *ACTUAL_LEN by the total
207 number of bytes read from STREAM. */
209 read_key_or_val(char **pbuf
,
210 svn_filesize_t
*actual_length
,
211 svn_stream_t
*stream
,
215 char *buf
= apr_pcalloc(pool
, len
+ 1);
220 SVN_ERR(svn_stream_read(stream
, buf
, &numread
));
221 *actual_length
+= numread
;
223 return stream_ran_dry();
226 /* Suck up extra newline after key data */
228 SVN_ERR(svn_stream_read(stream
, &c
, &numread
));
229 *actual_length
+= numread
;
231 return stream_ran_dry();
233 return stream_malformed();
239 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
240 return it in *MERGEINFO_VAL. */
242 prefix_mergeinfo_paths(const char **mergeinfo_val
, const char *mergeinfo_orig
,
243 const char *parent_dir
, apr_pool_t
*pool
)
245 apr_hash_t
*prefixed_mergeinfo
, *mergeinfo
;
246 apr_hash_index_t
*hi
;
247 svn_stringbuf_t
*merge_val
;
249 const void *merge_source
;
252 SVN_ERR(svn_mergeinfo_parse(&mergeinfo
, mergeinfo_orig
, pool
));
253 prefixed_mergeinfo
= apr_hash_make(pool
);
254 for (hi
= apr_hash_first(NULL
, mergeinfo
); hi
; hi
= apr_hash_next(hi
))
256 apr_hash_this(hi
, &merge_source
, NULL
, &rangelist
);
257 path
= svn_path_join(parent_dir
, (const char*)merge_source
+1, pool
);
258 apr_hash_set(prefixed_mergeinfo
, path
, APR_HASH_KEY_STRING
, rangelist
);
260 svn_mergeinfo_to_stringbuf(&merge_val
, prefixed_mergeinfo
, pool
);
261 *mergeinfo_val
= merge_val
->data
;
266 /* Read CONTENT_LENGTH bytes from STREAM, parsing the bytes as an
267 encoded Subversion properties hash, and making multiple calls to
268 PARSE_FNS->set_*_property on RECORD_BATON (depending on the value
271 Set *ACTUAL_LENGTH to the number of bytes consumed from STREAM.
272 If an error is returned, the value of *ACTUAL_LENGTH is undefined.
274 Use POOL for all allocations. */
276 parse_property_block(svn_stream_t
*stream
,
277 svn_filesize_t content_length
,
278 const svn_repos_parse_fns2_t
*parse_fns
,
280 svn_boolean_t is_node
,
281 svn_filesize_t
*actual_length
,
284 svn_stringbuf_t
*strbuf
;
285 apr_pool_t
*proppool
= svn_pool_create(pool
);
288 while (content_length
!= *actual_length
)
290 char *buf
; /* a pointer into the stringbuf's data */
293 svn_pool_clear(proppool
);
295 /* Read a key length line. (Actually, it might be PROPS_END). */
296 SVN_ERR(svn_stream_readline(stream
, &strbuf
, "\n", &eof
, proppool
));
300 /* We could just use stream_ran_dry() or stream_malformed(),
301 but better to give a non-generic property block error. */
302 return svn_error_create
303 (SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
304 _("Incomplete or unterminated property block"));
307 *actual_length
+= (strbuf
->len
+ 1); /* +1 because we read a \n too. */
310 if (! strcmp(buf
, "PROPS-END"))
311 break; /* no more properties. */
313 else if ((buf
[0] == 'K') && (buf
[1] == ' '))
317 SVN_ERR(read_key_or_val(&keybuf
, actual_length
,
318 stream
, atoi(buf
+ 2), proppool
));
320 /* Read a val length line */
321 SVN_ERR(svn_stream_readline(stream
, &strbuf
, "\n", &eof
, proppool
));
323 return stream_ran_dry();
325 *actual_length
+= (strbuf
->len
+ 1); /* +1 because we read \n too */
328 if ((buf
[0] == 'V') && (buf
[1] == ' '))
330 svn_string_t propstring
;
333 propstring
.len
= atoi(buf
+ 2);
334 SVN_ERR(read_key_or_val(&valbuf
, actual_length
,
335 stream
, propstring
.len
, proppool
));
336 propstring
.data
= valbuf
;
338 /* Now, send the property pair to the vtable! */
340 SVN_ERR(parse_fns
->set_node_property(record_baton
,
344 SVN_ERR(parse_fns
->set_revision_property(record_baton
,
349 return stream_malformed(); /* didn't find expected 'V' line */
351 else if ((buf
[0] == 'D') && (buf
[1] == ' '))
355 SVN_ERR(read_key_or_val(&keybuf
, actual_length
,
356 stream
, atoi(buf
+ 2), proppool
));
358 /* We don't expect these in revision properties, and if we see
359 one when we don't have a delete_node_property callback,
360 then we're seeing a v3 feature in a v2 dump. */
361 if (!is_node
|| !parse_fns
->delete_node_property
)
362 return stream_malformed();
364 SVN_ERR(parse_fns
->delete_node_property(record_baton
, keybuf
));
367 return stream_malformed(); /* didn't find expected 'K' line */
371 svn_pool_destroy(proppool
);
376 /* Read CONTENT_LENGTH bytes from STREAM, and use
377 PARSE_FNS->set_fulltext to push those bytes as replace fulltext for
378 a node. Use BUFFER/BUFLEN to push the fulltext in "chunks".
380 Use POOL for all allocations. */
382 parse_text_block(svn_stream_t
*stream
,
383 svn_filesize_t content_length
,
384 svn_boolean_t is_delta
,
385 const svn_repos_parse_fns2_t
*parse_fns
,
391 svn_stream_t
*text_stream
= NULL
;
392 apr_size_t num_to_read
, rlen
, wlen
;
396 svn_txdelta_window_handler_t wh
;
399 SVN_ERR(parse_fns
->apply_textdelta(&wh
, &whb
, record_baton
));
401 text_stream
= svn_txdelta_parse_svndiff(wh
, whb
, TRUE
, pool
);
405 /* Get a stream to which we can push the data. */
406 SVN_ERR(parse_fns
->set_fulltext(&text_stream
, record_baton
));
409 /* If there are no contents to read, just write an empty buffer
410 through our callback. */
411 if (content_length
== 0)
415 SVN_ERR(svn_stream_write(text_stream
, "", &wlen
));
418 /* Regardless of whether or not we have a sink for our data, we
420 while (content_length
)
422 if (content_length
>= buflen
)
425 rlen
= (apr_size_t
) content_length
;
428 SVN_ERR(svn_stream_read(stream
, buffer
, &rlen
));
429 content_length
-= rlen
;
430 if (rlen
!= num_to_read
)
431 return stream_ran_dry();
435 /* write however many bytes you read. */
437 SVN_ERR(svn_stream_write(text_stream
, buffer
, &wlen
));
440 /* Uh oh, didn't write as many bytes as we read. */
441 return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF
, NULL
,
442 _("Unexpected EOF writing contents"));
447 /* If we opened a stream, we must close it. */
449 SVN_ERR(svn_stream_close(text_stream
));
456 /* Parse VERSIONSTRING and verify that we support the dumpfile format
457 version number, setting *VERSION appropriately. */
459 parse_format_version(const char *versionstring
, int *version
)
461 static const int magic_len
= sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER
) - 1;
462 const char *p
= strchr(versionstring
, ':');
466 || p
!= (versionstring
+ magic_len
)
467 || strncmp(versionstring
,
468 SVN_REPOS_DUMPFILE_MAGIC_HEADER
,
470 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
471 _("Malformed dumpfile header"));
475 if (value
> SVN_REPOS_DUMPFILE_FORMAT_VERSION
)
476 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
477 _("Unsupported dumpfile version: %d"),
486 /* The Main Parser Logic */
488 svn_repos_parse_dumpstream2(svn_stream_t
*stream
,
489 const svn_repos_parse_fns2_t
*parse_fns
,
491 svn_cancel_func_t cancel_func
,
496 svn_stringbuf_t
*linebuf
;
497 void *rev_baton
= NULL
;
498 char *buffer
= apr_palloc(pool
, SVN__STREAM_CHUNK_SIZE
);
499 apr_size_t buflen
= SVN__STREAM_CHUNK_SIZE
;
500 apr_pool_t
*linepool
= svn_pool_create(pool
);
501 apr_pool_t
*revpool
= svn_pool_create(pool
);
502 apr_pool_t
*nodepool
= svn_pool_create(pool
);
505 SVN_ERR(svn_stream_readline(stream
, &linebuf
, "\n", &eof
, linepool
));
507 return stream_ran_dry();
509 /* The first two lines of the stream are the dumpfile-format version
510 number, and a blank line. */
511 SVN_ERR(parse_format_version(linebuf
->data
, &version
));
513 /* If we were called from svn_repos_parse_dumpstream(), the
514 callbacks to handle delta contents will be NULL, so we have to
515 reject dumpfiles with the current version. */
516 if (version
== SVN_REPOS_DUMPFILE_FORMAT_VERSION
517 && (!parse_fns
->delete_node_property
|| !parse_fns
->apply_textdelta
))
518 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
519 _("Unsupported dumpfile version: %d"), version
);
521 /* A dumpfile "record" is defined to be a header-block of
522 rfc822-style headers, possibly followed by a content-block.
524 - A header-block is always terminated by a single blank line (\n\n)
526 - We know whether the record has a content-block by looking for
527 a 'Content-length:' header. The content-block will always be
528 of a specific length, plus an extra newline.
530 Once a record is fully sucked from the stream, an indeterminate
531 number of blank lines (or lines that begin with whitespace) may
532 follow before the next record (or the end of the stream.)
539 svn_boolean_t found_node
= FALSE
;
540 svn_boolean_t old_v1_with_cl
= FALSE
;
541 const char *content_length
;
545 svn_filesize_t actual_prop_length
;
547 /* Clear our per-line pool. */
548 svn_pool_clear(linepool
);
550 /* Check for cancellation. */
552 SVN_ERR(cancel_func(cancel_baton
));
554 /* Keep reading blank lines until we discover a new record, or until
555 the stream runs out. */
556 SVN_ERR(svn_stream_readline(stream
, &linebuf
, "\n", &eof
, linepool
));
560 if (svn_stringbuf_isempty(linebuf
))
561 break; /* end of stream, go home. */
563 return stream_ran_dry();
566 if ((linebuf
->len
== 0) || (apr_isspace(linebuf
->data
[0])))
567 continue; /* empty line ... loop */
569 /*** Found the beginning of a new record. ***/
571 /* The last line we read better be a header of some sort.
572 Read the whole header-block into a hash. */
573 SVN_ERR(read_header_block(stream
, linebuf
, &headers
, linepool
));
575 /*** Handle the various header blocks. ***/
577 /* Is this a revision record? */
578 if (apr_hash_get(headers
, SVN_REPOS_DUMPFILE_REVISION_NUMBER
,
579 APR_HASH_KEY_STRING
))
581 /* If we already have a rev_baton open, we need to close it
582 and clear the per-revision subpool. */
583 if (rev_baton
!= NULL
)
585 SVN_ERR(parse_fns
->close_revision(rev_baton
));
586 svn_pool_clear(revpool
);
589 SVN_ERR(parse_fns
->new_revision_record(&rev_baton
,
590 headers
, parse_baton
,
593 /* Or is this, perhaps, a node record? */
594 else if (apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_PATH
,
595 APR_HASH_KEY_STRING
))
597 SVN_ERR(parse_fns
->new_node_record(&node_baton
,
603 /* Or is this the repos UUID? */
604 else if ((value
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_UUID
,
605 APR_HASH_KEY_STRING
)))
607 SVN_ERR(parse_fns
->uuid_record(value
, parse_baton
, pool
));
609 /* Or perhaps a dumpfile format? */
610 else if ((value
= apr_hash_get(headers
,
611 SVN_REPOS_DUMPFILE_MAGIC_HEADER
,
612 APR_HASH_KEY_STRING
)))
614 /* ### someday, switch modes of operation here. */
615 version
= atoi(value
);
617 /* Or is this bogosity?! */
620 /* What the heck is this record?!? */
621 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
622 _("Unrecognized record type in stream"));
625 /* Need 3 values below to determine v1 dump type
627 Old (pre 0.14?) v1 dumps don't have Prop-content-length
628 and Text-content-length fields, but always have a properties
629 block in a block with Content-Length > 0 */
631 content_length
= apr_hash_get(headers
,
632 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
,
633 APR_HASH_KEY_STRING
);
634 prop_cl
= apr_hash_get(headers
,
635 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
,
636 APR_HASH_KEY_STRING
);
637 text_cl
= apr_hash_get(headers
,
638 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
,
639 APR_HASH_KEY_STRING
);
641 version
== 1 && content_length
&& ! prop_cl
&& ! text_cl
;
643 /* Is there a props content-block to parse? */
644 if (prop_cl
|| old_v1_with_cl
)
646 const char *delta
= apr_hash_get(headers
,
647 SVN_REPOS_DUMPFILE_PROP_DELTA
,
648 APR_HASH_KEY_STRING
);
649 svn_boolean_t is_delta
= (delta
&& strcmp(delta
, "true") == 0);
651 /* First, remove all node properties, unless this is a delta
653 if (found_node
&& !is_delta
)
654 SVN_ERR(parse_fns
->remove_node_props(node_baton
));
656 SVN_ERR(parse_property_block
658 svn__atoui64(prop_cl
? prop_cl
: content_length
),
660 found_node
? node_baton
: rev_baton
,
663 found_node
? nodepool
: revpool
));
666 /* Is there a text content-block to parse? */
669 const char *delta
= apr_hash_get(headers
,
670 SVN_REPOS_DUMPFILE_TEXT_DELTA
,
671 APR_HASH_KEY_STRING
);
672 svn_boolean_t is_delta
= (delta
&& strcmp(delta
, "true") == 0);
674 SVN_ERR(parse_text_block(stream
,
675 svn__atoui64(text_cl
),
678 found_node
? node_baton
: rev_baton
,
681 found_node
? nodepool
: revpool
));
683 else if (old_v1_with_cl
)
685 /* An old-v1 block with a Content-length might have a text block.
686 If the property block did not consume all the bytes of the
687 Content-length, then it clearly does have a text block.
688 If not, then we must deduce whether we have an *empty* text
689 block or an *absent* text block. The rules are:
690 - "Node-kind: file" blocks have an empty (i.e. present, but
691 zero-length) text block, since they represent a file
692 modification. Note that file-copied-text-unmodified blocks
693 have no Content-length - even if they should have contained
694 a modified property block, the pre-0.14 dumper forgets to
695 dump the modified properties.
696 - If it is not a file node, then it is a revision or directory,
697 and so has an absent text block.
699 const char *node_kind
;
700 svn_filesize_t cl_value
= svn__atoui64(content_length
)
701 - actual_prop_length
;
704 ((node_kind
= apr_hash_get(headers
,
705 SVN_REPOS_DUMPFILE_NODE_KIND
,
706 APR_HASH_KEY_STRING
))
707 && strcmp(node_kind
, "file") == 0)
709 SVN_ERR(parse_text_block(stream
,
713 found_node
? node_baton
: rev_baton
,
716 found_node
? nodepool
: revpool
));
719 /* if we have a content-length header, did we read all of it?
720 in case of an old v1, we *always* read all of it, because
721 text-content-length == content-length - prop-content-length
723 if (content_length
&& ! old_v1_with_cl
)
725 apr_size_t rlen
, num_to_read
;
726 svn_filesize_t remaining
=
727 svn__atoui64(content_length
) -
728 (prop_cl
? svn__atoui64(prop_cl
) : 0) -
729 (text_cl
? svn__atoui64(text_cl
) : 0);
733 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
734 _("Sum of subblock sizes larger than "
735 "total block content length"));
737 /* Consume remaining bytes in this content block */
738 while (remaining
> 0)
740 if (remaining
>= buflen
)
743 rlen
= (apr_size_t
) remaining
;
746 SVN_ERR(svn_stream_read(stream
, buffer
, &rlen
));
748 if (rlen
!= num_to_read
)
749 return stream_ran_dry();
753 /* If we just finished processing a node record, we need to
754 close the node record and clear the per-node subpool. */
757 SVN_ERR(parse_fns
->close_node(node_baton
));
758 svn_pool_clear(nodepool
);
761 /*** End of processing for one record. ***/
763 } /* end of stream */
765 /* Close out whatever revision we're in. */
766 if (rev_baton
!= NULL
)
767 SVN_ERR(parse_fns
->close_revision(rev_baton
));
769 svn_pool_destroy(linepool
);
770 svn_pool_destroy(revpool
);
771 svn_pool_destroy(nodepool
);
777 svn_repos_parse_dumpstream(svn_stream_t
*stream
,
778 const svn_repos_parser_fns_t
*parse_fns
,
780 svn_cancel_func_t cancel_func
,
784 svn_repos_parse_fns2_t
*fns2
= fns2_from_fns(parse_fns
, pool
);
786 return svn_repos_parse_dumpstream2(stream
, fns2
, parse_baton
,
787 cancel_func
, cancel_baton
, pool
);
790 /*----------------------------------------------------------------------*/
792 /** vtable for doing commits to a fs **/
795 static struct node_baton
*
796 make_node_baton(apr_hash_t
*headers
,
797 struct revision_baton
*rb
,
800 struct node_baton
*nb
= apr_pcalloc(pool
, sizeof(*nb
));
803 /* Start with sensible defaults. */
806 nb
->kind
= svn_node_unknown
;
808 /* Then add info from the headers. */
809 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_PATH
,
810 APR_HASH_KEY_STRING
)))
812 if (rb
->pb
->parent_dir
)
813 nb
->path
= svn_path_join(rb
->pb
->parent_dir
, val
, pool
);
815 nb
->path
= apr_pstrdup(pool
, val
);
818 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_KIND
,
819 APR_HASH_KEY_STRING
)))
821 if (! strcmp(val
, "file"))
822 nb
->kind
= svn_node_file
;
823 else if (! strcmp(val
, "dir"))
824 nb
->kind
= svn_node_dir
;
827 nb
->action
= (enum svn_node_action
)(-1); /* an invalid action code */
828 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_ACTION
,
829 APR_HASH_KEY_STRING
)))
831 if (! strcmp(val
, "change"))
832 nb
->action
= svn_node_action_change
;
833 else if (! strcmp(val
, "add"))
834 nb
->action
= svn_node_action_add
;
835 else if (! strcmp(val
, "delete"))
836 nb
->action
= svn_node_action_delete
;
837 else if (! strcmp(val
, "replace"))
838 nb
->action
= svn_node_action_replace
;
841 nb
->copyfrom_rev
= SVN_INVALID_REVNUM
;
842 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
,
843 APR_HASH_KEY_STRING
)))
845 nb
->copyfrom_rev
= (svn_revnum_t
) atoi(val
);
847 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
,
848 APR_HASH_KEY_STRING
)))
850 if (rb
->pb
->parent_dir
)
851 nb
->copyfrom_path
= svn_path_join(rb
->pb
->parent_dir
,
852 (*val
== '/' ? val
+ 1 : val
), pool
);
854 nb
->copyfrom_path
= apr_pstrdup(pool
, val
);
857 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM
,
858 APR_HASH_KEY_STRING
)))
860 nb
->result_checksum
= apr_pstrdup(pool
, val
);
863 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM
,
864 APR_HASH_KEY_STRING
)))
866 nb
->base_checksum
= apr_pstrdup(pool
, val
);
869 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM
,
870 APR_HASH_KEY_STRING
)))
872 nb
->copy_source_checksum
= apr_pstrdup(pool
, val
);
875 /* What's cool about this dump format is that the parser just
876 ignores any unrecognized headers. :-) */
881 static struct revision_baton
*
882 make_revision_baton(apr_hash_t
*headers
,
883 struct parse_baton
*pb
,
886 struct revision_baton
*rb
= apr_pcalloc(pool
, sizeof(*rb
));
891 rb
->rev
= SVN_INVALID_REVNUM
;
893 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_REVISION_NUMBER
,
894 APR_HASH_KEY_STRING
)))
895 rb
->rev
= SVN_STR_TO_REV(val
);
902 new_revision_record(void **revision_baton
,
907 struct parse_baton
*pb
= parse_baton
;
908 struct revision_baton
*rb
;
909 svn_revnum_t head_rev
;
911 rb
= make_revision_baton(headers
, pb
, pool
);
912 SVN_ERR(svn_fs_youngest_rev(&head_rev
, pb
->fs
, pool
));
914 /* FIXME: This is a lame fallback loading multiple segments of dump in
915 several seperate operations. It is highly susceptible to race conditions.
916 Calculate the revision 'offset' for finding copyfrom sources.
917 It might be positive or negative. */
918 rb
->rev_offset
= (rb
->rev
) - (head_rev
+ 1);
922 /* Create a new fs txn. */
923 SVN_ERR(svn_fs_begin_txn2(&(rb
->txn
), pb
->fs
, head_rev
, 0, pool
));
924 SVN_ERR(svn_fs_txn_root(&(rb
->txn_root
), rb
->txn
, pool
));
926 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
927 _("<<< Started new transaction, based on "
928 "original revision %ld\n"), rb
->rev
));
931 /* If we're parsing revision 0, only the revision are (possibly)
932 interesting to us: when loading the stream into an empty
933 filesystem, then we want new filesystem's revision 0 to have the
934 same props. Otherwise, we just ignore revision 0 in the stream. */
936 *revision_baton
= rb
;
942 /* Factorized helper func for new_node_record() */
944 maybe_add_with_history(struct node_baton
*nb
,
945 struct revision_baton
*rb
,
948 struct parse_baton
*pb
= rb
->pb
;
951 if ((nb
->copyfrom_path
== NULL
) || (! pb
->use_history
))
953 /* Add empty file or dir, without history. */
954 if (nb
->kind
== svn_node_file
)
955 SVN_ERR(svn_fs_make_file(rb
->txn_root
, nb
->path
, pool
));
957 else if (nb
->kind
== svn_node_dir
)
958 SVN_ERR(svn_fs_make_dir(rb
->txn_root
, nb
->path
, pool
));
962 /* Hunt down the source revision in this fs. */
963 svn_fs_root_t
*copy_root
;
964 svn_revnum_t src_rev
= nb
->copyfrom_rev
- rb
->rev_offset
;
965 svn_revnum_t
*src_rev_from_map
;
966 if ((src_rev_from_map
= apr_hash_get(pb
->rev_map
, &nb
->copyfrom_rev
,
967 sizeof(nb
->copyfrom_rev
))))
968 src_rev
= *src_rev_from_map
;
970 if (! SVN_IS_VALID_REVNUM(src_rev
))
971 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
972 _("Relative source revision %ld is not"
973 " available in current repository"),
976 SVN_ERR(svn_fs_revision_root(©_root
, pb
->fs
, src_rev
, pool
));
978 if (nb
->copy_source_checksum
)
980 unsigned char md5_digest
[APR_MD5_DIGESTSIZE
];
982 SVN_ERR(svn_fs_file_md5_checksum(md5_digest
, copy_root
,
983 nb
->copyfrom_path
, pool
));
984 hex
= svn_md5_digest_to_cstring(md5_digest
, pool
);
985 if (hex
&& (strcmp(nb
->copy_source_checksum
, hex
) != 0))
986 return svn_error_createf
987 (SVN_ERR_CHECKSUM_MISMATCH
,
989 _("Copy source checksum mismatch on copy from '%s'@%ld\n"
990 " to '%s' in rev based on r%ld:\n"
993 nb
->copyfrom_path
, src_rev
,
995 nb
->copy_source_checksum
, hex
);
998 SVN_ERR(svn_fs_copy(copy_root
, nb
->copyfrom_path
,
999 rb
->txn_root
, nb
->path
, pool
));
1002 SVN_ERR(svn_stream_write(pb
->outstream
, "COPIED...", &len
));
1005 return SVN_NO_ERROR
;
1009 static svn_error_t
*
1010 uuid_record(const char *uuid
,
1014 struct parse_baton
*pb
= parse_baton
;
1015 svn_revnum_t youngest_rev
;
1017 if (pb
->uuid_action
== svn_repos_load_uuid_ignore
)
1018 return SVN_NO_ERROR
;
1020 if (pb
->uuid_action
!= svn_repos_load_uuid_force
)
1022 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, pb
->fs
, pool
));
1023 if (youngest_rev
!= 0)
1024 return SVN_NO_ERROR
;
1027 return svn_fs_set_uuid(pb
->fs
, uuid
, pool
);
1030 static svn_error_t
*
1031 new_node_record(void **node_baton
,
1032 apr_hash_t
*headers
,
1033 void *revision_baton
,
1036 struct revision_baton
*rb
= revision_baton
;
1037 struct parse_baton
*pb
= rb
->pb
;
1038 struct node_baton
*nb
;
1041 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
1042 _("Malformed dumpstream: "
1043 "Revision 0 must not contain node records"));
1045 nb
= make_node_baton(headers
, rb
, pool
);
1049 case svn_node_action_change
:
1051 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
1052 _(" * editing path : %s ..."),
1056 case svn_node_action_delete
:
1058 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
1059 _(" * deleting path : %s ..."),
1061 SVN_ERR(svn_fs_delete(rb
->txn_root
, nb
->path
, pool
));
1064 case svn_node_action_add
:
1066 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
1067 _(" * adding path : %s ..."),
1070 SVN_ERR(maybe_add_with_history(nb
, rb
, pool
));
1073 case svn_node_action_replace
:
1075 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
1076 _(" * replacing path : %s ..."),
1079 SVN_ERR(svn_fs_delete(rb
->txn_root
, nb
->path
, pool
));
1081 SVN_ERR(maybe_add_with_history(nb
, rb
, pool
));
1085 return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA
, NULL
,
1086 _("Unrecognized node-action on node '%s'"),
1091 return SVN_NO_ERROR
;
1095 static svn_error_t
*
1096 set_revision_property(void *baton
,
1098 const svn_string_t
*value
)
1100 struct revision_baton
*rb
= baton
;
1104 SVN_ERR(svn_fs_change_txn_prop(rb
->txn
, name
, value
, rb
->pool
));
1106 /* Remember any datestamp that passes through! (See comment in
1107 close_revision() below.) */
1108 if (! strcmp(name
, SVN_PROP_REVISION_DATE
))
1109 rb
->datestamp
= svn_string_dup(value
, rb
->pool
);
1111 else if (rb
->rev
== 0)
1113 /* Special case: set revision 0 properties when loading into an
1114 'empty' filesystem. */
1115 struct parse_baton
*pb
= rb
->pb
;
1116 svn_revnum_t youngest_rev
;
1118 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, pb
->fs
, rb
->pool
));
1120 if (youngest_rev
== 0)
1121 SVN_ERR(svn_fs_change_rev_prop(pb
->fs
, 0, name
, value
, rb
->pool
));
1124 return SVN_NO_ERROR
;
1128 static svn_error_t
*
1129 set_node_property(void *baton
,
1131 const svn_string_t
*value
)
1133 struct node_baton
*nb
= baton
;
1134 struct revision_baton
*rb
= nb
->rb
;
1135 const char *parent_dir
= rb
->pb
->parent_dir
;
1137 if (parent_dir
&& strcmp(name
, SVN_PROP_MERGEINFO
) == 0)
1139 /* Prefix the merge source paths with PARENT_DIR. */
1140 /* ASSUMPTION: All source paths are included in the dump stream. */
1141 const char *mergeinfo_val
;
1142 SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val
, value
->data
,
1143 parent_dir
, nb
->pool
));
1144 value
= svn_string_create(mergeinfo_val
, nb
->pool
);
1147 SVN_ERR(svn_fs_change_node_prop(rb
->txn_root
, nb
->path
,
1148 name
, value
, nb
->pool
));
1150 return SVN_NO_ERROR
;
1154 static svn_error_t
*
1155 delete_node_property(void *baton
,
1158 struct node_baton
*nb
= baton
;
1159 struct revision_baton
*rb
= nb
->rb
;
1161 SVN_ERR(svn_fs_change_node_prop(rb
->txn_root
, nb
->path
,
1162 name
, NULL
, nb
->pool
));
1164 return SVN_NO_ERROR
;
1168 static svn_error_t
*
1169 remove_node_props(void *baton
)
1171 struct node_baton
*nb
= baton
;
1172 struct revision_baton
*rb
= nb
->rb
;
1173 apr_hash_t
*proplist
;
1174 apr_hash_index_t
*hi
;
1176 SVN_ERR(svn_fs_node_proplist(&proplist
,
1177 rb
->txn_root
, nb
->path
, nb
->pool
));
1179 for (hi
= apr_hash_first(nb
->pool
, proplist
); hi
; hi
= apr_hash_next(hi
))
1183 apr_hash_this(hi
, &key
, NULL
, NULL
);
1185 SVN_ERR(svn_fs_change_node_prop(rb
->txn_root
, nb
->path
,
1186 (const char *) key
, NULL
,
1190 return SVN_NO_ERROR
;
1194 static svn_error_t
*
1195 apply_textdelta(svn_txdelta_window_handler_t
*handler
,
1196 void **handler_baton
,
1199 struct node_baton
*nb
= node_baton
;
1200 struct revision_baton
*rb
= nb
->rb
;
1202 return svn_fs_apply_textdelta(handler
, handler_baton
,
1203 rb
->txn_root
, nb
->path
,
1204 nb
->base_checksum
, nb
->result_checksum
,
1209 static svn_error_t
*
1210 set_fulltext(svn_stream_t
**stream
,
1213 struct node_baton
*nb
= node_baton
;
1214 struct revision_baton
*rb
= nb
->rb
;
1216 return svn_fs_apply_text(stream
,
1217 rb
->txn_root
, nb
->path
,
1218 nb
->result_checksum
,
1223 static svn_error_t
*
1224 close_node(void *baton
)
1226 struct node_baton
*nb
= baton
;
1227 struct revision_baton
*rb
= nb
->rb
;
1228 struct parse_baton
*pb
= rb
->pb
;
1231 SVN_ERR(svn_stream_write(pb
->outstream
, _(" done.\n"), &len
));
1233 return SVN_NO_ERROR
;
1237 static svn_error_t
*
1238 close_revision(void *baton
)
1240 struct revision_baton
*rb
= baton
;
1241 struct parse_baton
*pb
= rb
->pb
;
1242 const char *conflict_msg
= NULL
;
1243 svn_revnum_t
*old_rev
, *new_rev
;
1247 return SVN_NO_ERROR
;
1249 /* Prepare memory for saving dump-rev -> in-repos-rev mapping. */
1250 old_rev
= apr_palloc(pb
->pool
, sizeof(*old_rev
) * 2);
1251 new_rev
= old_rev
+ 1;
1254 /* Run the pre-commit hook, if so commanded. */
1255 if (pb
->use_pre_commit_hook
)
1257 const char *txn_name
;
1258 err
= svn_fs_txn_name(&txn_name
, rb
->txn
, rb
->pool
);
1260 err
= svn_repos__hooks_pre_commit(pb
->repos
, txn_name
, rb
->pool
);
1263 svn_error_clear(svn_fs_abort_txn(rb
->txn
, rb
->pool
));
1269 if ((err
= svn_fs_commit_txn(&conflict_msg
, new_rev
, rb
->txn
, rb
->pool
)))
1271 svn_error_clear(svn_fs_abort_txn(rb
->txn
, rb
->pool
));
1273 return svn_error_quick_wrap(err
, conflict_msg
);
1278 /* Run post-commit hook, if so commanded. */
1279 if (pb
->use_post_commit_hook
)
1281 if ((err
= svn_repos__hooks_post_commit(pb
->repos
, *new_rev
, rb
->pool
)))
1282 return svn_error_create
1283 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED
, err
,
1284 _("Commit succeeded, but post-commit hook failed"));
1287 /* After a successful commit, must record the dump-rev -> in-repos-rev
1288 mapping, so that copyfrom instructions in the dump file can look up the
1289 correct repository revision to copy from. */
1290 apr_hash_set(pb
->rev_map
, old_rev
, sizeof(svn_revnum_t
), new_rev
);
1292 /* Deltify the predecessors of paths changed in this revision. */
1293 SVN_ERR(svn_fs_deltify_revision(pb
->fs
, *new_rev
, rb
->pool
));
1295 /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the
1296 current clock-time. We don't want that, we want to preserve
1297 history exactly. Good thing revision props aren't versioned!
1298 Note that if rb->datestamp is NULL, that's fine -- if the dump
1299 data doesn't carry a datestamp, we want to preserve that fact in
1301 SVN_ERR(svn_fs_change_rev_prop(pb
->fs
, *new_rev
,
1302 SVN_PROP_REVISION_DATE
, rb
->datestamp
,
1305 if (*new_rev
== rb
->rev
)
1307 SVN_ERR(svn_stream_printf(pb
->outstream
, rb
->pool
,
1308 _("\n------- Committed revision %ld"
1309 " >>>\n\n"), *new_rev
));
1313 SVN_ERR(svn_stream_printf(pb
->outstream
, rb
->pool
,
1314 _("\n------- Committed new rev %ld"
1315 " (loaded from original rev %ld"
1316 ") >>>\n\n"), *new_rev
, rb
->rev
));
1319 return SVN_NO_ERROR
;
1323 /*----------------------------------------------------------------------*/
1325 /** The public routines **/
1329 svn_repos_get_fs_build_parser2(const svn_repos_parse_fns2_t
**callbacks
,
1332 svn_boolean_t use_history
,
1333 enum svn_repos_load_uuid uuid_action
,
1334 svn_stream_t
*outstream
,
1335 const char *parent_dir
,
1338 const svn_repos_parser_fns_t
*fns
;
1339 svn_repos_parse_fns2_t
*parser
;
1341 /* Fetch the old-style vtable and baton, convert the vtable to a
1342 * new-style vtable, and set the new callbacks. */
1343 SVN_ERR(svn_repos_get_fs_build_parser(&fns
, parse_baton
, repos
,
1344 use_history
, uuid_action
, outstream
,
1346 parser
= fns2_from_fns(fns
, pool
);
1347 parser
->delete_node_property
= delete_node_property
;
1348 parser
->apply_textdelta
= apply_textdelta
;
1349 *callbacks
= parser
;
1350 return SVN_NO_ERROR
;
1356 svn_repos_get_fs_build_parser(const svn_repos_parser_fns_t
**parser_callbacks
,
1359 svn_boolean_t use_history
,
1360 enum svn_repos_load_uuid uuid_action
,
1361 svn_stream_t
*outstream
,
1362 const char *parent_dir
,
1365 svn_repos_parser_fns_t
*parser
= apr_pcalloc(pool
, sizeof(*parser
));
1366 struct parse_baton
*pb
= apr_pcalloc(pool
, sizeof(*pb
));
1368 parser
->new_revision_record
= new_revision_record
;
1369 parser
->new_node_record
= new_node_record
;
1370 parser
->uuid_record
= uuid_record
;
1371 parser
->set_revision_property
= set_revision_property
;
1372 parser
->set_node_property
= set_node_property
;
1373 parser
->remove_node_props
= remove_node_props
;
1374 parser
->set_fulltext
= set_fulltext
;
1375 parser
->close_node
= close_node
;
1376 parser
->close_revision
= close_revision
;
1379 pb
->fs
= svn_repos_fs(repos
);
1380 pb
->use_history
= use_history
;
1381 pb
->outstream
= outstream
? outstream
: svn_stream_empty(pool
);
1382 pb
->uuid_action
= uuid_action
;
1383 pb
->parent_dir
= parent_dir
;
1385 pb
->rev_map
= apr_hash_make(pool
);
1387 *parser_callbacks
= parser
;
1389 return SVN_NO_ERROR
;
1395 svn_repos_load_fs2(svn_repos_t
*repos
,
1396 svn_stream_t
*dumpstream
,
1397 svn_stream_t
*feedback_stream
,
1398 enum svn_repos_load_uuid uuid_action
,
1399 const char *parent_dir
,
1400 svn_boolean_t use_pre_commit_hook
,
1401 svn_boolean_t use_post_commit_hook
,
1402 svn_cancel_func_t cancel_func
,
1406 const svn_repos_parse_fns2_t
*parser
;
1408 struct parse_baton
*pb
;
1410 /* This is really simple. */
1412 SVN_ERR(svn_repos_get_fs_build_parser2(&parser
, &parse_baton
,
1414 TRUE
, /* look for copyfrom revs */
1420 /* Heh. We know this is a parse_baton. This file made it. So
1421 cast away, and set our hook booleans. */
1423 pb
->use_pre_commit_hook
= use_pre_commit_hook
;
1424 pb
->use_post_commit_hook
= use_post_commit_hook
;
1426 SVN_ERR(svn_repos_parse_dumpstream2(dumpstream
, parser
, parse_baton
,
1427 cancel_func
, cancel_baton
, pool
));
1429 return SVN_NO_ERROR
;
1434 svn_repos_load_fs(svn_repos_t
*repos
,
1435 svn_stream_t
*dumpstream
,
1436 svn_stream_t
*feedback_stream
,
1437 enum svn_repos_load_uuid uuid_action
,
1438 const char *parent_dir
,
1439 svn_cancel_func_t cancel_func
,
1443 return svn_repos_load_fs2(repos
, dumpstream
, feedback_stream
,
1444 uuid_action
, parent_dir
, FALSE
, FALSE
,
1445 cancel_func
, cancel_baton
, pool
);