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"
34 #include "private/svn_mergeinfo_private.h"
36 /*----------------------------------------------------------------------*/
38 /** Batons used herein **/
45 svn_boolean_t use_history
;
46 svn_boolean_t use_pre_commit_hook
;
47 svn_boolean_t use_post_commit_hook
;
48 svn_stream_t
*outstream
;
49 enum svn_repos_load_uuid uuid_action
;
50 const char *parent_dir
;
60 svn_fs_root_t
*txn_root
;
62 const svn_string_t
*datestamp
;
64 apr_int32_t rev_offset
;
66 struct parse_baton
*pb
;
74 enum svn_node_action action
;
75 const char *base_checksum
; /* null, if not available */
76 const char *result_checksum
; /* null, if not available */
77 const char *copy_source_checksum
; /* null, if not available */
79 svn_revnum_t copyfrom_rev
;
80 const char *copyfrom_path
;
82 struct revision_baton
*rb
;
88 /*----------------------------------------------------------------------*/
90 /** A conversion function between the two vtable types. **/
91 static svn_repos_parse_fns2_t
*
92 fns2_from_fns(const svn_repos_parser_fns_t
*fns
,
95 svn_repos_parse_fns2_t
*fns2
;
97 fns2
= apr_palloc(pool
, sizeof(*fns2
));
98 fns2
->new_revision_record
= fns
->new_revision_record
;
99 fns2
->uuid_record
= fns
->uuid_record
;
100 fns2
->new_node_record
= fns
->new_node_record
;
101 fns2
->set_revision_property
= fns
->set_revision_property
;
102 fns2
->set_node_property
= fns
->set_node_property
;
103 fns2
->remove_node_props
= fns
->remove_node_props
;
104 fns2
->set_fulltext
= fns
->set_fulltext
;
105 fns2
->close_node
= fns
->close_node
;
106 fns2
->close_revision
= fns
->close_revision
;
107 fns2
->delete_node_property
= NULL
;
108 fns2
->apply_textdelta
= NULL
;
113 /*----------------------------------------------------------------------*/
115 /** The parser and related helper funcs **/
121 return svn_error_create(SVN_ERR_INCOMPLETE_DATA
, NULL
,
122 _("Premature end of content data in dumpstream"));
126 stream_malformed(void)
128 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
129 _("Dumpstream data appears to be malformed"));
132 /* Allocate a new hash *HEADERS in POOL, and read a series of
133 RFC822-style headers from STREAM. Duplicate each header's name and
134 value into POOL and store in hash as a const char * ==> const char *.
136 The headers are assumed to be terminated by a single blank line,
137 which will be permanently sucked from the stream and tossed.
139 If the caller has already read in the first header line, it should
140 be passed in as FIRST_HEADER. If not, pass NULL instead.
143 read_header_block(svn_stream_t
*stream
,
144 svn_stringbuf_t
*first_header
,
145 apr_hash_t
**headers
,
148 *headers
= apr_hash_make(pool
);
152 svn_stringbuf_t
*header_str
;
153 const char *name
, *value
;
157 if (first_header
!= NULL
)
159 header_str
= first_header
;
160 first_header
= NULL
; /* so we never visit this block again. */
165 /* Read the next line into a stringbuf. */
166 SVN_ERR(svn_stream_readline(stream
, &header_str
, "\n", &eof
, pool
));
168 if (svn_stringbuf_isempty(header_str
))
169 break; /* end of header block */
171 return stream_ran_dry();
173 /* Find the next colon in the stringbuf. */
174 while (header_str
->data
[i
] != ':')
176 if (header_str
->data
[i
] == '\0')
177 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
178 _("Dump stream contains a malformed "
179 "header (with no ':') at '%.20s'"),
183 /* Create a 'name' string and point to it. */
184 header_str
->data
[i
] = '\0';
185 name
= header_str
->data
;
187 /* Skip over the NULL byte and the space following it. */
189 if (i
> header_str
->len
)
190 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
191 _("Dump stream contains a malformed "
192 "header (with no value) at '%.20s'"),
195 /* Point to the 'value' string. */
196 value
= header_str
->data
+ i
;
198 /* Store name/value in hash. */
199 apr_hash_set(*headers
, name
, APR_HASH_KEY_STRING
, value
);
206 /* Set *PBUF to a string of length LEN, allocated in POOL, read from STREAM.
207 Also read a newline from STREAM and increase *ACTUAL_LEN by the total
208 number of bytes read from STREAM. */
210 read_key_or_val(char **pbuf
,
211 svn_filesize_t
*actual_length
,
212 svn_stream_t
*stream
,
216 char *buf
= apr_pcalloc(pool
, len
+ 1);
221 SVN_ERR(svn_stream_read(stream
, buf
, &numread
));
222 *actual_length
+= numread
;
224 return stream_ran_dry();
227 /* Suck up extra newline after key data */
229 SVN_ERR(svn_stream_read(stream
, &c
, &numread
));
230 *actual_length
+= numread
;
232 return stream_ran_dry();
234 return stream_malformed();
241 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
242 return it in *MERGEINFO_VAL. */
244 prefix_mergeinfo_paths(svn_string_t
**mergeinfo_val
,
245 const svn_string_t
*mergeinfo_orig
,
246 const char *parent_dir
,
249 apr_hash_t
*prefixed_mergeinfo
, *mergeinfo
;
250 apr_hash_index_t
*hi
;
253 SVN_ERR(svn_mergeinfo_parse(&mergeinfo
, mergeinfo_orig
->data
, pool
));
254 prefixed_mergeinfo
= apr_hash_make(pool
);
255 for (hi
= apr_hash_first(NULL
, mergeinfo
); hi
; hi
= apr_hash_next(hi
))
258 const void *merge_source
;
259 apr_hash_this(hi
, &merge_source
, NULL
, &rangelist
);
260 path
= svn_path_join(parent_dir
, (const char*)merge_source
+1, pool
);
261 apr_hash_set(prefixed_mergeinfo
, path
, APR_HASH_KEY_STRING
, rangelist
);
263 SVN_ERR(svn_mergeinfo_to_string(mergeinfo_val
, prefixed_mergeinfo
, pool
));
269 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
270 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
271 (allocated from POOL). */
273 renumber_mergeinfo_revs(svn_string_t
**final_val
,
274 const svn_string_t
*initial_val
,
275 struct revision_baton
*rb
,
278 apr_pool_t
*subpool
= svn_pool_create(pool
);
279 apr_hash_t
*mergeinfo
;
280 apr_hash_t
*final_mergeinfo
= apr_hash_make(subpool
);
281 apr_hash_index_t
*hi
;
283 SVN_ERR(svn_mergeinfo_parse(&mergeinfo
, initial_val
->data
, subpool
));
284 for (hi
= apr_hash_first(NULL
, mergeinfo
); hi
; hi
= apr_hash_next(hi
))
286 const char *merge_source
;
287 apr_array_header_t
*rangelist
;
288 struct parse_baton
*pb
= rb
->pb
;
293 apr_hash_this(hi
, &key
, NULL
, &val
);
294 merge_source
= (const char *) key
;
295 rangelist
= (apr_array_header_t
*) val
;
297 /* Possibly renumber revisions in merge source's rangelist. */
298 for (i
= 0; i
< rangelist
->nelts
; i
++)
300 svn_revnum_t
*rev_from_map
;
301 svn_merge_range_t
*range
= APR_ARRAY_IDX(rangelist
, i
,
302 svn_merge_range_t
*);
303 rev_from_map
= apr_hash_get(pb
->rev_map
, &range
->start
,
304 sizeof(svn_revnum_t
));
305 if (rev_from_map
&& SVN_IS_VALID_REVNUM(*rev_from_map
))
306 range
->start
= *rev_from_map
;
308 rev_from_map
= apr_hash_get(pb
->rev_map
, &range
->end
,
309 sizeof(svn_revnum_t
));
310 if (rev_from_map
&& SVN_IS_VALID_REVNUM(*rev_from_map
))
311 range
->end
= *rev_from_map
;
313 apr_hash_set(final_mergeinfo
, merge_source
,
314 APR_HASH_KEY_STRING
, rangelist
);
317 SVN_ERR(svn_mergeinfo_sort(final_mergeinfo
, subpool
));
318 SVN_ERR(svn_mergeinfo_to_string(final_val
, final_mergeinfo
, pool
));
319 svn_pool_destroy(subpool
);
325 /* Read CONTENT_LENGTH bytes from STREAM, parsing the bytes as an
326 encoded Subversion properties hash, and making multiple calls to
327 PARSE_FNS->set_*_property on RECORD_BATON (depending on the value
330 Set *ACTUAL_LENGTH to the number of bytes consumed from STREAM.
331 If an error is returned, the value of *ACTUAL_LENGTH is undefined.
333 Use POOL for all allocations. */
335 parse_property_block(svn_stream_t
*stream
,
336 svn_filesize_t content_length
,
337 const svn_repos_parse_fns2_t
*parse_fns
,
339 svn_boolean_t is_node
,
340 svn_filesize_t
*actual_length
,
343 svn_stringbuf_t
*strbuf
;
344 apr_pool_t
*proppool
= svn_pool_create(pool
);
347 while (content_length
!= *actual_length
)
349 char *buf
; /* a pointer into the stringbuf's data */
352 svn_pool_clear(proppool
);
354 /* Read a key length line. (Actually, it might be PROPS_END). */
355 SVN_ERR(svn_stream_readline(stream
, &strbuf
, "\n", &eof
, proppool
));
359 /* We could just use stream_ran_dry() or stream_malformed(),
360 but better to give a non-generic property block error. */
361 return svn_error_create
362 (SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
363 _("Incomplete or unterminated property block"));
366 *actual_length
+= (strbuf
->len
+ 1); /* +1 because we read a \n too. */
369 if (! strcmp(buf
, "PROPS-END"))
370 break; /* no more properties. */
372 else if ((buf
[0] == 'K') && (buf
[1] == ' '))
376 SVN_ERR(read_key_or_val(&keybuf
, actual_length
,
377 stream
, atoi(buf
+ 2), proppool
));
379 /* Read a val length line */
380 SVN_ERR(svn_stream_readline(stream
, &strbuf
, "\n", &eof
, proppool
));
382 return stream_ran_dry();
384 *actual_length
+= (strbuf
->len
+ 1); /* +1 because we read \n too */
387 if ((buf
[0] == 'V') && (buf
[1] == ' '))
389 svn_string_t propstring
;
392 propstring
.len
= atoi(buf
+ 2);
393 SVN_ERR(read_key_or_val(&valbuf
, actual_length
,
394 stream
, propstring
.len
, proppool
));
395 propstring
.data
= valbuf
;
397 /* Now, send the property pair to the vtable! */
399 SVN_ERR(parse_fns
->set_node_property(record_baton
,
403 SVN_ERR(parse_fns
->set_revision_property(record_baton
,
408 return stream_malformed(); /* didn't find expected 'V' line */
410 else if ((buf
[0] == 'D') && (buf
[1] == ' '))
414 SVN_ERR(read_key_or_val(&keybuf
, actual_length
,
415 stream
, atoi(buf
+ 2), proppool
));
417 /* We don't expect these in revision properties, and if we see
418 one when we don't have a delete_node_property callback,
419 then we're seeing a v3 feature in a v2 dump. */
420 if (!is_node
|| !parse_fns
->delete_node_property
)
421 return stream_malformed();
423 SVN_ERR(parse_fns
->delete_node_property(record_baton
, keybuf
));
426 return stream_malformed(); /* didn't find expected 'K' line */
430 svn_pool_destroy(proppool
);
435 /* Read CONTENT_LENGTH bytes from STREAM, and use
436 PARSE_FNS->set_fulltext to push those bytes as replace fulltext for
437 a node. Use BUFFER/BUFLEN to push the fulltext in "chunks".
439 Use POOL for all allocations. */
441 parse_text_block(svn_stream_t
*stream
,
442 svn_filesize_t content_length
,
443 svn_boolean_t is_delta
,
444 const svn_repos_parse_fns2_t
*parse_fns
,
450 svn_stream_t
*text_stream
= NULL
;
451 apr_size_t num_to_read
, rlen
, wlen
;
455 svn_txdelta_window_handler_t wh
;
458 SVN_ERR(parse_fns
->apply_textdelta(&wh
, &whb
, record_baton
));
460 text_stream
= svn_txdelta_parse_svndiff(wh
, whb
, TRUE
, pool
);
464 /* Get a stream to which we can push the data. */
465 SVN_ERR(parse_fns
->set_fulltext(&text_stream
, record_baton
));
468 /* If there are no contents to read, just write an empty buffer
469 through our callback. */
470 if (content_length
== 0)
474 SVN_ERR(svn_stream_write(text_stream
, "", &wlen
));
477 /* Regardless of whether or not we have a sink for our data, we
479 while (content_length
)
481 if (content_length
>= buflen
)
484 rlen
= (apr_size_t
) content_length
;
487 SVN_ERR(svn_stream_read(stream
, buffer
, &rlen
));
488 content_length
-= rlen
;
489 if (rlen
!= num_to_read
)
490 return stream_ran_dry();
494 /* write however many bytes you read. */
496 SVN_ERR(svn_stream_write(text_stream
, buffer
, &wlen
));
499 /* Uh oh, didn't write as many bytes as we read. */
500 return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF
, NULL
,
501 _("Unexpected EOF writing contents"));
506 /* If we opened a stream, we must close it. */
508 SVN_ERR(svn_stream_close(text_stream
));
515 /* Parse VERSIONSTRING and verify that we support the dumpfile format
516 version number, setting *VERSION appropriately. */
518 parse_format_version(const char *versionstring
, int *version
)
520 static const int magic_len
= sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER
) - 1;
521 const char *p
= strchr(versionstring
, ':');
525 || p
!= (versionstring
+ magic_len
)
526 || strncmp(versionstring
,
527 SVN_REPOS_DUMPFILE_MAGIC_HEADER
,
529 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
530 _("Malformed dumpfile header"));
534 if (value
> SVN_REPOS_DUMPFILE_FORMAT_VERSION
)
535 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
536 _("Unsupported dumpfile version: %d"),
545 /* The Main Parser Logic */
547 svn_repos_parse_dumpstream2(svn_stream_t
*stream
,
548 const svn_repos_parse_fns2_t
*parse_fns
,
550 svn_cancel_func_t cancel_func
,
555 svn_stringbuf_t
*linebuf
;
556 void *rev_baton
= NULL
;
557 char *buffer
= apr_palloc(pool
, SVN__STREAM_CHUNK_SIZE
);
558 apr_size_t buflen
= SVN__STREAM_CHUNK_SIZE
;
559 apr_pool_t
*linepool
= svn_pool_create(pool
);
560 apr_pool_t
*revpool
= svn_pool_create(pool
);
561 apr_pool_t
*nodepool
= svn_pool_create(pool
);
564 SVN_ERR(svn_stream_readline(stream
, &linebuf
, "\n", &eof
, linepool
));
566 return stream_ran_dry();
568 /* The first two lines of the stream are the dumpfile-format version
569 number, and a blank line. */
570 SVN_ERR(parse_format_version(linebuf
->data
, &version
));
572 /* If we were called from svn_repos_parse_dumpstream(), the
573 callbacks to handle delta contents will be NULL, so we have to
574 reject dumpfiles with the current version. */
575 if (version
== SVN_REPOS_DUMPFILE_FORMAT_VERSION
576 && (!parse_fns
->delete_node_property
|| !parse_fns
->apply_textdelta
))
577 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
578 _("Unsupported dumpfile version: %d"), version
);
580 /* A dumpfile "record" is defined to be a header-block of
581 rfc822-style headers, possibly followed by a content-block.
583 - A header-block is always terminated by a single blank line (\n\n)
585 - We know whether the record has a content-block by looking for
586 a 'Content-length:' header. The content-block will always be
587 of a specific length, plus an extra newline.
589 Once a record is fully sucked from the stream, an indeterminate
590 number of blank lines (or lines that begin with whitespace) may
591 follow before the next record (or the end of the stream.)
598 svn_boolean_t found_node
= FALSE
;
599 svn_boolean_t old_v1_with_cl
= FALSE
;
600 const char *content_length
;
604 svn_filesize_t actual_prop_length
;
606 /* Clear our per-line pool. */
607 svn_pool_clear(linepool
);
609 /* Check for cancellation. */
611 SVN_ERR(cancel_func(cancel_baton
));
613 /* Keep reading blank lines until we discover a new record, or until
614 the stream runs out. */
615 SVN_ERR(svn_stream_readline(stream
, &linebuf
, "\n", &eof
, linepool
));
619 if (svn_stringbuf_isempty(linebuf
))
620 break; /* end of stream, go home. */
622 return stream_ran_dry();
625 if ((linebuf
->len
== 0) || (apr_isspace(linebuf
->data
[0])))
626 continue; /* empty line ... loop */
628 /*** Found the beginning of a new record. ***/
630 /* The last line we read better be a header of some sort.
631 Read the whole header-block into a hash. */
632 SVN_ERR(read_header_block(stream
, linebuf
, &headers
, linepool
));
634 /*** Handle the various header blocks. ***/
636 /* Is this a revision record? */
637 if (apr_hash_get(headers
, SVN_REPOS_DUMPFILE_REVISION_NUMBER
,
638 APR_HASH_KEY_STRING
))
640 /* If we already have a rev_baton open, we need to close it
641 and clear the per-revision subpool. */
642 if (rev_baton
!= NULL
)
644 SVN_ERR(parse_fns
->close_revision(rev_baton
));
645 svn_pool_clear(revpool
);
648 SVN_ERR(parse_fns
->new_revision_record(&rev_baton
,
649 headers
, parse_baton
,
652 /* Or is this, perhaps, a node record? */
653 else if (apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_PATH
,
654 APR_HASH_KEY_STRING
))
656 SVN_ERR(parse_fns
->new_node_record(&node_baton
,
662 /* Or is this the repos UUID? */
663 else if ((value
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_UUID
,
664 APR_HASH_KEY_STRING
)))
666 SVN_ERR(parse_fns
->uuid_record(value
, parse_baton
, pool
));
668 /* Or perhaps a dumpfile format? */
669 else if ((value
= apr_hash_get(headers
,
670 SVN_REPOS_DUMPFILE_MAGIC_HEADER
,
671 APR_HASH_KEY_STRING
)))
673 /* ### someday, switch modes of operation here. */
674 version
= atoi(value
);
676 /* Or is this bogosity?! */
679 /* What the heck is this record?!? */
680 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
681 _("Unrecognized record type in stream"));
684 /* Need 3 values below to determine v1 dump type
686 Old (pre 0.14?) v1 dumps don't have Prop-content-length
687 and Text-content-length fields, but always have a properties
688 block in a block with Content-Length > 0 */
690 content_length
= apr_hash_get(headers
,
691 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
,
692 APR_HASH_KEY_STRING
);
693 prop_cl
= apr_hash_get(headers
,
694 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
,
695 APR_HASH_KEY_STRING
);
696 text_cl
= apr_hash_get(headers
,
697 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
,
698 APR_HASH_KEY_STRING
);
700 version
== 1 && content_length
&& ! prop_cl
&& ! text_cl
;
702 /* Is there a props content-block to parse? */
703 if (prop_cl
|| old_v1_with_cl
)
705 const char *delta
= apr_hash_get(headers
,
706 SVN_REPOS_DUMPFILE_PROP_DELTA
,
707 APR_HASH_KEY_STRING
);
708 svn_boolean_t is_delta
= (delta
&& strcmp(delta
, "true") == 0);
710 /* First, remove all node properties, unless this is a delta
712 if (found_node
&& !is_delta
)
713 SVN_ERR(parse_fns
->remove_node_props(node_baton
));
715 SVN_ERR(parse_property_block
717 svn__atoui64(prop_cl
? prop_cl
: content_length
),
719 found_node
? node_baton
: rev_baton
,
722 found_node
? nodepool
: revpool
));
725 /* Is there a text content-block to parse? */
728 const char *delta
= apr_hash_get(headers
,
729 SVN_REPOS_DUMPFILE_TEXT_DELTA
,
730 APR_HASH_KEY_STRING
);
731 svn_boolean_t is_delta
= (delta
&& strcmp(delta
, "true") == 0);
733 SVN_ERR(parse_text_block(stream
,
734 svn__atoui64(text_cl
),
737 found_node
? node_baton
: rev_baton
,
740 found_node
? nodepool
: revpool
));
742 else if (old_v1_with_cl
)
744 /* An old-v1 block with a Content-length might have a text block.
745 If the property block did not consume all the bytes of the
746 Content-length, then it clearly does have a text block.
747 If not, then we must deduce whether we have an *empty* text
748 block or an *absent* text block. The rules are:
749 - "Node-kind: file" blocks have an empty (i.e. present, but
750 zero-length) text block, since they represent a file
751 modification. Note that file-copied-text-unmodified blocks
752 have no Content-length - even if they should have contained
753 a modified property block, the pre-0.14 dumper forgets to
754 dump the modified properties.
755 - If it is not a file node, then it is a revision or directory,
756 and so has an absent text block.
758 const char *node_kind
;
759 svn_filesize_t cl_value
= svn__atoui64(content_length
)
760 - actual_prop_length
;
763 ((node_kind
= apr_hash_get(headers
,
764 SVN_REPOS_DUMPFILE_NODE_KIND
,
765 APR_HASH_KEY_STRING
))
766 && strcmp(node_kind
, "file") == 0)
768 SVN_ERR(parse_text_block(stream
,
772 found_node
? node_baton
: rev_baton
,
775 found_node
? nodepool
: revpool
));
778 /* if we have a content-length header, did we read all of it?
779 in case of an old v1, we *always* read all of it, because
780 text-content-length == content-length - prop-content-length
782 if (content_length
&& ! old_v1_with_cl
)
784 apr_size_t rlen
, num_to_read
;
785 svn_filesize_t remaining
=
786 svn__atoui64(content_length
) -
787 (prop_cl
? svn__atoui64(prop_cl
) : 0) -
788 (text_cl
? svn__atoui64(text_cl
) : 0);
792 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
793 _("Sum of subblock sizes larger than "
794 "total block content length"));
796 /* Consume remaining bytes in this content block */
797 while (remaining
> 0)
799 if (remaining
>= buflen
)
802 rlen
= (apr_size_t
) remaining
;
805 SVN_ERR(svn_stream_read(stream
, buffer
, &rlen
));
807 if (rlen
!= num_to_read
)
808 return stream_ran_dry();
812 /* If we just finished processing a node record, we need to
813 close the node record and clear the per-node subpool. */
816 SVN_ERR(parse_fns
->close_node(node_baton
));
817 svn_pool_clear(nodepool
);
820 /*** End of processing for one record. ***/
822 } /* end of stream */
824 /* Close out whatever revision we're in. */
825 if (rev_baton
!= NULL
)
826 SVN_ERR(parse_fns
->close_revision(rev_baton
));
828 svn_pool_destroy(linepool
);
829 svn_pool_destroy(revpool
);
830 svn_pool_destroy(nodepool
);
836 svn_repos_parse_dumpstream(svn_stream_t
*stream
,
837 const svn_repos_parser_fns_t
*parse_fns
,
839 svn_cancel_func_t cancel_func
,
843 svn_repos_parse_fns2_t
*fns2
= fns2_from_fns(parse_fns
, pool
);
845 return svn_repos_parse_dumpstream2(stream
, fns2
, parse_baton
,
846 cancel_func
, cancel_baton
, pool
);
849 /*----------------------------------------------------------------------*/
851 /** vtable for doing commits to a fs **/
854 static struct node_baton
*
855 make_node_baton(apr_hash_t
*headers
,
856 struct revision_baton
*rb
,
859 struct node_baton
*nb
= apr_pcalloc(pool
, sizeof(*nb
));
862 /* Start with sensible defaults. */
865 nb
->kind
= svn_node_unknown
;
867 /* Then add info from the headers. */
868 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_PATH
,
869 APR_HASH_KEY_STRING
)))
871 if (rb
->pb
->parent_dir
)
872 nb
->path
= svn_path_join(rb
->pb
->parent_dir
, val
, pool
);
874 nb
->path
= apr_pstrdup(pool
, val
);
877 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_KIND
,
878 APR_HASH_KEY_STRING
)))
880 if (! strcmp(val
, "file"))
881 nb
->kind
= svn_node_file
;
882 else if (! strcmp(val
, "dir"))
883 nb
->kind
= svn_node_dir
;
886 nb
->action
= (enum svn_node_action
)(-1); /* an invalid action code */
887 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_ACTION
,
888 APR_HASH_KEY_STRING
)))
890 if (! strcmp(val
, "change"))
891 nb
->action
= svn_node_action_change
;
892 else if (! strcmp(val
, "add"))
893 nb
->action
= svn_node_action_add
;
894 else if (! strcmp(val
, "delete"))
895 nb
->action
= svn_node_action_delete
;
896 else if (! strcmp(val
, "replace"))
897 nb
->action
= svn_node_action_replace
;
900 nb
->copyfrom_rev
= SVN_INVALID_REVNUM
;
901 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
,
902 APR_HASH_KEY_STRING
)))
904 nb
->copyfrom_rev
= (svn_revnum_t
) atoi(val
);
906 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
,
907 APR_HASH_KEY_STRING
)))
909 if (rb
->pb
->parent_dir
)
910 nb
->copyfrom_path
= svn_path_join(rb
->pb
->parent_dir
,
911 (*val
== '/' ? val
+ 1 : val
), pool
);
913 nb
->copyfrom_path
= apr_pstrdup(pool
, val
);
916 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM
,
917 APR_HASH_KEY_STRING
)))
919 nb
->result_checksum
= apr_pstrdup(pool
, val
);
922 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM
,
923 APR_HASH_KEY_STRING
)))
925 nb
->base_checksum
= apr_pstrdup(pool
, val
);
928 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM
,
929 APR_HASH_KEY_STRING
)))
931 nb
->copy_source_checksum
= apr_pstrdup(pool
, val
);
934 /* What's cool about this dump format is that the parser just
935 ignores any unrecognized headers. :-) */
940 static struct revision_baton
*
941 make_revision_baton(apr_hash_t
*headers
,
942 struct parse_baton
*pb
,
945 struct revision_baton
*rb
= apr_pcalloc(pool
, sizeof(*rb
));
950 rb
->rev
= SVN_INVALID_REVNUM
;
952 if ((val
= apr_hash_get(headers
, SVN_REPOS_DUMPFILE_REVISION_NUMBER
,
953 APR_HASH_KEY_STRING
)))
954 rb
->rev
= SVN_STR_TO_REV(val
);
961 new_revision_record(void **revision_baton
,
966 struct parse_baton
*pb
= parse_baton
;
967 struct revision_baton
*rb
;
968 svn_revnum_t head_rev
;
970 rb
= make_revision_baton(headers
, pb
, pool
);
971 SVN_ERR(svn_fs_youngest_rev(&head_rev
, pb
->fs
, pool
));
973 /* FIXME: This is a lame fallback loading multiple segments of dump in
974 several seperate operations. It is highly susceptible to race conditions.
975 Calculate the revision 'offset' for finding copyfrom sources.
976 It might be positive or negative. */
977 rb
->rev_offset
= (rb
->rev
) - (head_rev
+ 1);
981 /* Create a new fs txn. */
982 SVN_ERR(svn_fs_begin_txn2(&(rb
->txn
), pb
->fs
, head_rev
, 0, pool
));
983 SVN_ERR(svn_fs_txn_root(&(rb
->txn_root
), rb
->txn
, pool
));
985 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
986 _("<<< Started new transaction, based on "
987 "original revision %ld\n"), rb
->rev
));
990 /* If we're parsing revision 0, only the revision are (possibly)
991 interesting to us: when loading the stream into an empty
992 filesystem, then we want new filesystem's revision 0 to have the
993 same props. Otherwise, we just ignore revision 0 in the stream. */
995 *revision_baton
= rb
;
1001 /* Factorized helper func for new_node_record() */
1002 static svn_error_t
*
1003 maybe_add_with_history(struct node_baton
*nb
,
1004 struct revision_baton
*rb
,
1007 struct parse_baton
*pb
= rb
->pb
;
1010 if ((nb
->copyfrom_path
== NULL
) || (! pb
->use_history
))
1012 /* Add empty file or dir, without history. */
1013 if (nb
->kind
== svn_node_file
)
1014 SVN_ERR(svn_fs_make_file(rb
->txn_root
, nb
->path
, pool
));
1016 else if (nb
->kind
== svn_node_dir
)
1017 SVN_ERR(svn_fs_make_dir(rb
->txn_root
, nb
->path
, pool
));
1021 /* Hunt down the source revision in this fs. */
1022 svn_fs_root_t
*copy_root
;
1023 svn_revnum_t src_rev
= nb
->copyfrom_rev
- rb
->rev_offset
;
1024 svn_revnum_t
*src_rev_from_map
;
1025 if ((src_rev_from_map
= apr_hash_get(pb
->rev_map
, &nb
->copyfrom_rev
,
1026 sizeof(nb
->copyfrom_rev
))))
1027 src_rev
= *src_rev_from_map
;
1029 if (! SVN_IS_VALID_REVNUM(src_rev
))
1030 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1031 _("Relative source revision %ld is not"
1032 " available in current repository"),
1035 SVN_ERR(svn_fs_revision_root(©_root
, pb
->fs
, src_rev
, pool
));
1037 if (nb
->copy_source_checksum
)
1039 unsigned char md5_digest
[APR_MD5_DIGESTSIZE
];
1041 SVN_ERR(svn_fs_file_md5_checksum(md5_digest
, copy_root
,
1042 nb
->copyfrom_path
, pool
));
1043 hex
= svn_md5_digest_to_cstring(md5_digest
, pool
);
1044 if (hex
&& (strcmp(nb
->copy_source_checksum
, hex
) != 0))
1045 return svn_error_createf
1046 (SVN_ERR_CHECKSUM_MISMATCH
,
1048 _("Copy source checksum mismatch on copy from '%s'@%ld\n"
1049 " to '%s' in rev based on r%ld:\n"
1052 nb
->copyfrom_path
, src_rev
,
1054 nb
->copy_source_checksum
, hex
);
1057 SVN_ERR(svn_fs_copy(copy_root
, nb
->copyfrom_path
,
1058 rb
->txn_root
, nb
->path
, pool
));
1061 SVN_ERR(svn_stream_write(pb
->outstream
, "COPIED...", &len
));
1064 return SVN_NO_ERROR
;
1068 static svn_error_t
*
1069 uuid_record(const char *uuid
,
1073 struct parse_baton
*pb
= parse_baton
;
1074 svn_revnum_t youngest_rev
;
1076 if (pb
->uuid_action
== svn_repos_load_uuid_ignore
)
1077 return SVN_NO_ERROR
;
1079 if (pb
->uuid_action
!= svn_repos_load_uuid_force
)
1081 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, pb
->fs
, pool
));
1082 if (youngest_rev
!= 0)
1083 return SVN_NO_ERROR
;
1086 return svn_fs_set_uuid(pb
->fs
, uuid
, pool
);
1089 static svn_error_t
*
1090 new_node_record(void **node_baton
,
1091 apr_hash_t
*headers
,
1092 void *revision_baton
,
1095 struct revision_baton
*rb
= revision_baton
;
1096 struct parse_baton
*pb
= rb
->pb
;
1097 struct node_baton
*nb
;
1100 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA
, NULL
,
1101 _("Malformed dumpstream: "
1102 "Revision 0 must not contain node records"));
1104 nb
= make_node_baton(headers
, rb
, pool
);
1108 case svn_node_action_change
:
1110 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
1111 _(" * editing path : %s ..."),
1115 case svn_node_action_delete
:
1117 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
1118 _(" * deleting path : %s ..."),
1120 SVN_ERR(svn_fs_delete(rb
->txn_root
, nb
->path
, pool
));
1123 case svn_node_action_add
:
1125 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
1126 _(" * adding path : %s ..."),
1129 SVN_ERR(maybe_add_with_history(nb
, rb
, pool
));
1132 case svn_node_action_replace
:
1134 SVN_ERR(svn_stream_printf(pb
->outstream
, pool
,
1135 _(" * replacing path : %s ..."),
1138 SVN_ERR(svn_fs_delete(rb
->txn_root
, nb
->path
, pool
));
1140 SVN_ERR(maybe_add_with_history(nb
, rb
, pool
));
1144 return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA
, NULL
,
1145 _("Unrecognized node-action on node '%s'"),
1150 return SVN_NO_ERROR
;
1154 static svn_error_t
*
1155 set_revision_property(void *baton
,
1157 const svn_string_t
*value
)
1159 struct revision_baton
*rb
= baton
;
1163 SVN_ERR(svn_fs_change_txn_prop(rb
->txn
, name
, value
, rb
->pool
));
1165 /* Remember any datestamp that passes through! (See comment in
1166 close_revision() below.) */
1167 if (! strcmp(name
, SVN_PROP_REVISION_DATE
))
1168 rb
->datestamp
= svn_string_dup(value
, rb
->pool
);
1170 else if (rb
->rev
== 0)
1172 /* Special case: set revision 0 properties when loading into an
1173 'empty' filesystem. */
1174 struct parse_baton
*pb
= rb
->pb
;
1175 svn_revnum_t youngest_rev
;
1177 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, pb
->fs
, rb
->pool
));
1179 if (youngest_rev
== 0)
1180 SVN_ERR(svn_fs_change_rev_prop(pb
->fs
, 0, name
, value
, rb
->pool
));
1183 return SVN_NO_ERROR
;
1187 static svn_error_t
*
1188 set_node_property(void *baton
,
1190 const svn_string_t
*value
)
1192 struct node_baton
*nb
= baton
;
1193 struct revision_baton
*rb
= nb
->rb
;
1194 const char *parent_dir
= rb
->pb
->parent_dir
;
1196 if (strcmp(name
, SVN_PROP_MERGEINFO
) == 0)
1198 /* Renumber mergeinfo as appropriate. */
1199 svn_string_t
*renumbered_mergeinfo
;
1200 SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo
, value
, rb
,
1202 value
= renumbered_mergeinfo
;
1205 /* Prefix the merge source paths with PARENT_DIR. */
1206 /* ASSUMPTION: All source paths are included in the dump stream. */
1207 svn_string_t
*mergeinfo_val
;
1208 SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val
, value
, parent_dir
,
1210 value
= mergeinfo_val
;
1214 SVN_ERR(svn_fs_change_node_prop(rb
->txn_root
, nb
->path
,
1215 name
, value
, nb
->pool
));
1217 return SVN_NO_ERROR
;
1221 static svn_error_t
*
1222 delete_node_property(void *baton
,
1225 struct node_baton
*nb
= baton
;
1226 struct revision_baton
*rb
= nb
->rb
;
1228 SVN_ERR(svn_fs_change_node_prop(rb
->txn_root
, nb
->path
,
1229 name
, NULL
, nb
->pool
));
1231 return SVN_NO_ERROR
;
1235 static svn_error_t
*
1236 remove_node_props(void *baton
)
1238 struct node_baton
*nb
= baton
;
1239 struct revision_baton
*rb
= nb
->rb
;
1240 apr_hash_t
*proplist
;
1241 apr_hash_index_t
*hi
;
1243 SVN_ERR(svn_fs_node_proplist(&proplist
,
1244 rb
->txn_root
, nb
->path
, nb
->pool
));
1246 for (hi
= apr_hash_first(nb
->pool
, proplist
); hi
; hi
= apr_hash_next(hi
))
1250 apr_hash_this(hi
, &key
, NULL
, NULL
);
1252 SVN_ERR(svn_fs_change_node_prop(rb
->txn_root
, nb
->path
,
1253 (const char *) key
, NULL
,
1257 return SVN_NO_ERROR
;
1261 static svn_error_t
*
1262 apply_textdelta(svn_txdelta_window_handler_t
*handler
,
1263 void **handler_baton
,
1266 struct node_baton
*nb
= node_baton
;
1267 struct revision_baton
*rb
= nb
->rb
;
1269 return svn_fs_apply_textdelta(handler
, handler_baton
,
1270 rb
->txn_root
, nb
->path
,
1271 nb
->base_checksum
, nb
->result_checksum
,
1276 static svn_error_t
*
1277 set_fulltext(svn_stream_t
**stream
,
1280 struct node_baton
*nb
= node_baton
;
1281 struct revision_baton
*rb
= nb
->rb
;
1283 return svn_fs_apply_text(stream
,
1284 rb
->txn_root
, nb
->path
,
1285 nb
->result_checksum
,
1290 static svn_error_t
*
1291 close_node(void *baton
)
1293 struct node_baton
*nb
= baton
;
1294 struct revision_baton
*rb
= nb
->rb
;
1295 struct parse_baton
*pb
= rb
->pb
;
1298 SVN_ERR(svn_stream_write(pb
->outstream
, _(" done.\n"), &len
));
1300 return SVN_NO_ERROR
;
1304 static svn_error_t
*
1305 close_revision(void *baton
)
1307 struct revision_baton
*rb
= baton
;
1308 struct parse_baton
*pb
= rb
->pb
;
1309 const char *conflict_msg
= NULL
;
1310 svn_revnum_t
*old_rev
, *new_rev
;
1314 return SVN_NO_ERROR
;
1316 /* Prepare memory for saving dump-rev -> in-repos-rev mapping. */
1317 old_rev
= apr_palloc(pb
->pool
, sizeof(*old_rev
) * 2);
1318 new_rev
= old_rev
+ 1;
1321 /* Run the pre-commit hook, if so commanded. */
1322 if (pb
->use_pre_commit_hook
)
1324 const char *txn_name
;
1325 err
= svn_fs_txn_name(&txn_name
, rb
->txn
, rb
->pool
);
1327 err
= svn_repos__hooks_pre_commit(pb
->repos
, txn_name
, rb
->pool
);
1330 svn_error_clear(svn_fs_abort_txn(rb
->txn
, rb
->pool
));
1336 if ((err
= svn_fs_commit_txn(&conflict_msg
, new_rev
, rb
->txn
, rb
->pool
)))
1338 svn_error_clear(svn_fs_abort_txn(rb
->txn
, rb
->pool
));
1340 return svn_error_quick_wrap(err
, conflict_msg
);
1345 /* Run post-commit hook, if so commanded. */
1346 if (pb
->use_post_commit_hook
)
1348 if ((err
= svn_repos__hooks_post_commit(pb
->repos
, *new_rev
, rb
->pool
)))
1349 return svn_error_create
1350 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED
, err
,
1351 _("Commit succeeded, but post-commit hook failed"));
1354 /* After a successful commit, must record the dump-rev -> in-repos-rev
1355 mapping, so that copyfrom instructions in the dump file can look up the
1356 correct repository revision to copy from. */
1357 apr_hash_set(pb
->rev_map
, old_rev
, sizeof(svn_revnum_t
), new_rev
);
1359 /* Deltify the predecessors of paths changed in this revision. */
1360 SVN_ERR(svn_fs_deltify_revision(pb
->fs
, *new_rev
, rb
->pool
));
1362 /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the
1363 current clock-time. We don't want that, we want to preserve
1364 history exactly. Good thing revision props aren't versioned!
1365 Note that if rb->datestamp is NULL, that's fine -- if the dump
1366 data doesn't carry a datestamp, we want to preserve that fact in
1368 SVN_ERR(svn_fs_change_rev_prop(pb
->fs
, *new_rev
,
1369 SVN_PROP_REVISION_DATE
, rb
->datestamp
,
1372 if (*new_rev
== rb
->rev
)
1374 SVN_ERR(svn_stream_printf(pb
->outstream
, rb
->pool
,
1375 _("\n------- Committed revision %ld"
1376 " >>>\n\n"), *new_rev
));
1380 SVN_ERR(svn_stream_printf(pb
->outstream
, rb
->pool
,
1381 _("\n------- Committed new rev %ld"
1382 " (loaded from original rev %ld"
1383 ") >>>\n\n"), *new_rev
, rb
->rev
));
1386 return SVN_NO_ERROR
;
1390 /*----------------------------------------------------------------------*/
1392 /** The public routines **/
1396 svn_repos_get_fs_build_parser2(const svn_repos_parse_fns2_t
**callbacks
,
1399 svn_boolean_t use_history
,
1400 enum svn_repos_load_uuid uuid_action
,
1401 svn_stream_t
*outstream
,
1402 const char *parent_dir
,
1405 const svn_repos_parser_fns_t
*fns
;
1406 svn_repos_parse_fns2_t
*parser
;
1408 /* Fetch the old-style vtable and baton, convert the vtable to a
1409 * new-style vtable, and set the new callbacks. */
1410 SVN_ERR(svn_repos_get_fs_build_parser(&fns
, parse_baton
, repos
,
1411 use_history
, uuid_action
, outstream
,
1413 parser
= fns2_from_fns(fns
, pool
);
1414 parser
->delete_node_property
= delete_node_property
;
1415 parser
->apply_textdelta
= apply_textdelta
;
1416 *callbacks
= parser
;
1417 return SVN_NO_ERROR
;
1423 svn_repos_get_fs_build_parser(const svn_repos_parser_fns_t
**parser_callbacks
,
1426 svn_boolean_t use_history
,
1427 enum svn_repos_load_uuid uuid_action
,
1428 svn_stream_t
*outstream
,
1429 const char *parent_dir
,
1432 svn_repos_parser_fns_t
*parser
= apr_pcalloc(pool
, sizeof(*parser
));
1433 struct parse_baton
*pb
= apr_pcalloc(pool
, sizeof(*pb
));
1435 parser
->new_revision_record
= new_revision_record
;
1436 parser
->new_node_record
= new_node_record
;
1437 parser
->uuid_record
= uuid_record
;
1438 parser
->set_revision_property
= set_revision_property
;
1439 parser
->set_node_property
= set_node_property
;
1440 parser
->remove_node_props
= remove_node_props
;
1441 parser
->set_fulltext
= set_fulltext
;
1442 parser
->close_node
= close_node
;
1443 parser
->close_revision
= close_revision
;
1446 pb
->fs
= svn_repos_fs(repos
);
1447 pb
->use_history
= use_history
;
1448 pb
->outstream
= outstream
? outstream
: svn_stream_empty(pool
);
1449 pb
->uuid_action
= uuid_action
;
1450 pb
->parent_dir
= parent_dir
;
1452 pb
->rev_map
= apr_hash_make(pool
);
1454 *parser_callbacks
= parser
;
1456 return SVN_NO_ERROR
;
1462 svn_repos_load_fs2(svn_repos_t
*repos
,
1463 svn_stream_t
*dumpstream
,
1464 svn_stream_t
*feedback_stream
,
1465 enum svn_repos_load_uuid uuid_action
,
1466 const char *parent_dir
,
1467 svn_boolean_t use_pre_commit_hook
,
1468 svn_boolean_t use_post_commit_hook
,
1469 svn_cancel_func_t cancel_func
,
1473 const svn_repos_parse_fns2_t
*parser
;
1475 struct parse_baton
*pb
;
1477 /* This is really simple. */
1479 SVN_ERR(svn_repos_get_fs_build_parser2(&parser
, &parse_baton
,
1481 TRUE
, /* look for copyfrom revs */
1487 /* Heh. We know this is a parse_baton. This file made it. So
1488 cast away, and set our hook booleans. */
1490 pb
->use_pre_commit_hook
= use_pre_commit_hook
;
1491 pb
->use_post_commit_hook
= use_post_commit_hook
;
1493 SVN_ERR(svn_repos_parse_dumpstream2(dumpstream
, parser
, parse_baton
,
1494 cancel_func
, cancel_baton
, pool
));
1496 return SVN_NO_ERROR
;
1501 svn_repos_load_fs(svn_repos_t
*repos
,
1502 svn_stream_t
*dumpstream
,
1503 svn_stream_t
*feedback_stream
,
1504 enum svn_repos_load_uuid uuid_action
,
1505 const char *parent_dir
,
1506 svn_cancel_func_t cancel_func
,
1510 return svn_repos_load_fs2(repos
, dumpstream
, feedback_stream
,
1511 uuid_action
, parent_dir
, FALSE
, FALSE
,
1512 cancel_func
, cancel_baton
, pool
);