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_repos_private.h"
47 #include "private/svn_mergeinfo_private.h"
48 #include "private/svn_cmdline_private.h"
49 #include "private/svn_sorts_private.h"
53 /* Writes a property in dumpfile format to given stringbuf. */
55 write_prop_to_stringbuf(svn_stringbuf_t *strbuf,
57 const svn_string_t *value)
61 char buf[SVN_KEYLINE_MAXLEN];
63 /* Output name length, then name. */
64 namelen = strlen(name);
65 svn_stringbuf_appendbytes(strbuf, "K ", 2);
67 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
68 svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
69 svn_stringbuf_appendbyte(strbuf, '\n');
71 svn_stringbuf_appendbytes(strbuf, name, namelen);
72 svn_stringbuf_appendbyte(strbuf, '\n');
74 /* Output value length, then value. */
75 svn_stringbuf_appendbytes(strbuf, "V ", 2);
77 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len);
78 svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
79 svn_stringbuf_appendbyte(strbuf, '\n');
81 svn_stringbuf_appendbytes(strbuf, value->data, value->len);
82 svn_stringbuf_appendbyte(strbuf, '\n');
86 /* Writes a property deletion in dumpfile format to given stringbuf. */
88 write_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
93 char buf[SVN_KEYLINE_MAXLEN];
95 /* Output name length, then name. */
96 namelen = strlen(name);
97 svn_stringbuf_appendbytes(*strbuf, "D ", 2);
99 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
100 svn_stringbuf_appendbytes(*strbuf, buf, bytes_used);
101 svn_stringbuf_appendbyte(*strbuf, '\n');
103 svn_stringbuf_appendbytes(*strbuf, name, namelen);
104 svn_stringbuf_appendbyte(*strbuf, '\n');
108 /* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
109 * Return TRUE if any prefix is a prefix of PATH (matching whole path
110 * components); FALSE otherwise.
111 * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
112 /* This function is a duplicate of svnadmin.c:ary_prefix_match(). */
114 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
117 size_t path_len = strlen(path);
119 for (i = 0; i < pfxlist->nelts; i++)
121 const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
122 size_t pfx_len = strlen(pfx);
124 if (path_len < pfx_len)
126 if (strncmp(path, pfx, pfx_len) == 0
127 && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
135 /* Check whether we need to skip this PATH based on its presence in
136 the PREFIXES list, and the DO_EXCLUDE option.
137 PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
138 static APR_INLINE svn_boolean_t
139 skip_path(const char *path, const apr_array_header_t *prefixes,
140 svn_boolean_t do_exclude, svn_boolean_t glob)
142 const svn_boolean_t matches =
144 ? svn_cstring_match_glob_list(path, prefixes)
145 : ary_prefix_match(prefixes, path));
148 return (matches ? do_exclude : !do_exclude);
153 /* Note: the input stream parser calls us with events.
154 Output of the filtered dump occurs for the most part streamily with the
155 event callbacks, to avoid caching large quantities of data in memory.
156 The exceptions this are:
157 - All revision data (headers and props) must be cached until a non-skipped
158 node within the revision is found, or the revision is closed.
159 - Node headers and props must be cached until all props have been received
160 (to allow the Prop-content-length to be found). This is signalled either
161 by the node text arriving, or the node being closed.
162 The writing_begun members of the associated object batons track the state.
163 output_revision() and output_node() are called to cause this flushing of
164 cached data to occur.
168 /* Filtering batons */
172 svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
173 svn_boolean_t was_dropped; /* Was this revision dropped? */
178 /* Command-line options values. */
179 svn_boolean_t do_exclude;
182 svn_boolean_t drop_empty_revs;
183 svn_boolean_t drop_all_empty_revs;
184 svn_boolean_t do_renumber_revs;
185 svn_boolean_t preserve_revprops;
186 svn_boolean_t skip_missing_merge_sources;
187 svn_boolean_t allow_deltas;
188 apr_array_header_t *prefixes;
190 /* Input and output streams. */
191 svn_stream_t *in_stream;
192 svn_stream_t *out_stream;
194 /* State for the filtering process. */
195 apr_int32_t rev_drop_count;
196 apr_hash_t *dropped_nodes;
197 apr_hash_t *renumber_history; /* svn_revnum_t -> struct revmap_t */
198 svn_revnum_t last_live_revision;
199 /* The oldest original revision, greater than r0, in the input
200 stream which was not filtered. */
201 svn_revnum_t oldest_original_rev;
204 struct revision_baton_t
206 /* Reference to the global parse baton. */
207 struct parse_baton_t *pb;
209 /* Does this revision have node or prop changes? */
210 svn_boolean_t has_nodes;
212 /* Did we drop any nodes? */
213 svn_boolean_t had_dropped_nodes;
215 /* Written to output stream? */
216 svn_boolean_t writing_begun;
218 /* The original and new (re-mapped) revision numbers. */
219 svn_revnum_t rev_orig;
220 svn_revnum_t rev_actual;
222 /* Pointers to dumpfile data. */
223 apr_hash_t *original_headers;
229 /* Reference to the current revision baton. */
230 struct revision_baton_t *rb;
232 /* Are we skipping this node? */
233 svn_boolean_t do_skip;
235 /* Have we been instructed to change or remove props on, or change
236 the text of, this node? */
237 svn_boolean_t has_props;
238 svn_boolean_t has_text;
240 /* Written to output stream? */
241 svn_boolean_t writing_begun;
243 /* The text content length according to the dumpfile headers, because we
244 need the length before we have the actual text. */
247 /* Pointers to dumpfile data. */
248 svn_repos__dumpfile_headers_t *headers;
249 svn_stringbuf_t *props;
252 svn_boolean_t has_prop_delta;
253 svn_boolean_t has_text_delta;
255 /* We might need the node path in a parse error message. */
258 apr_pool_t *node_pool;
263 /* Filtering vtable members */
265 /* File-format stamp. */
267 magic_header_record(int version, void *parse_baton, apr_pool_t *pool)
269 struct parse_baton_t *pb = parse_baton;
271 if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
272 pb->allow_deltas = TRUE;
274 SVN_ERR(svn_stream_printf(pb->out_stream, pool,
275 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
282 /* Return a deep copy of a (char * -> char *) hash. */
284 headers_dup(apr_hash_t *headers,
287 apr_hash_t *new_hash = apr_hash_make(pool);
288 apr_hash_index_t *hi;
290 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
292 const char *key = apr_hash_this_key(hi);
293 const char *val = apr_hash_this_val(hi);
295 svn_hash_sets(new_hash, apr_pstrdup(pool, key), apr_pstrdup(pool, val));
300 /* New revision: set up revision_baton, decide if we skip it. */
302 new_revision_record(void **revision_baton,
307 struct revision_baton_t *rb;
308 const char *rev_orig;
310 *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t));
311 rb = *revision_baton;
312 rb->pb = parse_baton;
313 rb->has_nodes = FALSE;
314 rb->had_dropped_nodes = FALSE;
315 rb->writing_begun = FALSE;
316 rb->props = apr_hash_make(pool);
317 rb->original_headers = headers_dup(headers, pool);
319 rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
320 rb->rev_orig = SVN_STR_TO_REV(rev_orig);
322 if (rb->pb->do_renumber_revs)
323 rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
325 rb->rev_actual = rb->rev_orig;
331 /* Output revision to dumpstream
332 This may be called by new_node_record(), iff rb->has_nodes has been set
333 to TRUE, or by close_revision() otherwise. This must only be called
334 if rb->writing_begun is FALSE. */
336 output_revision(struct revision_baton_t *rb)
338 svn_boolean_t write_out_rev = FALSE;
339 apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
340 apr_pool_t *subpool = svn_pool_create(hash_pool);
342 rb->writing_begun = TRUE;
344 /* If this revision has no nodes left because the ones it had were
345 dropped, and we are not dropping empty revisions, and we were not
346 told to preserve revision props, then we want to fixup the
347 revision props to only contain:
349 - a log message that reports that this revision is just stuffing. */
350 if ((! rb->pb->preserve_revprops)
352 && rb->had_dropped_nodes
353 && (! rb->pb->drop_empty_revs)
354 && (! rb->pb->drop_all_empty_revs))
356 apr_hash_t *old_props = rb->props;
357 rb->props = apr_hash_make(hash_pool);
358 svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE,
359 svn_hash_gets(old_props, SVN_PROP_REVISION_DATE));
360 svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG,
361 svn_string_create(_("This is an empty revision for "
362 "padding."), hash_pool));
365 /* write out the revision */
366 /* Revision is written out in the following cases:
367 1. If the revision has nodes or
368 it is revision 0 (Special case: To preserve the props on r0).
369 2. --drop-empty-revs has been supplied,
370 but revision has not all nodes dropped.
371 3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied,
372 write out the revision which has no nodes to begin with.
374 if (rb->has_nodes || (rb->rev_orig == 0))
375 write_out_rev = TRUE;
376 else if (rb->pb->drop_empty_revs)
377 write_out_rev = ! rb->had_dropped_nodes;
378 else if (! rb->pb->drop_all_empty_revs)
379 write_out_rev = TRUE;
383 /* This revision is a keeper. */
384 SVN_ERR(svn_repos__dump_revision_record(rb->pb->out_stream,
386 rb->original_headers,
388 FALSE /*props_section_always*/,
391 /* Stash the oldest original rev not dropped. */
393 && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
394 rb->pb->oldest_original_rev = rb->rev_orig;
396 if (rb->pb->do_renumber_revs)
398 svn_revnum_t *rr_key;
399 struct revmap_t *rr_val;
400 apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
401 rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
402 rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
403 *rr_key = rb->rev_orig;
404 rr_val->rev = rb->rev_actual;
405 rr_val->was_dropped = FALSE;
406 apr_hash_set(rb->pb->renumber_history, rr_key,
407 sizeof(*rr_key), rr_val);
408 rb->pb->last_live_revision = rb->rev_actual;
412 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
413 _("Revision %ld committed as %ld.\n"),
414 rb->rev_orig, rb->rev_actual));
418 /* We're dropping this revision. */
419 rb->pb->rev_drop_count++;
420 if (rb->pb->do_renumber_revs)
422 svn_revnum_t *rr_key;
423 struct revmap_t *rr_val;
424 apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
425 rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
426 rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
427 *rr_key = rb->rev_orig;
428 rr_val->rev = rb->pb->last_live_revision;
429 rr_val->was_dropped = TRUE;
430 apr_hash_set(rb->pb->renumber_history, rr_key,
431 sizeof(*rr_key), rr_val);
435 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
436 _("Revision %ld skipped.\n"),
439 svn_pool_destroy(subpool);
444 /* UUID record here: dump it, as we do not filter them. */
446 uuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
448 struct parse_baton_t *pb = parse_baton;
449 SVN_ERR(svn_stream_printf(pb->out_stream, pool,
450 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
455 /* New node here. Set up node_baton by copying headers. */
457 new_node_record(void **node_baton,
462 struct parse_baton_t *pb;
463 struct node_baton_t *nb;
464 char *node_path, *copyfrom_path;
465 apr_hash_index_t *hi;
468 *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
471 nb->node_pool = pool;
474 node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH);
475 copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH);
477 /* Ensure that paths start with a leading '/'. */
478 if (node_path[0] != '/')
479 node_path = apr_pstrcat(pool, "/", node_path, SVN_VA_NULL);
480 if (copyfrom_path && copyfrom_path[0] != '/')
481 copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, SVN_VA_NULL);
483 nb->do_skip = skip_path(node_path, pb->prefixes,
484 pb->do_exclude, pb->glob);
486 /* If we're skipping the node, take note of path, discarding the
490 svn_hash_sets(pb->dropped_nodes,
491 apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
494 nb->rb->had_dropped_nodes = TRUE;
501 tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
503 /* Test if this node was copied from dropped source. */
505 skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
507 /* This node was copied from a dropped source.
508 We have a problem, since we did not want to drop this node too.
510 However, there is one special case we'll handle. If the node is
511 a file, and this was a copy-and-modify operation, then the
512 dumpfile should contain the new contents of the file. In this
513 scenario, we'll just do an add without history using the new
515 kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
517 /* If there is a Text-content-length header, and the kind is
518 "file", we just fallback to an add without history. */
519 if (tcl && (strcmp(kind, "file") == 0))
521 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
523 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
525 copyfrom_path = NULL;
527 /* Else, this is either a directory or a file whose contents we
528 don't have readily available. */
531 return svn_error_createf
532 (SVN_ERR_INCOMPLETE_DATA, 0,
533 _("Invalid copy source path '%s'"), copyfrom_path);
537 nb->has_props = FALSE;
538 nb->has_text = FALSE;
539 nb->has_prop_delta = FALSE;
540 nb->has_text_delta = FALSE;
541 nb->writing_begun = FALSE;
542 nb->tcl = tcl ? svn__atoui64(tcl) : 0;
543 nb->headers = svn_repos__dumpfile_headers_create(pool);
544 nb->props = svn_stringbuf_create_empty(pool);
545 nb->node_path = apr_pstrdup(pool, node_path);
547 /* Now we know for sure that we have a node that will not be
548 skipped, flush the revision if it has not already been done. */
549 nb->rb->has_nodes = TRUE;
550 if (! nb->rb->writing_begun)
551 SVN_ERR(output_revision(nb->rb));
553 /* A node record is required to begin with 'Node-path', skip the
554 leading '/' to match the form used by 'svnadmin dump'. */
555 svn_repos__dumpfile_header_push(
556 nb->headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_path + 1);
558 /* Node-kind is next and is optional. */
559 kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
561 svn_repos__dumpfile_header_push(
562 nb->headers, SVN_REPOS_DUMPFILE_NODE_KIND, kind);
564 /* Node-action is next and required. */
565 action = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION);
567 svn_repos__dumpfile_header_push(
568 nb->headers, SVN_REPOS_DUMPFILE_NODE_ACTION, action);
570 return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
571 _("Missing Node-action for path '%s'"),
574 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
576 const char *key = apr_hash_this_key(hi);
577 const char *val = apr_hash_this_val(hi);
579 if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
580 && (!strcmp(val, "true")))
581 nb->has_prop_delta = TRUE;
583 if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
584 && (!strcmp(val, "true")))
585 nb->has_text_delta = TRUE;
587 if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
588 || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
589 || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH))
590 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_PATH))
591 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_KIND))
592 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_ACTION)))
595 /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
596 The number points to some revision in the past. We keep track
597 of revision renumbering in an apr_hash, which maps original
598 revisions to new ones. Dropped revision are mapped to -1.
599 This should never happen here.
601 if (pb->do_renumber_revs
602 && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
604 svn_revnum_t cf_orig_rev;
605 struct revmap_t *cf_renum_val;
607 cf_orig_rev = SVN_STR_TO_REV(val);
608 cf_renum_val = apr_hash_get(pb->renumber_history,
610 sizeof(cf_orig_rev));
611 if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
612 return svn_error_createf
613 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
614 _("No valid copyfrom revision in filtered stream"));
615 svn_repos__dumpfile_header_pushf(
616 nb->headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
617 "%ld", cf_renum_val->rev);
621 /* passthru: put header straight to output */
622 svn_repos__dumpfile_header_push(nb->headers, key, val);
630 /* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
631 sources or renumbering revisions in rangelists as appropriate, and
632 return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
635 adjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
636 struct revision_baton_t *rb, apr_pool_t *pool)
638 apr_hash_t *mergeinfo;
639 apr_hash_t *final_mergeinfo = apr_hash_make(pool);
640 apr_hash_index_t *hi;
641 apr_pool_t *subpool = svn_pool_create(pool);
643 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
645 /* Issue #3020: If we are skipping missing merge sources, then also
646 filter mergeinfo ranges as old or older than the oldest revision in the
647 dump stream. Those older than the oldest obviously refer to history
648 outside of the dump stream. The oldest rev itself is present in the
649 dump, but cannot be a valid merge source revision since it is the
650 start of all history. E.g. if we dump -r100:400 then dumpfilter the
651 result with --skip-missing-merge-sources, any mergeinfo with revision
652 100 implies a change of -r99:100, but r99 is part of the history we
655 If the oldest rev is r0 then there is nothing to filter. */
657 /* ### This seems to cater only for use cases where the revisions being
658 processed are not following on from revisions that will already
659 exist in the destination repository. If the revisions being
660 processed do follow on, then we might want to keep the mergeinfo
661 that refers to those older revisions. */
663 if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
664 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
665 &mergeinfo, mergeinfo,
666 rb->pb->oldest_original_rev, 0,
667 FALSE, subpool, subpool));
669 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
671 const char *merge_source = apr_hash_this_key(hi);
672 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
673 struct parse_baton_t *pb = rb->pb;
675 /* Determine whether the merge_source is a part of the prefix. */
676 if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
678 if (pb->skip_missing_merge_sources)
681 return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
682 _("Missing merge source path '%s'; try "
683 "with --skip-missing-merge-sources"),
687 /* Possibly renumber revisions in merge source's rangelist. */
688 if (pb->do_renumber_revs)
692 for (i = 0; i < rangelist->nelts; i++)
694 struct revmap_t *revmap_start;
695 struct revmap_t *revmap_end;
696 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
697 svn_merge_range_t *);
699 revmap_start = apr_hash_get(pb->renumber_history,
700 &range->start, sizeof(range->start));
701 if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
702 return svn_error_createf
703 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
704 _("No valid revision range 'start' in filtered stream"));
706 revmap_end = apr_hash_get(pb->renumber_history,
707 &range->end, sizeof(range->end));
708 if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
709 return svn_error_createf
710 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
711 _("No valid revision range 'end' in filtered stream"));
713 range->start = revmap_start->rev;
714 range->end = revmap_end->rev;
717 svn_hash_sets(final_mergeinfo, merge_source, rangelist);
720 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
721 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
722 svn_pool_destroy(subpool);
729 set_revision_property(void *revision_baton,
731 const svn_string_t *value)
733 struct revision_baton_t *rb = revision_baton;
734 apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
736 svn_hash_sets(rb->props,
737 apr_pstrdup(hash_pool, name),
738 svn_string_dup(value, hash_pool));
744 set_node_property(void *node_baton,
746 const svn_string_t *value)
748 struct node_baton_t *nb = node_baton;
749 struct revision_baton_t *rb = nb->rb;
754 /* Try to detect if a delta-mode property occurs unexpectedly. HAS_PROPS
755 can be false here only if the parser didn't call remove_node_props(),
756 so this may indicate a bug rather than bad data. */
757 if (! (nb->has_props || nb->has_prop_delta))
758 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
759 _("Delta property block detected, but deltas "
760 "are not enabled for node '%s' in original "
762 nb->node_path, rb->rev_orig);
764 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
766 svn_string_t *filtered_mergeinfo; /* Avoid compiler warning. */
767 apr_pool_t *pool = apr_hash_pool_get(rb->props);
768 SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
769 value = filtered_mergeinfo;
772 nb->has_props = TRUE;
773 write_prop_to_stringbuf(nb->props, name, value);
780 delete_node_property(void *node_baton, const char *name)
782 struct node_baton_t *nb = node_baton;
783 struct revision_baton_t *rb = nb->rb;
788 if (!nb->has_prop_delta)
789 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
790 _("Delta property block detected, but deltas "
791 "are not enabled for node '%s' in original "
793 nb->node_path, rb->rev_orig);
795 nb->has_props = TRUE;
796 write_propdel_to_stringbuf(&(nb->props), name);
802 /* The parser calls this method if the node record has a non-delta
803 * property content section, before any calls to set_node_property().
804 * If the node record uses property deltas, this is not called.
807 remove_node_props(void *node_baton)
809 struct node_baton_t *nb = node_baton;
811 /* In this case, not actually indicating that the node *has* props,
812 rather that it has a property content section. */
813 nb->has_props = TRUE;
820 set_fulltext(svn_stream_t **stream, void *node_baton)
822 struct node_baton_t *nb = node_baton;
827 if (! nb->writing_begun)
829 nb->writing_begun = TRUE;
832 svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
834 SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
836 nb->has_props ? nb->props : NULL,
839 TRUE /*content_length_always*/,
842 *stream = nb->rb->pb->out_stream;
851 close_node(void *node_baton)
853 struct node_baton_t *nb = node_baton;
856 /* Get out of here if we can. */
860 /* If the node was not flushed already to output its text, do it now. */
861 if (! nb->writing_begun)
863 nb->writing_begun = TRUE;
866 svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
868 SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
870 nb->has_props ? nb->props : NULL,
873 TRUE /*content_length_always*/,
877 /* put an end to node. */
878 SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
884 /* Finalize revision */
886 close_revision(void *revision_baton)
888 struct revision_baton_t *rb = revision_baton;
890 /* If no node has yet flushed the revision, do it now. */
891 if (! rb->writing_begun)
892 return output_revision(rb);
898 /* Filtering vtable */
899 static svn_repos_parse_fns3_t filtering_vtable =
905 set_revision_property,
907 delete_node_property,
919 static svn_opt_subcommand_t
926 svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
927 svndumpfilter__drop_all_empty_revs,
928 svndumpfilter__renumber_revs,
929 svndumpfilter__preserve_revprops,
930 svndumpfilter__skip_missing_merge_sources,
931 svndumpfilter__targets,
932 svndumpfilter__quiet,
934 svndumpfilter__version
937 /* Option codes and descriptions.
939 * The entire list must be terminated with an entry of nulls.
941 static const apr_getopt_option_t options_table[] =
944 N_("show help on a subcommand")},
947 N_("show help on a subcommand")},
949 {"version", svndumpfilter__version, 0,
950 N_("show program version information") },
951 {"quiet", svndumpfilter__quiet, 0,
952 N_("Do not display filtering statistics.") },
953 {"pattern", svndumpfilter__glob, 0,
954 N_("Treat the path prefixes as file glob patterns.\n"
955 " Glob special characters are '*' '?' '[]' and '\\'.\n"
956 " Character '/' is not treated specially, so\n"
957 " pattern /*/foo matches paths /a/foo and /a/b/foo.") },
958 {"drop-empty-revs", svndumpfilter__drop_empty_revs, 0,
959 N_("Remove revisions emptied by filtering.")},
960 {"drop-all-empty-revs", svndumpfilter__drop_all_empty_revs, 0,
961 N_("Remove all empty revisions found in dumpstream\n"
962 " except revision 0.")},
963 {"renumber-revs", svndumpfilter__renumber_revs, 0,
964 N_("Renumber revisions left after filtering.") },
965 {"skip-missing-merge-sources",
966 svndumpfilter__skip_missing_merge_sources, 0,
967 N_("Skip missing merge sources.") },
968 {"preserve-revprops", svndumpfilter__preserve_revprops, 0,
969 N_("Don't filter revision properties.") },
970 {"targets", svndumpfilter__targets, 1,
971 N_("Read additional prefixes, one per line, from\n"
977 /* Array of available subcommands.
978 * The entire list must be terminated with an entry of nulls.
980 static const svn_opt_subcommand_desc2_t cmd_table[] =
982 {"exclude", subcommand_exclude, {0},
983 N_("Filter out nodes with given prefixes from dumpstream.\n"
984 "usage: svndumpfilter exclude PATH_PREFIX...\n"),
985 {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
986 svndumpfilter__renumber_revs,
987 svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
988 svndumpfilter__preserve_revprops, svndumpfilter__quiet,
989 svndumpfilter__glob} },
991 {"include", subcommand_include, {0},
992 N_("Filter out nodes without given prefixes from dumpstream.\n"
993 "usage: svndumpfilter include PATH_PREFIX...\n"),
994 {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
995 svndumpfilter__renumber_revs,
996 svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
997 svndumpfilter__preserve_revprops, svndumpfilter__quiet,
998 svndumpfilter__glob} },
1000 {"help", subcommand_help, {"?", "h"},
1001 N_("Describe the usage of this program or its subcommands.\n"
1002 "usage: svndumpfilter help [SUBCOMMAND...]\n"),
1005 { NULL, NULL, {0}, NULL, {0} }
1009 /* Baton for passing option/argument state to a subcommand function. */
1010 struct svndumpfilter_opt_state
1012 svn_opt_revision_t start_revision; /* -r X[:Y] is */
1013 svn_opt_revision_t end_revision; /* not implemented. */
1014 svn_boolean_t quiet; /* --quiet */
1015 svn_boolean_t glob; /* --pattern */
1016 svn_boolean_t version; /* --version */
1017 svn_boolean_t drop_empty_revs; /* --drop-empty-revs */
1018 svn_boolean_t drop_all_empty_revs; /* --drop-all-empty-revs */
1019 svn_boolean_t help; /* --help or -? */
1020 svn_boolean_t renumber_revs; /* --renumber-revs */
1021 svn_boolean_t preserve_revprops; /* --preserve-revprops */
1022 svn_boolean_t skip_missing_merge_sources;
1023 /* --skip-missing-merge-sources */
1024 const char *targets_file; /* --targets-file */
1025 apr_array_header_t *prefixes; /* mainargs. */
1029 static svn_error_t *
1030 parse_baton_initialize(struct parse_baton_t **pb,
1031 struct svndumpfilter_opt_state *opt_state,
1032 svn_boolean_t do_exclude,
1035 struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1037 /* Read the stream from STDIN. Users can redirect a file. */
1038 SVN_ERR(svn_stream_for_stdin2(&baton->in_stream, TRUE, pool));
1040 /* Have the parser dump results to STDOUT. Users can redirect a file. */
1041 SVN_ERR(svn_stream_for_stdout(&baton->out_stream, pool));
1043 baton->do_exclude = do_exclude;
1045 /* Ignore --renumber-revs if there can't possibly be
1046 anything to renumber. */
1047 baton->do_renumber_revs =
1048 (opt_state->renumber_revs && (opt_state->drop_empty_revs
1049 || opt_state->drop_all_empty_revs));
1051 baton->drop_empty_revs = opt_state->drop_empty_revs;
1052 baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
1053 baton->preserve_revprops = opt_state->preserve_revprops;
1054 baton->quiet = opt_state->quiet;
1055 baton->glob = opt_state->glob;
1056 baton->prefixes = opt_state->prefixes;
1057 baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
1058 baton->rev_drop_count = 0; /* used to shift revnums while filtering */
1059 baton->dropped_nodes = apr_hash_make(pool);
1060 baton->renumber_history = apr_hash_make(pool);
1061 baton->last_live_revision = SVN_INVALID_REVNUM;
1062 baton->oldest_original_rev = SVN_INVALID_REVNUM;
1063 baton->allow_deltas = FALSE;
1066 return SVN_NO_ERROR;
1069 /* This implements `help` subcommand. */
1070 static svn_error_t *
1071 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1073 struct svndumpfilter_opt_state *opt_state = baton;
1074 const char *header =
1075 _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
1076 "Subversion repository dump filtering tool.\n"
1077 "Type 'svndumpfilter help <subcommand>' for help on a "
1078 "specific subcommand.\n"
1079 "Type 'svndumpfilter --version' to see the program version.\n"
1081 "Available subcommands:\n");
1083 SVN_ERR(svn_opt_print_help4(os, "svndumpfilter",
1084 opt_state ? opt_state->version : FALSE,
1085 opt_state ? opt_state->quiet : FALSE,
1086 /*###opt_state ? opt_state->verbose :*/ FALSE,
1087 NULL, header, cmd_table, options_table,
1090 return SVN_NO_ERROR;
1094 /* Version compatibility check */
1095 static svn_error_t *
1096 check_lib_versions(void)
1098 static const svn_version_checklist_t checklist[] =
1100 { "svn_subr", svn_subr_version },
1101 { "svn_repos", svn_repos_version },
1102 { "svn_delta", svn_delta_version },
1105 SVN_VERSION_DEFINE(my_version);
1107 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1111 /* Do the real work of filtering. */
1112 static svn_error_t *
1113 do_filter(apr_getopt_t *os,
1115 svn_boolean_t do_exclude,
1118 struct svndumpfilter_opt_state *opt_state = baton;
1119 struct parse_baton_t *pb;
1120 apr_hash_index_t *hi;
1121 apr_array_header_t *keys;
1124 if (! opt_state->quiet)
1126 apr_pool_t *subpool = svn_pool_create(pool);
1128 if (opt_state->glob)
1130 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1132 ? (opt_state->drop_empty_revs
1133 || opt_state->drop_all_empty_revs)
1134 ? _("Excluding (and dropping empty "
1135 "revisions for) prefix patterns:\n")
1136 : _("Excluding prefix patterns:\n")
1137 : (opt_state->drop_empty_revs
1138 || opt_state->drop_all_empty_revs)
1139 ? _("Including (and dropping empty "
1140 "revisions for) prefix patterns:\n")
1141 : _("Including prefix patterns:\n")));
1145 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1147 ? (opt_state->drop_empty_revs
1148 || opt_state->drop_all_empty_revs)
1149 ? _("Excluding (and dropping empty "
1150 "revisions for) prefixes:\n")
1151 : _("Excluding prefixes:\n")
1152 : (opt_state->drop_empty_revs
1153 || opt_state->drop_all_empty_revs)
1154 ? _("Including (and dropping empty "
1155 "revisions for) prefixes:\n")
1156 : _("Including prefixes:\n")));
1159 for (i = 0; i < opt_state->prefixes->nelts; i++)
1161 svn_pool_clear(subpool);
1162 SVN_ERR(svn_cmdline_fprintf
1163 (stderr, subpool, " '%s'\n",
1164 APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
1167 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1168 svn_pool_destroy(subpool);
1171 SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
1172 SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
1173 TRUE, NULL, NULL, pool));
1175 /* The rest of this is just reporting. If we aren't reporting, get
1177 if (opt_state->quiet)
1178 return SVN_NO_ERROR;
1180 SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1182 if (pb->rev_drop_count)
1183 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1184 Q_("Dropped %d revision.\n\n",
1185 "Dropped %d revisions.\n\n",
1186 pb->rev_drop_count),
1187 pb->rev_drop_count));
1189 if (pb->do_renumber_revs)
1191 apr_pool_t *subpool = svn_pool_create(pool);
1192 SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1195 /* Get the keys of the hash, sort them, then print the hash keys
1196 and values, sorted by keys. */
1197 num_keys = apr_hash_count(pb->renumber_history);
1198 keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
1199 for (hi = apr_hash_first(pool, pb->renumber_history);
1201 hi = apr_hash_next(hi))
1203 const svn_revnum_t *revnum = apr_hash_this_key(hi);
1205 APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1207 svn_sort__array(keys, svn_sort_compare_revisions);
1208 for (i = 0; i < keys->nelts; i++)
1210 svn_revnum_t this_key;
1211 struct revmap_t *this_val;
1213 svn_pool_clear(subpool);
1214 this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
1215 this_val = apr_hash_get(pb->renumber_history, &this_key,
1217 if (this_val->was_dropped)
1218 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1219 _(" %ld => (dropped)\n"),
1222 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1224 this_key, this_val->rev));
1226 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1227 svn_pool_destroy(subpool);
1230 if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1232 apr_pool_t *subpool = svn_pool_create(pool);
1233 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1234 Q_("Dropped %d node:\n",
1235 "Dropped %d nodes:\n",
1239 /* Get the keys of the hash, sort them, then print the hash keys
1240 and values, sorted by keys. */
1241 keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
1242 for (hi = apr_hash_first(pool, pb->dropped_nodes);
1244 hi = apr_hash_next(hi))
1246 const char *path = apr_hash_this_key(hi);
1248 APR_ARRAY_PUSH(keys, const char *) = path;
1250 svn_sort__array(keys, svn_sort_compare_paths);
1251 for (i = 0; i < keys->nelts; i++)
1253 svn_pool_clear(subpool);
1254 SVN_ERR(svn_cmdline_fprintf
1255 (stderr, subpool, " '%s'\n",
1256 (const char *)APR_ARRAY_IDX(keys, i, const char *)));
1258 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1259 svn_pool_destroy(subpool);
1262 return SVN_NO_ERROR;
1265 /* This implements `exclude' subcommand. */
1266 static svn_error_t *
1267 subcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1269 return do_filter(os, baton, TRUE, pool);
1273 /* This implements `include` subcommand. */
1274 static svn_error_t *
1275 subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1277 return do_filter(os, baton, FALSE, pool);
1285 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
1286 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
1287 * return SVN_NO_ERROR.
1289 static svn_error_t *
1290 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1293 apr_status_t apr_err;
1295 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1296 struct svndumpfilter_opt_state opt_state;
1299 apr_array_header_t *received_opts;
1302 /* Check library versions */
1303 SVN_ERR(check_lib_versions());
1305 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1307 /* Initialize the FS library. */
1308 SVN_ERR(svn_fs_initialize(pool));
1312 SVN_ERR(subcommand_help(NULL, NULL, pool));
1313 *exit_code = EXIT_FAILURE;
1314 return SVN_NO_ERROR;
1317 /* Initialize opt_state. */
1318 memset(&opt_state, 0, sizeof(opt_state));
1319 opt_state.start_revision.kind = svn_opt_revision_unspecified;
1320 opt_state.end_revision.kind = svn_opt_revision_unspecified;
1322 /* Parse options. */
1323 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
1328 const char *opt_arg;
1330 /* Parse the next option. */
1331 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1332 if (APR_STATUS_IS_EOF(apr_err))
1336 SVN_ERR(subcommand_help(NULL, NULL, pool));
1337 *exit_code = EXIT_FAILURE;
1338 return SVN_NO_ERROR;
1341 /* Stash the option code in an array before parsing it. */
1342 APR_ARRAY_PUSH(received_opts, int) = opt_id;
1348 opt_state.help = TRUE;
1350 case svndumpfilter__version:
1351 opt_state.version = TRUE;
1353 case svndumpfilter__quiet:
1354 opt_state.quiet = TRUE;
1356 case svndumpfilter__glob:
1357 opt_state.glob = TRUE;
1359 case svndumpfilter__drop_empty_revs:
1360 opt_state.drop_empty_revs = TRUE;
1362 case svndumpfilter__drop_all_empty_revs:
1363 opt_state.drop_all_empty_revs = TRUE;
1365 case svndumpfilter__renumber_revs:
1366 opt_state.renumber_revs = TRUE;
1368 case svndumpfilter__preserve_revprops:
1369 opt_state.preserve_revprops = TRUE;
1371 case svndumpfilter__skip_missing_merge_sources:
1372 opt_state.skip_missing_merge_sources = TRUE;
1374 case svndumpfilter__targets:
1375 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.targets_file,
1380 SVN_ERR(subcommand_help(NULL, NULL, pool));
1381 *exit_code = EXIT_FAILURE;
1382 return SVN_NO_ERROR;
1384 } /* close `switch' */
1385 } /* close `while' */
1387 /* Disallow simultaneous use of both --drop-empty-revs and
1388 --drop-all-empty-revs. */
1389 if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
1391 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS,
1393 _("--drop-empty-revs cannot be used with "
1394 "--drop-all-empty-revs"));
1397 /* If the user asked for help, then the rest of the arguments are
1398 the names of subcommands to get help on (if any), or else they're
1399 just typos/mistakes. Whatever the case, the subcommand to
1400 actually run is subcommand_help(). */
1402 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
1404 /* If we're not running the `help' subcommand, then look for a
1405 subcommand in the first argument. */
1406 if (subcommand == NULL)
1408 if (os->ind >= os->argc)
1410 if (opt_state.version)
1412 /* Use the "help" subcommand to handle the "--version" option. */
1413 static const svn_opt_subcommand_desc2_t pseudo_cmd =
1414 { "--version", subcommand_help, {0}, "",
1415 {svndumpfilter__version, /* must accept its own option */
1416 svndumpfilter__quiet,
1419 subcommand = &pseudo_cmd;
1423 svn_error_clear(svn_cmdline_fprintf
1425 _("Subcommand argument required\n")));
1426 SVN_ERR(subcommand_help(NULL, NULL, pool));
1427 *exit_code = EXIT_FAILURE;
1428 return SVN_NO_ERROR;
1433 const char *first_arg;
1435 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
1437 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
1438 if (subcommand == NULL)
1441 svn_cmdline_fprintf(stderr, pool,
1442 _("Unknown subcommand: '%s'\n"),
1444 SVN_ERR(subcommand_help(NULL, NULL, pool));
1445 *exit_code = EXIT_FAILURE;
1446 return SVN_NO_ERROR;
1451 /* If there's a second argument, it's probably [one of] prefixes.
1452 Every subcommand except `help' requires at least one, so we parse
1453 them out here and store in opt_state. */
1455 if (subcommand->cmd_func != subcommand_help)
1458 opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
1459 sizeof(const char *));
1460 for (i = os->ind ; i< os->argc; i++)
1464 /* Ensure that each prefix is UTF8-encoded, in internal
1465 style, and absolute. */
1466 SVN_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
1467 prefix = svn_relpath__internal_style(prefix, pool);
1468 if (prefix[0] != '/')
1469 prefix = apr_pstrcat(pool, "/", prefix, SVN_VA_NULL);
1470 APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1473 if (opt_state.targets_file)
1475 svn_stringbuf_t *buffer, *buffer_utf8;
1476 apr_array_header_t *targets = apr_array_make(pool, 0,
1477 sizeof(const char *));
1479 /* We need to convert to UTF-8 now, even before we divide
1480 the targets into an array, because otherwise we wouldn't
1481 know what delimiter to use for svn_cstring_split(). */
1482 SVN_ERR(svn_stringbuf_from_file2(&buffer, opt_state.targets_file,
1484 SVN_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1486 targets = apr_array_append(pool,
1487 svn_cstring_split(buffer_utf8->data, "\n\r",
1491 for (i = 0; i < targets->nelts; i++)
1493 const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
1494 if (prefix[0] != '/')
1495 prefix = apr_pstrcat(pool, "/", prefix, SVN_VA_NULL);
1496 APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1500 if (apr_is_empty_array(opt_state.prefixes))
1502 svn_error_clear(svn_cmdline_fprintf
1504 _("\nError: no prefixes supplied.\n")));
1505 *exit_code = EXIT_FAILURE;
1506 return SVN_NO_ERROR;
1511 /* Check that the subcommand wasn't passed any inappropriate options. */
1512 for (i = 0; i < received_opts->nelts; i++)
1514 opt_id = APR_ARRAY_IDX(received_opts, i, int);
1516 /* All commands implicitly accept --help, so just skip over this
1517 when we see it. Note that we don't want to include this option
1518 in their "accepted options" list because it would be awfully
1519 redundant to display it in every commands' help text. */
1520 if (opt_id == 'h' || opt_id == '?')
1523 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1526 const apr_getopt_option_t *badopt =
1527 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
1529 svn_opt_format_option(&optstr, badopt, FALSE, pool);
1530 if (subcommand->name[0] == '-')
1531 SVN_ERR(subcommand_help(NULL, NULL, pool));
1533 svn_error_clear(svn_cmdline_fprintf
1535 _("Subcommand '%s' doesn't accept option '%s'\n"
1536 "Type 'svndumpfilter help %s' for usage.\n"),
1537 subcommand->name, optstr, subcommand->name));
1538 *exit_code = EXIT_FAILURE;
1539 return SVN_NO_ERROR;
1543 /* Run the subcommand. */
1544 err = (*subcommand->cmd_func)(os, &opt_state, pool);
1547 /* For argument-related problems, suggest using the 'help'
1549 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1550 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1552 err = svn_error_quick_wrap(err,
1553 _("Try 'svndumpfilter help' for more "
1559 return SVN_NO_ERROR;
1563 main(int argc, const char *argv[])
1566 int exit_code = EXIT_SUCCESS;
1569 /* Initialize the app. */
1570 if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1571 return EXIT_FAILURE;
1573 /* Create our top-level pool. Use a separate mutexless allocator,
1574 * given this application is single threaded.
1576 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1578 err = sub_main(&exit_code, argc, argv, pool);
1580 /* Flush stdout and report if it fails. It would be flushed on exit anyway
1581 but this makes sure that output is not silently lost if it fails. */
1582 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1586 exit_code = EXIT_FAILURE;
1587 svn_cmdline_handle_exit_error(err, NULL, "svndumpfilter: ");
1590 svn_pool_destroy(pool);