2 * svndumpfilter.c: Subversion dump stream filtering tool main file.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
27 #include <apr_file_io.h>
29 #include "svn_private_config.h"
30 #include "svn_cmdline.h"
31 #include "svn_error.h"
32 #include "svn_string.h"
35 #include "svn_dirent_uri.h"
38 #include "svn_repos.h"
40 #include "svn_pools.h"
41 #include "svn_sorts.h"
42 #include "svn_props.h"
43 #include "svn_mergeinfo.h"
44 #include "svn_version.h"
46 #include "private/svn_mergeinfo_private.h"
47 #include "private/svn_cmdline_private.h"
48 #include "private/svn_subr_private.h"
51 typedef apr_status_t (__stdcall *open_fn_t)(apr_file_t **, apr_pool_t *);
53 typedef apr_status_t (*open_fn_t)(apr_file_t **, apr_pool_t *);
58 /* Helper to open stdio streams */
60 /* NOTE: we used to call svn_stream_from_stdio(), which wraps a stream
61 around a standard stdio.h FILE pointer. The problem is that these
62 pointers operate through C Run Time (CRT) on Win32, which does all
63 sorts of translation on them: LF's become CRLF's, and ctrl-Z's
64 embedded in Word documents are interpreted as premature EOF's.
66 So instead, we use apr_file_open_std*, which bypass the CRT and
67 directly wrap the OS's file-handles, which don't know or care about
68 translation. Thus dump/load works correctly on Win32.
71 create_stdio_stream(svn_stream_t **stream,
75 apr_file_t *stdio_file;
76 apr_status_t apr_err = open_fn(&stdio_file, pool);
79 return svn_error_wrap_apr(apr_err, _("Can't open stdio file"));
81 *stream = svn_stream_from_aprfile2(stdio_file, TRUE, pool);
86 /* Writes a property in dumpfile format to given stringbuf. */
88 write_prop_to_stringbuf(svn_stringbuf_t *strbuf,
90 const svn_string_t *value)
94 char buf[SVN_KEYLINE_MAXLEN];
96 /* Output name length, then name. */
97 namelen = strlen(name);
98 svn_stringbuf_appendbytes(strbuf, "K ", 2);
100 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
101 svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
102 svn_stringbuf_appendbyte(strbuf, '\n');
104 svn_stringbuf_appendbytes(strbuf, name, namelen);
105 svn_stringbuf_appendbyte(strbuf, '\n');
107 /* Output value length, then value. */
108 svn_stringbuf_appendbytes(strbuf, "V ", 2);
110 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len);
111 svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
112 svn_stringbuf_appendbyte(strbuf, '\n');
114 svn_stringbuf_appendbytes(strbuf, value->data, value->len);
115 svn_stringbuf_appendbyte(strbuf, '\n');
119 /* Writes a property deletion in dumpfile format to given stringbuf. */
121 write_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
126 char buf[SVN_KEYLINE_MAXLEN];
128 /* Output name length, then name. */
129 namelen = strlen(name);
130 svn_stringbuf_appendbytes(*strbuf, "D ", 2);
132 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
133 svn_stringbuf_appendbytes(*strbuf, buf, bytes_used);
134 svn_stringbuf_appendbyte(*strbuf, '\n');
136 svn_stringbuf_appendbytes(*strbuf, name, namelen);
137 svn_stringbuf_appendbyte(*strbuf, '\n');
141 /* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
142 * Return TRUE if any prefix is a prefix of PATH (matching whole path
143 * components); FALSE otherwise.
144 * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
146 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
149 size_t path_len = strlen(path);
151 for (i = 0; i < pfxlist->nelts; i++)
153 const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
154 size_t pfx_len = strlen(pfx);
156 if (path_len < pfx_len)
158 if (strncmp(path, pfx, pfx_len) == 0
159 && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
167 /* Check whether we need to skip this PATH based on its presence in
168 the PREFIXES list, and the DO_EXCLUDE option.
169 PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
170 static APR_INLINE svn_boolean_t
171 skip_path(const char *path, const apr_array_header_t *prefixes,
172 svn_boolean_t do_exclude, svn_boolean_t glob)
174 const svn_boolean_t matches =
176 ? svn_cstring_match_glob_list(path, prefixes)
177 : ary_prefix_match(prefixes, path));
180 return (matches ? do_exclude : !do_exclude);
185 /* Note: the input stream parser calls us with events.
186 Output of the filtered dump occurs for the most part streamily with the
187 event callbacks, to avoid caching large quantities of data in memory.
188 The exceptions this are:
189 - All revision data (headers and props) must be cached until a non-skipped
190 node within the revision is found, or the revision is closed.
191 - Node headers and props must be cached until all props have been received
192 (to allow the Prop-content-length to be found). This is signalled either
193 by the node text arriving, or the node being closed.
194 The writing_begun members of the associated object batons track the state.
195 output_revision() and output_node() are called to cause this flushing of
196 cached data to occur.
200 /* Filtering batons */
204 svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
205 svn_boolean_t was_dropped; /* Was this revision dropped? */
210 /* Command-line options values. */
211 svn_boolean_t do_exclude;
214 svn_boolean_t drop_empty_revs;
215 svn_boolean_t drop_all_empty_revs;
216 svn_boolean_t do_renumber_revs;
217 svn_boolean_t preserve_revprops;
218 svn_boolean_t skip_missing_merge_sources;
219 svn_boolean_t allow_deltas;
220 apr_array_header_t *prefixes;
222 /* Input and output streams. */
223 svn_stream_t *in_stream;
224 svn_stream_t *out_stream;
226 /* State for the filtering process. */
227 apr_int32_t rev_drop_count;
228 apr_hash_t *dropped_nodes;
229 apr_hash_t *renumber_history; /* svn_revnum_t -> struct revmap_t */
230 svn_revnum_t last_live_revision;
231 /* The oldest original revision, greater than r0, in the input
232 stream which was not filtered. */
233 svn_revnum_t oldest_original_rev;
236 struct revision_baton_t
238 /* Reference to the global parse baton. */
239 struct parse_baton_t *pb;
241 /* Does this revision have node or prop changes? */
242 svn_boolean_t has_nodes;
243 svn_boolean_t has_props;
245 /* Did we drop any nodes? */
246 svn_boolean_t had_dropped_nodes;
248 /* Written to output stream? */
249 svn_boolean_t writing_begun;
251 /* The original and new (re-mapped) revision numbers. */
252 svn_revnum_t rev_orig;
253 svn_revnum_t rev_actual;
255 /* Pointers to dumpfile data. */
256 svn_stringbuf_t *header;
262 /* Reference to the current revision baton. */
263 struct revision_baton_t *rb;
265 /* Are we skipping this node? */
266 svn_boolean_t do_skip;
268 /* Have we been instructed to change or remove props on, or change
269 the text of, this node? */
270 svn_boolean_t has_props;
271 svn_boolean_t has_text;
273 /* Written to output stream? */
274 svn_boolean_t writing_begun;
276 /* The text content length according to the dumpfile headers, because we
277 need the length before we have the actual text. */
280 /* Pointers to dumpfile data. */
281 svn_stringbuf_t *header;
282 svn_stringbuf_t *props;
285 svn_boolean_t has_prop_delta;
286 svn_boolean_t has_text_delta;
288 /* We might need the node path in a parse error message. */
294 /* Filtering vtable members */
296 /* File-format stamp. */
298 magic_header_record(int version, void *parse_baton, apr_pool_t *pool)
300 struct parse_baton_t *pb = parse_baton;
302 if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
303 pb->allow_deltas = TRUE;
305 SVN_ERR(svn_stream_printf(pb->out_stream, pool,
306 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
313 /* New revision: set up revision_baton, decide if we skip it. */
315 new_revision_record(void **revision_baton,
320 struct revision_baton_t *rb;
321 apr_hash_index_t *hi;
322 const char *rev_orig;
323 svn_stream_t *header_stream;
325 *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t));
326 rb = *revision_baton;
327 rb->pb = parse_baton;
328 rb->has_nodes = FALSE;
329 rb->has_props = FALSE;
330 rb->had_dropped_nodes = FALSE;
331 rb->writing_begun = FALSE;
332 rb->header = svn_stringbuf_create_empty(pool);
333 rb->props = apr_hash_make(pool);
335 header_stream = svn_stream_from_stringbuf(rb->header, pool);
337 rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
338 rb->rev_orig = SVN_STR_TO_REV(rev_orig);
340 if (rb->pb->do_renumber_revs)
341 rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
343 rb->rev_actual = rb->rev_orig;
345 SVN_ERR(svn_stream_printf(header_stream, pool,
346 SVN_REPOS_DUMPFILE_REVISION_NUMBER ": %ld\n",
349 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
351 const char *key = svn__apr_hash_index_key(hi);
352 const char *val = svn__apr_hash_index_val(hi);
354 if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
355 || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
356 || (!strcmp(key, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
359 /* passthru: put header into header stringbuf. */
361 SVN_ERR(svn_stream_printf(header_stream, pool, "%s: %s\n",
365 SVN_ERR(svn_stream_close(header_stream));
371 /* Output revision to dumpstream
372 This may be called by new_node_record(), iff rb->has_nodes has been set
373 to TRUE, or by close_revision() otherwise. This must only be called
374 if rb->writing_begun is FALSE. */
376 output_revision(struct revision_baton_t *rb)
379 char buf[SVN_KEYLINE_MAXLEN];
380 apr_hash_index_t *hi;
381 svn_boolean_t write_out_rev = FALSE;
382 apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
383 svn_stringbuf_t *props = svn_stringbuf_create_empty(hash_pool);
384 apr_pool_t *subpool = svn_pool_create(hash_pool);
386 rb->writing_begun = TRUE;
388 /* If this revision has no nodes left because the ones it had were
389 dropped, and we are not dropping empty revisions, and we were not
390 told to preserve revision props, then we want to fixup the
391 revision props to only contain:
393 - a log message that reports that this revision is just stuffing. */
394 if ((! rb->pb->preserve_revprops)
396 && rb->had_dropped_nodes
397 && (! rb->pb->drop_empty_revs)
398 && (! rb->pb->drop_all_empty_revs))
400 apr_hash_t *old_props = rb->props;
401 rb->has_props = TRUE;
402 rb->props = apr_hash_make(hash_pool);
403 svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE,
404 svn_hash_gets(old_props, SVN_PROP_REVISION_DATE));
405 svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG,
406 svn_string_create(_("This is an empty revision for "
407 "padding."), hash_pool));
410 /* Now, "rasterize" the props to a string, and append the property
411 information to the header string. */
414 for (hi = apr_hash_first(subpool, rb->props);
416 hi = apr_hash_next(hi))
418 const char *pname = svn__apr_hash_index_key(hi);
419 const svn_string_t *pval = svn__apr_hash_index_val(hi);
421 write_prop_to_stringbuf(props, pname, pval);
423 svn_stringbuf_appendcstr(props, "PROPS-END\n");
424 svn_stringbuf_appendcstr(rb->header,
425 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
426 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
428 svn_stringbuf_appendbytes(rb->header, buf, bytes_used);
429 svn_stringbuf_appendbyte(rb->header, '\n');
432 svn_stringbuf_appendcstr(rb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
433 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT, props->len);
434 svn_stringbuf_appendbytes(rb->header, buf, bytes_used);
435 svn_stringbuf_appendbyte(rb->header, '\n');
437 /* put an end to headers */
438 svn_stringbuf_appendbyte(rb->header, '\n');
440 /* put an end to revision */
441 svn_stringbuf_appendbyte(props, '\n');
443 /* write out the revision */
444 /* Revision is written out in the following cases:
445 1. If the revision has nodes or
446 it is revision 0 (Special case: To preserve the props on r0).
447 2. --drop-empty-revs has been supplied,
448 but revision has not all nodes dropped.
449 3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied,
450 write out the revision which has no nodes to begin with.
452 if (rb->has_nodes || (rb->rev_orig == 0))
453 write_out_rev = TRUE;
454 else if (rb->pb->drop_empty_revs)
455 write_out_rev = ! rb->had_dropped_nodes;
456 else if (! rb->pb->drop_all_empty_revs)
457 write_out_rev = TRUE;
461 /* This revision is a keeper. */
462 SVN_ERR(svn_stream_write(rb->pb->out_stream,
463 rb->header->data, &(rb->header->len)));
464 SVN_ERR(svn_stream_write(rb->pb->out_stream,
465 props->data, &(props->len)));
467 /* Stash the oldest original rev not dropped. */
469 && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
470 rb->pb->oldest_original_rev = rb->rev_orig;
472 if (rb->pb->do_renumber_revs)
474 svn_revnum_t *rr_key;
475 struct revmap_t *rr_val;
476 apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
477 rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
478 rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
479 *rr_key = rb->rev_orig;
480 rr_val->rev = rb->rev_actual;
481 rr_val->was_dropped = FALSE;
482 apr_hash_set(rb->pb->renumber_history, rr_key,
483 sizeof(*rr_key), rr_val);
484 rb->pb->last_live_revision = rb->rev_actual;
488 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
489 _("Revision %ld committed as %ld.\n"),
490 rb->rev_orig, rb->rev_actual));
494 /* We're dropping this revision. */
495 rb->pb->rev_drop_count++;
496 if (rb->pb->do_renumber_revs)
498 svn_revnum_t *rr_key;
499 struct revmap_t *rr_val;
500 apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
501 rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
502 rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
503 *rr_key = rb->rev_orig;
504 rr_val->rev = rb->pb->last_live_revision;
505 rr_val->was_dropped = TRUE;
506 apr_hash_set(rb->pb->renumber_history, rr_key,
507 sizeof(*rr_key), rr_val);
511 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
512 _("Revision %ld skipped.\n"),
515 svn_pool_destroy(subpool);
520 /* UUID record here: dump it, as we do not filter them. */
522 uuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
524 struct parse_baton_t *pb = parse_baton;
525 SVN_ERR(svn_stream_printf(pb->out_stream, pool,
526 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
531 /* New node here. Set up node_baton by copying headers. */
533 new_node_record(void **node_baton,
538 struct parse_baton_t *pb;
539 struct node_baton_t *nb;
540 char *node_path, *copyfrom_path;
541 apr_hash_index_t *hi;
544 *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
549 node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH);
550 copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH);
552 /* Ensure that paths start with a leading '/'. */
553 if (node_path[0] != '/')
554 node_path = apr_pstrcat(pool, "/", node_path, (char *)NULL);
555 if (copyfrom_path && copyfrom_path[0] != '/')
556 copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, (char *)NULL);
558 nb->do_skip = skip_path(node_path, pb->prefixes,
559 pb->do_exclude, pb->glob);
561 /* If we're skipping the node, take note of path, discarding the
565 svn_hash_sets(pb->dropped_nodes,
566 apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
569 nb->rb->had_dropped_nodes = TRUE;
576 tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
578 /* Test if this node was copied from dropped source. */
580 skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
582 /* This node was copied from a dropped source.
583 We have a problem, since we did not want to drop this node too.
585 However, there is one special case we'll handle. If the node is
586 a file, and this was a copy-and-modify operation, then the
587 dumpfile should contain the new contents of the file. In this
588 scenario, we'll just do an add without history using the new
590 kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
592 /* If there is a Text-content-length header, and the kind is
593 "file", we just fallback to an add without history. */
594 if (tcl && (strcmp(kind, "file") == 0))
596 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
598 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
600 copyfrom_path = NULL;
602 /* Else, this is either a directory or a file whose contents we
603 don't have readily available. */
606 return svn_error_createf
607 (SVN_ERR_INCOMPLETE_DATA, 0,
608 _("Invalid copy source path '%s'"), copyfrom_path);
612 nb->has_props = FALSE;
613 nb->has_text = FALSE;
614 nb->has_prop_delta = FALSE;
615 nb->has_text_delta = FALSE;
616 nb->writing_begun = FALSE;
617 nb->tcl = tcl ? svn__atoui64(tcl) : 0;
618 nb->header = svn_stringbuf_create_empty(pool);
619 nb->props = svn_stringbuf_create_empty(pool);
620 nb->node_path = apr_pstrdup(pool, node_path);
622 /* Now we know for sure that we have a node that will not be
623 skipped, flush the revision if it has not already been done. */
624 nb->rb->has_nodes = TRUE;
625 if (! nb->rb->writing_begun)
626 SVN_ERR(output_revision(nb->rb));
628 /* A node record is required to begin with 'Node-path', skip the
629 leading '/' to match the form used by 'svnadmin dump'. */
630 SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
632 SVN_REPOS_DUMPFILE_NODE_PATH, node_path + 1));
634 /* Node-kind is next and is optional. */
635 kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
637 SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
639 SVN_REPOS_DUMPFILE_NODE_KIND, kind));
641 /* Node-action is next and required. */
642 action = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION);
644 SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
646 SVN_REPOS_DUMPFILE_NODE_ACTION, action));
648 return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
649 _("Missing Node-action for path '%s'"),
652 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
654 const char *key = svn__apr_hash_index_key(hi);
655 const char *val = svn__apr_hash_index_val(hi);
657 if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
658 && (!strcmp(val, "true")))
659 nb->has_prop_delta = TRUE;
661 if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
662 && (!strcmp(val, "true")))
663 nb->has_text_delta = TRUE;
665 if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
666 || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
667 || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH))
668 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_PATH))
669 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_KIND))
670 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_ACTION)))
673 /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
674 The number points to some revision in the past. We keep track
675 of revision renumbering in an apr_hash, which maps original
676 revisions to new ones. Dropped revision are mapped to -1.
677 This should never happen here.
679 if (pb->do_renumber_revs
680 && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
682 svn_revnum_t cf_orig_rev;
683 struct revmap_t *cf_renum_val;
685 cf_orig_rev = SVN_STR_TO_REV(val);
686 cf_renum_val = apr_hash_get(pb->renumber_history,
688 sizeof(svn_revnum_t));
689 if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
690 return svn_error_createf
691 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
692 _("No valid copyfrom revision in filtered stream"));
693 SVN_ERR(svn_stream_printf
694 (nb->rb->pb->out_stream, pool,
695 SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV ": %ld\n",
700 /* passthru: put header straight to output */
702 SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
712 /* Output node header and props to dumpstream
713 This will be called by set_fulltext() after setting nb->has_text to TRUE,
714 if the node has any text, or by close_node() otherwise. This must only
715 be called if nb->writing_begun is FALSE. */
717 output_node(struct node_baton_t *nb)
720 char buf[SVN_KEYLINE_MAXLEN];
722 nb->writing_begun = TRUE;
724 /* when there are no props nb->props->len would be zero and won't mess up
727 svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
729 /* 1. recalculate & check text-md5 if present. Passed through right now. */
731 /* 2. recalculate and add content-lengths */
735 svn_stringbuf_appendcstr(nb->header,
736 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
737 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
739 svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
740 svn_stringbuf_appendbyte(nb->header, '\n');
744 svn_stringbuf_appendcstr(nb->header,
745 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
746 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
748 svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
749 svn_stringbuf_appendbyte(nb->header, '\n');
751 svn_stringbuf_appendcstr(nb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
752 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
753 (svn_filesize_t) (nb->props->len + nb->tcl));
754 svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
755 svn_stringbuf_appendbyte(nb->header, '\n');
757 /* put an end to headers */
758 svn_stringbuf_appendbyte(nb->header, '\n');
760 /* 3. output all the stuff */
762 SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
763 nb->header->data , &(nb->header->len)));
764 SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
765 nb->props->data , &(nb->props->len)));
771 /* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
772 sources or renumbering revisions in rangelists as appropriate, and
773 return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
776 adjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
777 struct revision_baton_t *rb, apr_pool_t *pool)
779 apr_hash_t *mergeinfo;
780 apr_hash_t *final_mergeinfo = apr_hash_make(pool);
781 apr_hash_index_t *hi;
782 apr_pool_t *subpool = svn_pool_create(pool);
784 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
786 /* Issue #3020: If we are skipping missing merge sources, then also
787 filter mergeinfo ranges as old or older than the oldest revision in the
788 dump stream. Those older than the oldest obviously refer to history
789 outside of the dump stream. The oldest rev itself is present in the
790 dump, but cannot be a valid merge source revision since it is the
791 start of all history. E.g. if we dump -r100:400 then dumpfilter the
792 result with --skip-missing-merge-sources, any mergeinfo with revision
793 100 implies a change of -r99:100, but r99 is part of the history we
796 If the oldest rev is r0 then there is nothing to filter. */
797 if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
798 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
799 &mergeinfo, mergeinfo,
800 rb->pb->oldest_original_rev, 0,
801 FALSE, subpool, subpool));
803 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
805 const char *merge_source = svn__apr_hash_index_key(hi);
806 svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
807 struct parse_baton_t *pb = rb->pb;
809 /* Determine whether the merge_source is a part of the prefix. */
810 if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
812 if (pb->skip_missing_merge_sources)
815 return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
816 _("Missing merge source path '%s'; try "
817 "with --skip-missing-merge-sources"),
821 /* Possibly renumber revisions in merge source's rangelist. */
822 if (pb->do_renumber_revs)
826 for (i = 0; i < rangelist->nelts; i++)
828 struct revmap_t *revmap_start;
829 struct revmap_t *revmap_end;
830 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
831 svn_merge_range_t *);
833 revmap_start = apr_hash_get(pb->renumber_history,
834 &range->start, sizeof(svn_revnum_t));
835 if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
836 return svn_error_createf
837 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
838 _("No valid revision range 'start' in filtered stream"));
840 revmap_end = apr_hash_get(pb->renumber_history,
841 &range->end, sizeof(svn_revnum_t));
842 if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
843 return svn_error_createf
844 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
845 _("No valid revision range 'end' in filtered stream"));
847 range->start = revmap_start->rev;
848 range->end = revmap_end->rev;
851 svn_hash_sets(final_mergeinfo, merge_source, rangelist);
854 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
855 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
856 svn_pool_destroy(subpool);
863 set_revision_property(void *revision_baton,
865 const svn_string_t *value)
867 struct revision_baton_t *rb = revision_baton;
868 apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
870 rb->has_props = TRUE;
871 svn_hash_sets(rb->props,
872 apr_pstrdup(hash_pool, name),
873 svn_string_dup(value, hash_pool));
879 set_node_property(void *node_baton,
881 const svn_string_t *value)
883 struct node_baton_t *nb = node_baton;
884 struct revision_baton_t *rb = nb->rb;
889 if (! (nb->has_props || nb->has_prop_delta))
890 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
891 _("Delta property block detected, but deltas "
892 "are not enabled for node '%s' in original "
894 nb->node_path, rb->rev_orig);
896 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
898 svn_string_t *filtered_mergeinfo; /* Avoid compiler warning. */
899 apr_pool_t *pool = apr_hash_pool_get(rb->props);
900 SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
901 value = filtered_mergeinfo;
904 nb->has_props = TRUE;
905 write_prop_to_stringbuf(nb->props, name, value);
912 delete_node_property(void *node_baton, const char *name)
914 struct node_baton_t *nb = node_baton;
915 struct revision_baton_t *rb = nb->rb;
920 if (!nb->has_prop_delta)
921 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
922 _("Delta property block detected, but deltas "
923 "are not enabled for node '%s' in original "
925 nb->node_path, rb->rev_orig);
927 nb->has_props = TRUE;
928 write_propdel_to_stringbuf(&(nb->props), name);
935 remove_node_props(void *node_baton)
937 struct node_baton_t *nb = node_baton;
939 /* In this case, not actually indicating that the node *has* props,
940 rather that we know about all the props that it has, since it now
942 nb->has_props = TRUE;
949 set_fulltext(svn_stream_t **stream, void *node_baton)
951 struct node_baton_t *nb = node_baton;
956 if (! nb->writing_begun)
957 SVN_ERR(output_node(nb));
958 *stream = nb->rb->pb->out_stream;
967 close_node(void *node_baton)
969 struct node_baton_t *nb = node_baton;
972 /* Get out of here if we can. */
976 /* If the node was not flushed already to output its text, do it now. */
977 if (! nb->writing_begun)
978 SVN_ERR(output_node(nb));
980 /* put an end to node. */
981 SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
987 /* Finalize revision */
989 close_revision(void *revision_baton)
991 struct revision_baton_t *rb = revision_baton;
993 /* If no node has yet flushed the revision, do it now. */
994 if (! rb->writing_begun)
995 return output_revision(rb);
1001 /* Filtering vtable */
1002 svn_repos_parse_fns3_t filtering_vtable =
1004 magic_header_record,
1006 new_revision_record,
1008 set_revision_property,
1010 delete_node_property,
1020 /** Subcommands. **/
1022 static svn_opt_subcommand_t
1029 svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
1030 svndumpfilter__drop_all_empty_revs,
1031 svndumpfilter__renumber_revs,
1032 svndumpfilter__preserve_revprops,
1033 svndumpfilter__skip_missing_merge_sources,
1034 svndumpfilter__targets,
1035 svndumpfilter__quiet,
1036 svndumpfilter__glob,
1037 svndumpfilter__version
1040 /* Option codes and descriptions.
1042 * The entire list must be terminated with an entry of nulls.
1044 static const apr_getopt_option_t options_table[] =
1047 N_("show help on a subcommand")},
1050 N_("show help on a subcommand")},
1052 {"version", svndumpfilter__version, 0,
1053 N_("show program version information") },
1054 {"quiet", svndumpfilter__quiet, 0,
1055 N_("Do not display filtering statistics.") },
1056 {"pattern", svndumpfilter__glob, 0,
1057 N_("Treat the path prefixes as file glob patterns.") },
1058 {"drop-empty-revs", svndumpfilter__drop_empty_revs, 0,
1059 N_("Remove revisions emptied by filtering.")},
1060 {"drop-all-empty-revs", svndumpfilter__drop_all_empty_revs, 0,
1061 N_("Remove all empty revisions found in dumpstream\n"
1062 " except revision 0.")},
1063 {"renumber-revs", svndumpfilter__renumber_revs, 0,
1064 N_("Renumber revisions left after filtering.") },
1065 {"skip-missing-merge-sources",
1066 svndumpfilter__skip_missing_merge_sources, 0,
1067 N_("Skip missing merge sources.") },
1068 {"preserve-revprops", svndumpfilter__preserve_revprops, 0,
1069 N_("Don't filter revision properties.") },
1070 {"targets", svndumpfilter__targets, 1,
1071 N_("Read additional prefixes, one per line, from\n"
1077 /* Array of available subcommands.
1078 * The entire list must be terminated with an entry of nulls.
1080 static const svn_opt_subcommand_desc2_t cmd_table[] =
1082 {"exclude", subcommand_exclude, {0},
1083 N_("Filter out nodes with given prefixes from dumpstream.\n"
1084 "usage: svndumpfilter exclude PATH_PREFIX...\n"),
1085 {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1086 svndumpfilter__renumber_revs,
1087 svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1088 svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1089 svndumpfilter__glob} },
1091 {"include", subcommand_include, {0},
1092 N_("Filter out nodes without given prefixes from dumpstream.\n"
1093 "usage: svndumpfilter include PATH_PREFIX...\n"),
1094 {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1095 svndumpfilter__renumber_revs,
1096 svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1097 svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1098 svndumpfilter__glob} },
1100 {"help", subcommand_help, {"?", "h"},
1101 N_("Describe the usage of this program or its subcommands.\n"
1102 "usage: svndumpfilter help [SUBCOMMAND...]\n"),
1105 { NULL, NULL, {0}, NULL, {0} }
1109 /* Baton for passing option/argument state to a subcommand function. */
1110 struct svndumpfilter_opt_state
1112 svn_opt_revision_t start_revision; /* -r X[:Y] is */
1113 svn_opt_revision_t end_revision; /* not implemented. */
1114 svn_boolean_t quiet; /* --quiet */
1115 svn_boolean_t glob; /* --pattern */
1116 svn_boolean_t version; /* --version */
1117 svn_boolean_t drop_empty_revs; /* --drop-empty-revs */
1118 svn_boolean_t drop_all_empty_revs; /* --drop-all-empty-revs */
1119 svn_boolean_t help; /* --help or -? */
1120 svn_boolean_t renumber_revs; /* --renumber-revs */
1121 svn_boolean_t preserve_revprops; /* --preserve-revprops */
1122 svn_boolean_t skip_missing_merge_sources;
1123 /* --skip-missing-merge-sources */
1124 const char *targets_file; /* --targets-file */
1125 apr_array_header_t *prefixes; /* mainargs. */
1129 static svn_error_t *
1130 parse_baton_initialize(struct parse_baton_t **pb,
1131 struct svndumpfilter_opt_state *opt_state,
1132 svn_boolean_t do_exclude,
1135 struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1137 /* Read the stream from STDIN. Users can redirect a file. */
1138 SVN_ERR(create_stdio_stream(&(baton->in_stream),
1139 apr_file_open_stdin, pool));
1141 /* Have the parser dump results to STDOUT. Users can redirect a file. */
1142 SVN_ERR(create_stdio_stream(&(baton->out_stream),
1143 apr_file_open_stdout, pool));
1145 baton->do_exclude = do_exclude;
1147 /* Ignore --renumber-revs if there can't possibly be
1148 anything to renumber. */
1149 baton->do_renumber_revs =
1150 (opt_state->renumber_revs && (opt_state->drop_empty_revs
1151 || opt_state->drop_all_empty_revs));
1153 baton->drop_empty_revs = opt_state->drop_empty_revs;
1154 baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
1155 baton->preserve_revprops = opt_state->preserve_revprops;
1156 baton->quiet = opt_state->quiet;
1157 baton->glob = opt_state->glob;
1158 baton->prefixes = opt_state->prefixes;
1159 baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
1160 baton->rev_drop_count = 0; /* used to shift revnums while filtering */
1161 baton->dropped_nodes = apr_hash_make(pool);
1162 baton->renumber_history = apr_hash_make(pool);
1163 baton->last_live_revision = SVN_INVALID_REVNUM;
1164 baton->oldest_original_rev = SVN_INVALID_REVNUM;
1165 baton->allow_deltas = FALSE;
1168 return SVN_NO_ERROR;
1171 /* This implements `help` subcommand. */
1172 static svn_error_t *
1173 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1175 struct svndumpfilter_opt_state *opt_state = baton;
1176 const char *header =
1177 _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
1178 "Type 'svndumpfilter help <subcommand>' for help on a "
1179 "specific subcommand.\n"
1180 "Type 'svndumpfilter --version' to see the program version.\n"
1182 "Available subcommands:\n");
1184 SVN_ERR(svn_opt_print_help4(os, "svndumpfilter",
1185 opt_state ? opt_state->version : FALSE,
1186 opt_state ? opt_state->quiet : FALSE,
1187 /*###opt_state ? opt_state->verbose :*/ FALSE,
1188 NULL, header, cmd_table, options_table,
1191 return SVN_NO_ERROR;
1195 /* Version compatibility check */
1196 static svn_error_t *
1197 check_lib_versions(void)
1199 static const svn_version_checklist_t checklist[] =
1201 { "svn_subr", svn_subr_version },
1202 { "svn_repos", svn_repos_version },
1203 { "svn_delta", svn_delta_version },
1206 SVN_VERSION_DEFINE(my_version);
1208 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1212 /* Do the real work of filtering. */
1213 static svn_error_t *
1214 do_filter(apr_getopt_t *os,
1216 svn_boolean_t do_exclude,
1219 struct svndumpfilter_opt_state *opt_state = baton;
1220 struct parse_baton_t *pb;
1221 apr_hash_index_t *hi;
1222 apr_array_header_t *keys;
1225 if (! opt_state->quiet)
1227 apr_pool_t *subpool = svn_pool_create(pool);
1229 if (opt_state->glob)
1231 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1233 ? (opt_state->drop_empty_revs
1234 || opt_state->drop_all_empty_revs)
1235 ? _("Excluding (and dropping empty "
1236 "revisions for) prefix patterns:\n")
1237 : _("Excluding prefix patterns:\n")
1238 : (opt_state->drop_empty_revs
1239 || opt_state->drop_all_empty_revs)
1240 ? _("Including (and dropping empty "
1241 "revisions for) prefix patterns:\n")
1242 : _("Including prefix patterns:\n")));
1246 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1248 ? (opt_state->drop_empty_revs
1249 || opt_state->drop_all_empty_revs)
1250 ? _("Excluding (and dropping empty "
1251 "revisions for) prefixes:\n")
1252 : _("Excluding prefixes:\n")
1253 : (opt_state->drop_empty_revs
1254 || opt_state->drop_all_empty_revs)
1255 ? _("Including (and dropping empty "
1256 "revisions for) prefixes:\n")
1257 : _("Including prefixes:\n")));
1260 for (i = 0; i < opt_state->prefixes->nelts; i++)
1262 svn_pool_clear(subpool);
1263 SVN_ERR(svn_cmdline_fprintf
1264 (stderr, subpool, " '%s'\n",
1265 APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
1268 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1269 svn_pool_destroy(subpool);
1272 SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
1273 SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
1274 TRUE, NULL, NULL, pool));
1276 /* The rest of this is just reporting. If we aren't reporting, get
1278 if (opt_state->quiet)
1279 return SVN_NO_ERROR;
1281 SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1283 if (pb->rev_drop_count)
1284 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1285 Q_("Dropped %d revision.\n\n",
1286 "Dropped %d revisions.\n\n",
1287 pb->rev_drop_count),
1288 pb->rev_drop_count));
1290 if (pb->do_renumber_revs)
1292 apr_pool_t *subpool = svn_pool_create(pool);
1293 SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1296 /* Get the keys of the hash, sort them, then print the hash keys
1297 and values, sorted by keys. */
1298 num_keys = apr_hash_count(pb->renumber_history);
1299 keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
1300 for (hi = apr_hash_first(pool, pb->renumber_history);
1302 hi = apr_hash_next(hi))
1304 const svn_revnum_t *revnum = svn__apr_hash_index_key(hi);
1306 APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1308 qsort(keys->elts, keys->nelts,
1309 keys->elt_size, svn_sort_compare_revisions);
1310 for (i = 0; i < keys->nelts; i++)
1312 svn_revnum_t this_key;
1313 struct revmap_t *this_val;
1315 svn_pool_clear(subpool);
1316 this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
1317 this_val = apr_hash_get(pb->renumber_history, &this_key,
1319 if (this_val->was_dropped)
1320 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1321 _(" %ld => (dropped)\n"),
1324 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1326 this_key, this_val->rev));
1328 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1329 svn_pool_destroy(subpool);
1332 if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1334 apr_pool_t *subpool = svn_pool_create(pool);
1335 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1336 Q_("Dropped %d node:\n",
1337 "Dropped %d nodes:\n",
1341 /* Get the keys of the hash, sort them, then print the hash keys
1342 and values, sorted by keys. */
1343 keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
1344 for (hi = apr_hash_first(pool, pb->dropped_nodes);
1346 hi = apr_hash_next(hi))
1348 const char *path = svn__apr_hash_index_key(hi);
1350 APR_ARRAY_PUSH(keys, const char *) = path;
1352 qsort(keys->elts, keys->nelts, keys->elt_size, svn_sort_compare_paths);
1353 for (i = 0; i < keys->nelts; i++)
1355 svn_pool_clear(subpool);
1356 SVN_ERR(svn_cmdline_fprintf
1357 (stderr, subpool, " '%s'\n",
1358 (const char *)APR_ARRAY_IDX(keys, i, const char *)));
1360 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1361 svn_pool_destroy(subpool);
1364 return SVN_NO_ERROR;
1367 /* This implements `exclude' subcommand. */
1368 static svn_error_t *
1369 subcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1371 return do_filter(os, baton, TRUE, pool);
1375 /* This implements `include` subcommand. */
1376 static svn_error_t *
1377 subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1379 return do_filter(os, baton, FALSE, pool);
1387 main(int argc, const char *argv[])
1390 apr_status_t apr_err;
1393 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1394 struct svndumpfilter_opt_state opt_state;
1397 apr_array_header_t *received_opts;
1401 /* Initialize the app. */
1402 if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1403 return EXIT_FAILURE;
1405 /* Create our top-level pool. Use a separate mutexless allocator,
1406 * given this application is single threaded.
1408 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1410 /* Check library versions */
1411 err = check_lib_versions();
1413 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1415 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1417 /* Initialize the FS library. */
1418 err = svn_fs_initialize(pool);
1420 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1424 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1425 svn_pool_destroy(pool);
1426 return EXIT_FAILURE;
1429 /* Initialize opt_state. */
1430 memset(&opt_state, 0, sizeof(opt_state));
1431 opt_state.start_revision.kind = svn_opt_revision_unspecified;
1432 opt_state.end_revision.kind = svn_opt_revision_unspecified;
1434 /* Parse options. */
1435 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1437 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1442 const char *opt_arg;
1444 /* Parse the next option. */
1445 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1446 if (APR_STATUS_IS_EOF(apr_err))
1450 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1451 svn_pool_destroy(pool);
1452 return EXIT_FAILURE;
1455 /* Stash the option code in an array before parsing it. */
1456 APR_ARRAY_PUSH(received_opts, int) = opt_id;
1462 opt_state.help = TRUE;
1464 case svndumpfilter__version:
1465 opt_state.version = TRUE;
1467 case svndumpfilter__quiet:
1468 opt_state.quiet = TRUE;
1470 case svndumpfilter__glob:
1471 opt_state.glob = TRUE;
1473 case svndumpfilter__drop_empty_revs:
1474 opt_state.drop_empty_revs = TRUE;
1476 case svndumpfilter__drop_all_empty_revs:
1477 opt_state.drop_all_empty_revs = TRUE;
1479 case svndumpfilter__renumber_revs:
1480 opt_state.renumber_revs = TRUE;
1482 case svndumpfilter__preserve_revprops:
1483 opt_state.preserve_revprops = TRUE;
1485 case svndumpfilter__skip_missing_merge_sources:
1486 opt_state.skip_missing_merge_sources = TRUE;
1488 case svndumpfilter__targets:
1489 opt_state.targets_file = opt_arg;
1493 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1494 svn_pool_destroy(pool);
1495 return EXIT_FAILURE;
1497 } /* close `switch' */
1498 } /* close `while' */
1500 /* Disallow simultaneous use of both --drop-empty-revs and
1501 --drop-all-empty-revs. */
1502 if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
1504 err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
1505 _("--drop-empty-revs cannot be used with "
1506 "--drop-all-empty-revs"));
1507 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1510 /* If the user asked for help, then the rest of the arguments are
1511 the names of subcommands to get help on (if any), or else they're
1512 just typos/mistakes. Whatever the case, the subcommand to
1513 actually run is subcommand_help(). */
1515 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
1517 /* If we're not running the `help' subcommand, then look for a
1518 subcommand in the first argument. */
1519 if (subcommand == NULL)
1521 if (os->ind >= os->argc)
1523 if (opt_state.version)
1525 /* Use the "help" subcommand to handle the "--version" option. */
1526 static const svn_opt_subcommand_desc2_t pseudo_cmd =
1527 { "--version", subcommand_help, {0}, "",
1528 {svndumpfilter__version, /* must accept its own option */
1529 svndumpfilter__quiet,
1532 subcommand = &pseudo_cmd;
1536 svn_error_clear(svn_cmdline_fprintf
1538 _("Subcommand argument required\n")));
1539 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1540 svn_pool_destroy(pool);
1541 return EXIT_FAILURE;
1546 const char *first_arg = os->argv[os->ind++];
1547 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
1548 if (subcommand == NULL)
1550 const char* first_arg_utf8;
1551 if ((err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
1553 return svn_cmdline_handle_exit_error(err, pool,
1557 svn_cmdline_fprintf(stderr, pool,
1558 _("Unknown subcommand: '%s'\n"),
1560 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1561 svn_pool_destroy(pool);
1562 return EXIT_FAILURE;
1567 /* If there's a second argument, it's probably [one of] prefixes.
1568 Every subcommand except `help' requires at least one, so we parse
1569 them out here and store in opt_state. */
1571 if (subcommand->cmd_func != subcommand_help)
1574 opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
1575 sizeof(const char *));
1576 for (i = os->ind ; i< os->argc; i++)
1580 /* Ensure that each prefix is UTF8-encoded, in internal
1581 style, and absolute. */
1582 SVN_INT_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
1583 prefix = svn_relpath__internal_style(prefix, pool);
1584 if (prefix[0] != '/')
1585 prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
1586 APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1589 if (opt_state.targets_file)
1591 svn_stringbuf_t *buffer, *buffer_utf8;
1592 const char *utf8_targets_file;
1593 apr_array_header_t *targets = apr_array_make(pool, 0,
1594 sizeof(const char *));
1596 /* We need to convert to UTF-8 now, even before we divide
1597 the targets into an array, because otherwise we wouldn't
1598 know what delimiter to use for svn_cstring_split(). */
1600 SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_targets_file,
1601 opt_state.targets_file, pool));
1603 SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_targets_file,
1605 SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1607 targets = apr_array_append(pool,
1608 svn_cstring_split(buffer_utf8->data, "\n\r",
1612 for (i = 0; i < targets->nelts; i++)
1614 const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
1615 if (prefix[0] != '/')
1616 prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
1617 APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1621 if (apr_is_empty_array(opt_state.prefixes))
1623 svn_error_clear(svn_cmdline_fprintf
1625 _("\nError: no prefixes supplied.\n")));
1626 svn_pool_destroy(pool);
1627 return EXIT_FAILURE;
1632 /* Check that the subcommand wasn't passed any inappropriate options. */
1633 for (i = 0; i < received_opts->nelts; i++)
1635 opt_id = APR_ARRAY_IDX(received_opts, i, int);
1637 /* All commands implicitly accept --help, so just skip over this
1638 when we see it. Note that we don't want to include this option
1639 in their "accepted options" list because it would be awfully
1640 redundant to display it in every commands' help text. */
1641 if (opt_id == 'h' || opt_id == '?')
1644 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1647 const apr_getopt_option_t *badopt =
1648 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
1650 svn_opt_format_option(&optstr, badopt, FALSE, pool);
1651 if (subcommand->name[0] == '-')
1652 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1654 svn_error_clear(svn_cmdline_fprintf
1656 _("Subcommand '%s' doesn't accept option '%s'\n"
1657 "Type 'svndumpfilter help %s' for usage.\n"),
1658 subcommand->name, optstr, subcommand->name));
1659 svn_pool_destroy(pool);
1660 return EXIT_FAILURE;
1664 /* Run the subcommand. */
1665 err = (*subcommand->cmd_func)(os, &opt_state, pool);
1668 /* For argument-related problems, suggest using the 'help'
1670 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1671 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1673 err = svn_error_quick_wrap(err,
1674 _("Try 'svndumpfilter help' for more "
1677 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1681 svn_pool_destroy(pool);
1683 /* Flush stdout, making sure the user will see any print errors. */
1684 SVN_INT_ERR(svn_cmdline_fflush(stdout));
1685 return EXIT_SUCCESS;