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"
52 typedef apr_status_t (__stdcall *open_fn_t)(apr_file_t **, apr_pool_t *);
54 typedef apr_status_t (*open_fn_t)(apr_file_t **, apr_pool_t *);
59 /* Helper to open stdio streams */
61 /* NOTE: we used to call svn_stream_from_stdio(), which wraps a stream
62 around a standard stdio.h FILE pointer. The problem is that these
63 pointers operate through C Run Time (CRT) on Win32, which does all
64 sorts of translation on them: LF's become CRLF's, and ctrl-Z's
65 embedded in Word documents are interpreted as premature EOF's.
67 So instead, we use apr_file_open_std*, which bypass the CRT and
68 directly wrap the OS's file-handles, which don't know or care about
69 translation. Thus dump/load works correctly on Win32.
72 create_stdio_stream(svn_stream_t **stream,
76 apr_file_t *stdio_file;
77 apr_status_t apr_err = open_fn(&stdio_file, pool);
80 return svn_error_wrap_apr(apr_err, _("Can't open stdio file"));
82 *stream = svn_stream_from_aprfile2(stdio_file, TRUE, pool);
87 /* Writes a property in dumpfile format to given stringbuf. */
89 write_prop_to_stringbuf(svn_stringbuf_t *strbuf,
91 const svn_string_t *value)
95 char buf[SVN_KEYLINE_MAXLEN];
97 /* Output name length, then name. */
98 namelen = strlen(name);
99 svn_stringbuf_appendbytes(strbuf, "K ", 2);
101 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
102 svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
103 svn_stringbuf_appendbyte(strbuf, '\n');
105 svn_stringbuf_appendbytes(strbuf, name, namelen);
106 svn_stringbuf_appendbyte(strbuf, '\n');
108 /* Output value length, then value. */
109 svn_stringbuf_appendbytes(strbuf, "V ", 2);
111 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len);
112 svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
113 svn_stringbuf_appendbyte(strbuf, '\n');
115 svn_stringbuf_appendbytes(strbuf, value->data, value->len);
116 svn_stringbuf_appendbyte(strbuf, '\n');
120 /* Writes a property deletion in dumpfile format to given stringbuf. */
122 write_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
127 char buf[SVN_KEYLINE_MAXLEN];
129 /* Output name length, then name. */
130 namelen = strlen(name);
131 svn_stringbuf_appendbytes(*strbuf, "D ", 2);
133 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
134 svn_stringbuf_appendbytes(*strbuf, buf, bytes_used);
135 svn_stringbuf_appendbyte(*strbuf, '\n');
137 svn_stringbuf_appendbytes(*strbuf, name, namelen);
138 svn_stringbuf_appendbyte(*strbuf, '\n');
142 /* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
143 * Return TRUE if any prefix is a prefix of PATH (matching whole path
144 * components); FALSE otherwise.
145 * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
147 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
150 size_t path_len = strlen(path);
152 for (i = 0; i < pfxlist->nelts; i++)
154 const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
155 size_t pfx_len = strlen(pfx);
157 if (path_len < pfx_len)
159 if (strncmp(path, pfx, pfx_len) == 0
160 && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
168 /* Check whether we need to skip this PATH based on its presence in
169 the PREFIXES list, and the DO_EXCLUDE option.
170 PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
171 static APR_INLINE svn_boolean_t
172 skip_path(const char *path, const apr_array_header_t *prefixes,
173 svn_boolean_t do_exclude, svn_boolean_t glob)
175 const svn_boolean_t matches =
177 ? svn_cstring_match_glob_list(path, prefixes)
178 : ary_prefix_match(prefixes, path));
181 return (matches ? do_exclude : !do_exclude);
186 /* Note: the input stream parser calls us with events.
187 Output of the filtered dump occurs for the most part streamily with the
188 event callbacks, to avoid caching large quantities of data in memory.
189 The exceptions this are:
190 - All revision data (headers and props) must be cached until a non-skipped
191 node within the revision is found, or the revision is closed.
192 - Node headers and props must be cached until all props have been received
193 (to allow the Prop-content-length to be found). This is signalled either
194 by the node text arriving, or the node being closed.
195 The writing_begun members of the associated object batons track the state.
196 output_revision() and output_node() are called to cause this flushing of
197 cached data to occur.
201 /* Filtering batons */
205 svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
206 svn_boolean_t was_dropped; /* Was this revision dropped? */
211 /* Command-line options values. */
212 svn_boolean_t do_exclude;
215 svn_boolean_t drop_empty_revs;
216 svn_boolean_t drop_all_empty_revs;
217 svn_boolean_t do_renumber_revs;
218 svn_boolean_t preserve_revprops;
219 svn_boolean_t skip_missing_merge_sources;
220 svn_boolean_t allow_deltas;
221 apr_array_header_t *prefixes;
223 /* Input and output streams. */
224 svn_stream_t *in_stream;
225 svn_stream_t *out_stream;
227 /* State for the filtering process. */
228 apr_int32_t rev_drop_count;
229 apr_hash_t *dropped_nodes;
230 apr_hash_t *renumber_history; /* svn_revnum_t -> struct revmap_t */
231 svn_revnum_t last_live_revision;
232 /* The oldest original revision, greater than r0, in the input
233 stream which was not filtered. */
234 svn_revnum_t oldest_original_rev;
237 struct revision_baton_t
239 /* Reference to the global parse baton. */
240 struct parse_baton_t *pb;
242 /* Does this revision have node or prop changes? */
243 svn_boolean_t has_nodes;
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 apr_hash_t *original_headers;
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_repos__dumpfile_headers_t *headers;
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. */
291 apr_pool_t *node_pool;
296 /* Filtering vtable members */
298 /* File-format stamp. */
300 magic_header_record(int version, void *parse_baton, apr_pool_t *pool)
302 struct parse_baton_t *pb = parse_baton;
304 if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
305 pb->allow_deltas = TRUE;
307 SVN_ERR(svn_stream_printf(pb->out_stream, pool,
308 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
315 /* Return a deep copy of a (char * -> char *) hash. */
317 headers_dup(apr_hash_t *headers,
320 apr_hash_t *new_hash = apr_hash_make(pool);
321 apr_hash_index_t *hi;
323 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
325 const char *key = apr_hash_this_key(hi);
326 const char *val = apr_hash_this_val(hi);
328 svn_hash_sets(new_hash, apr_pstrdup(pool, key), apr_pstrdup(pool, val));
333 /* New revision: set up revision_baton, decide if we skip it. */
335 new_revision_record(void **revision_baton,
340 struct revision_baton_t *rb;
341 const char *rev_orig;
343 *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t));
344 rb = *revision_baton;
345 rb->pb = parse_baton;
346 rb->has_nodes = FALSE;
347 rb->had_dropped_nodes = FALSE;
348 rb->writing_begun = FALSE;
349 rb->props = apr_hash_make(pool);
350 rb->original_headers = headers_dup(headers, pool);
352 rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
353 rb->rev_orig = SVN_STR_TO_REV(rev_orig);
355 if (rb->pb->do_renumber_revs)
356 rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
358 rb->rev_actual = rb->rev_orig;
364 /* Output revision to dumpstream
365 This may be called by new_node_record(), iff rb->has_nodes has been set
366 to TRUE, or by close_revision() otherwise. This must only be called
367 if rb->writing_begun is FALSE. */
369 output_revision(struct revision_baton_t *rb)
371 svn_boolean_t write_out_rev = FALSE;
372 apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
373 apr_pool_t *subpool = svn_pool_create(hash_pool);
375 rb->writing_begun = TRUE;
377 /* If this revision has no nodes left because the ones it had were
378 dropped, and we are not dropping empty revisions, and we were not
379 told to preserve revision props, then we want to fixup the
380 revision props to only contain:
382 - a log message that reports that this revision is just stuffing. */
383 if ((! rb->pb->preserve_revprops)
385 && rb->had_dropped_nodes
386 && (! rb->pb->drop_empty_revs)
387 && (! rb->pb->drop_all_empty_revs))
389 apr_hash_t *old_props = rb->props;
390 rb->props = apr_hash_make(hash_pool);
391 svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE,
392 svn_hash_gets(old_props, SVN_PROP_REVISION_DATE));
393 svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG,
394 svn_string_create(_("This is an empty revision for "
395 "padding."), hash_pool));
398 /* write out the revision */
399 /* Revision is written out in the following cases:
400 1. If the revision has nodes or
401 it is revision 0 (Special case: To preserve the props on r0).
402 2. --drop-empty-revs has been supplied,
403 but revision has not all nodes dropped.
404 3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied,
405 write out the revision which has no nodes to begin with.
407 if (rb->has_nodes || (rb->rev_orig == 0))
408 write_out_rev = TRUE;
409 else if (rb->pb->drop_empty_revs)
410 write_out_rev = ! rb->had_dropped_nodes;
411 else if (! rb->pb->drop_all_empty_revs)
412 write_out_rev = TRUE;
416 /* This revision is a keeper. */
417 SVN_ERR(svn_repos__dump_revision_record(rb->pb->out_stream,
419 rb->original_headers,
421 FALSE /*props_section_always*/,
424 /* Stash the oldest original rev not dropped. */
426 && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
427 rb->pb->oldest_original_rev = rb->rev_orig;
429 if (rb->pb->do_renumber_revs)
431 svn_revnum_t *rr_key;
432 struct revmap_t *rr_val;
433 apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
434 rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
435 rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
436 *rr_key = rb->rev_orig;
437 rr_val->rev = rb->rev_actual;
438 rr_val->was_dropped = FALSE;
439 apr_hash_set(rb->pb->renumber_history, rr_key,
440 sizeof(*rr_key), rr_val);
441 rb->pb->last_live_revision = rb->rev_actual;
445 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
446 _("Revision %ld committed as %ld.\n"),
447 rb->rev_orig, rb->rev_actual));
451 /* We're dropping this revision. */
452 rb->pb->rev_drop_count++;
453 if (rb->pb->do_renumber_revs)
455 svn_revnum_t *rr_key;
456 struct revmap_t *rr_val;
457 apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
458 rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
459 rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
460 *rr_key = rb->rev_orig;
461 rr_val->rev = rb->pb->last_live_revision;
462 rr_val->was_dropped = TRUE;
463 apr_hash_set(rb->pb->renumber_history, rr_key,
464 sizeof(*rr_key), rr_val);
468 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
469 _("Revision %ld skipped.\n"),
472 svn_pool_destroy(subpool);
477 /* UUID record here: dump it, as we do not filter them. */
479 uuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
481 struct parse_baton_t *pb = parse_baton;
482 SVN_ERR(svn_stream_printf(pb->out_stream, pool,
483 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
488 /* New node here. Set up node_baton by copying headers. */
490 new_node_record(void **node_baton,
495 struct parse_baton_t *pb;
496 struct node_baton_t *nb;
497 char *node_path, *copyfrom_path;
498 apr_hash_index_t *hi;
501 *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
504 nb->node_pool = pool;
507 node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH);
508 copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH);
510 /* Ensure that paths start with a leading '/'. */
511 if (node_path[0] != '/')
512 node_path = apr_pstrcat(pool, "/", node_path, SVN_VA_NULL);
513 if (copyfrom_path && copyfrom_path[0] != '/')
514 copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, SVN_VA_NULL);
516 nb->do_skip = skip_path(node_path, pb->prefixes,
517 pb->do_exclude, pb->glob);
519 /* If we're skipping the node, take note of path, discarding the
523 svn_hash_sets(pb->dropped_nodes,
524 apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
527 nb->rb->had_dropped_nodes = TRUE;
534 tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
536 /* Test if this node was copied from dropped source. */
538 skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
540 /* This node was copied from a dropped source.
541 We have a problem, since we did not want to drop this node too.
543 However, there is one special case we'll handle. If the node is
544 a file, and this was a copy-and-modify operation, then the
545 dumpfile should contain the new contents of the file. In this
546 scenario, we'll just do an add without history using the new
548 kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
550 /* If there is a Text-content-length header, and the kind is
551 "file", we just fallback to an add without history. */
552 if (tcl && (strcmp(kind, "file") == 0))
554 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
556 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
558 copyfrom_path = NULL;
560 /* Else, this is either a directory or a file whose contents we
561 don't have readily available. */
564 return svn_error_createf
565 (SVN_ERR_INCOMPLETE_DATA, 0,
566 _("Invalid copy source path '%s'"), copyfrom_path);
570 nb->has_props = FALSE;
571 nb->has_text = FALSE;
572 nb->has_prop_delta = FALSE;
573 nb->has_text_delta = FALSE;
574 nb->writing_begun = FALSE;
575 nb->tcl = tcl ? svn__atoui64(tcl) : 0;
576 nb->headers = svn_repos__dumpfile_headers_create(pool);
577 nb->props = svn_stringbuf_create_empty(pool);
578 nb->node_path = apr_pstrdup(pool, node_path);
580 /* Now we know for sure that we have a node that will not be
581 skipped, flush the revision if it has not already been done. */
582 nb->rb->has_nodes = TRUE;
583 if (! nb->rb->writing_begun)
584 SVN_ERR(output_revision(nb->rb));
586 /* A node record is required to begin with 'Node-path', skip the
587 leading '/' to match the form used by 'svnadmin dump'. */
588 svn_repos__dumpfile_header_push(
589 nb->headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_path + 1);
591 /* Node-kind is next and is optional. */
592 kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
594 svn_repos__dumpfile_header_push(
595 nb->headers, SVN_REPOS_DUMPFILE_NODE_KIND, kind);
597 /* Node-action is next and required. */
598 action = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION);
600 svn_repos__dumpfile_header_push(
601 nb->headers, SVN_REPOS_DUMPFILE_NODE_ACTION, action);
603 return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
604 _("Missing Node-action for path '%s'"),
607 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
609 const char *key = apr_hash_this_key(hi);
610 const char *val = apr_hash_this_val(hi);
612 if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
613 && (!strcmp(val, "true")))
614 nb->has_prop_delta = TRUE;
616 if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
617 && (!strcmp(val, "true")))
618 nb->has_text_delta = TRUE;
620 if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
621 || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
622 || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH))
623 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_PATH))
624 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_KIND))
625 || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_ACTION)))
628 /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
629 The number points to some revision in the past. We keep track
630 of revision renumbering in an apr_hash, which maps original
631 revisions to new ones. Dropped revision are mapped to -1.
632 This should never happen here.
634 if (pb->do_renumber_revs
635 && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
637 svn_revnum_t cf_orig_rev;
638 struct revmap_t *cf_renum_val;
640 cf_orig_rev = SVN_STR_TO_REV(val);
641 cf_renum_val = apr_hash_get(pb->renumber_history,
643 sizeof(svn_revnum_t));
644 if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
645 return svn_error_createf
646 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
647 _("No valid copyfrom revision in filtered stream"));
648 svn_repos__dumpfile_header_pushf(
649 nb->headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
650 "%ld", cf_renum_val->rev);
654 /* passthru: put header straight to output */
655 svn_repos__dumpfile_header_push(nb->headers, key, val);
663 /* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
664 sources or renumbering revisions in rangelists as appropriate, and
665 return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
668 adjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
669 struct revision_baton_t *rb, apr_pool_t *pool)
671 apr_hash_t *mergeinfo;
672 apr_hash_t *final_mergeinfo = apr_hash_make(pool);
673 apr_hash_index_t *hi;
674 apr_pool_t *subpool = svn_pool_create(pool);
676 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
678 /* Issue #3020: If we are skipping missing merge sources, then also
679 filter mergeinfo ranges as old or older than the oldest revision in the
680 dump stream. Those older than the oldest obviously refer to history
681 outside of the dump stream. The oldest rev itself is present in the
682 dump, but cannot be a valid merge source revision since it is the
683 start of all history. E.g. if we dump -r100:400 then dumpfilter the
684 result with --skip-missing-merge-sources, any mergeinfo with revision
685 100 implies a change of -r99:100, but r99 is part of the history we
688 If the oldest rev is r0 then there is nothing to filter. */
690 /* ### This seems to cater only for use cases where the revisions being
691 processed are not following on from revisions that will already
692 exist in the destination repository. If the revisions being
693 processed do follow on, then we might want to keep the mergeinfo
694 that refers to those older revisions. */
696 if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
697 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
698 &mergeinfo, mergeinfo,
699 rb->pb->oldest_original_rev, 0,
700 FALSE, subpool, subpool));
702 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
704 const char *merge_source = apr_hash_this_key(hi);
705 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
706 struct parse_baton_t *pb = rb->pb;
708 /* Determine whether the merge_source is a part of the prefix. */
709 if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
711 if (pb->skip_missing_merge_sources)
714 return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
715 _("Missing merge source path '%s'; try "
716 "with --skip-missing-merge-sources"),
720 /* Possibly renumber revisions in merge source's rangelist. */
721 if (pb->do_renumber_revs)
725 for (i = 0; i < rangelist->nelts; i++)
727 struct revmap_t *revmap_start;
728 struct revmap_t *revmap_end;
729 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
730 svn_merge_range_t *);
732 revmap_start = apr_hash_get(pb->renumber_history,
733 &range->start, sizeof(svn_revnum_t));
734 if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
735 return svn_error_createf
736 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
737 _("No valid revision range 'start' in filtered stream"));
739 revmap_end = apr_hash_get(pb->renumber_history,
740 &range->end, sizeof(svn_revnum_t));
741 if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
742 return svn_error_createf
743 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
744 _("No valid revision range 'end' in filtered stream"));
746 range->start = revmap_start->rev;
747 range->end = revmap_end->rev;
750 svn_hash_sets(final_mergeinfo, merge_source, rangelist);
753 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
754 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
755 svn_pool_destroy(subpool);
762 set_revision_property(void *revision_baton,
764 const svn_string_t *value)
766 struct revision_baton_t *rb = revision_baton;
767 apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
769 svn_hash_sets(rb->props,
770 apr_pstrdup(hash_pool, name),
771 svn_string_dup(value, hash_pool));
777 set_node_property(void *node_baton,
779 const svn_string_t *value)
781 struct node_baton_t *nb = node_baton;
782 struct revision_baton_t *rb = nb->rb;
787 /* Try to detect if a delta-mode property occurs unexpectedly. HAS_PROPS
788 can be false here only if the parser didn't call remove_node_props(),
789 so this may indicate a bug rather than bad data. */
790 if (! (nb->has_props || nb->has_prop_delta))
791 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
792 _("Delta property block detected, but deltas "
793 "are not enabled for node '%s' in original "
795 nb->node_path, rb->rev_orig);
797 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
799 svn_string_t *filtered_mergeinfo; /* Avoid compiler warning. */
800 apr_pool_t *pool = apr_hash_pool_get(rb->props);
801 SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
802 value = filtered_mergeinfo;
805 nb->has_props = TRUE;
806 write_prop_to_stringbuf(nb->props, name, value);
813 delete_node_property(void *node_baton, const char *name)
815 struct node_baton_t *nb = node_baton;
816 struct revision_baton_t *rb = nb->rb;
821 if (!nb->has_prop_delta)
822 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
823 _("Delta property block detected, but deltas "
824 "are not enabled for node '%s' in original "
826 nb->node_path, rb->rev_orig);
828 nb->has_props = TRUE;
829 write_propdel_to_stringbuf(&(nb->props), name);
835 /* The parser calls this method if the node record has a non-delta
836 * property content section, before any calls to set_node_property().
837 * If the node record uses property deltas, this is not called.
840 remove_node_props(void *node_baton)
842 struct node_baton_t *nb = node_baton;
844 /* In this case, not actually indicating that the node *has* props,
845 rather that it has a property content section. */
846 nb->has_props = TRUE;
853 set_fulltext(svn_stream_t **stream, void *node_baton)
855 struct node_baton_t *nb = node_baton;
860 if (! nb->writing_begun)
862 nb->writing_begun = TRUE;
865 svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
867 SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
869 nb->has_props ? nb->props : NULL,
872 TRUE /*content_length_always*/,
875 *stream = nb->rb->pb->out_stream;
884 close_node(void *node_baton)
886 struct node_baton_t *nb = node_baton;
889 /* Get out of here if we can. */
893 /* If the node was not flushed already to output its text, do it now. */
894 if (! nb->writing_begun)
896 nb->writing_begun = TRUE;
899 svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
901 SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
903 nb->has_props ? nb->props : NULL,
906 TRUE /*content_length_always*/,
910 /* put an end to node. */
911 SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
917 /* Finalize revision */
919 close_revision(void *revision_baton)
921 struct revision_baton_t *rb = revision_baton;
923 /* If no node has yet flushed the revision, do it now. */
924 if (! rb->writing_begun)
925 return output_revision(rb);
931 /* Filtering vtable */
932 static svn_repos_parse_fns3_t filtering_vtable =
938 set_revision_property,
940 delete_node_property,
952 static svn_opt_subcommand_t
959 svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
960 svndumpfilter__drop_all_empty_revs,
961 svndumpfilter__renumber_revs,
962 svndumpfilter__preserve_revprops,
963 svndumpfilter__skip_missing_merge_sources,
964 svndumpfilter__targets,
965 svndumpfilter__quiet,
967 svndumpfilter__version
970 /* Option codes and descriptions.
972 * The entire list must be terminated with an entry of nulls.
974 static const apr_getopt_option_t options_table[] =
977 N_("show help on a subcommand")},
980 N_("show help on a subcommand")},
982 {"version", svndumpfilter__version, 0,
983 N_("show program version information") },
984 {"quiet", svndumpfilter__quiet, 0,
985 N_("Do not display filtering statistics.") },
986 {"pattern", svndumpfilter__glob, 0,
987 N_("Treat the path prefixes as file glob patterns.") },
988 {"drop-empty-revs", svndumpfilter__drop_empty_revs, 0,
989 N_("Remove revisions emptied by filtering.")},
990 {"drop-all-empty-revs", svndumpfilter__drop_all_empty_revs, 0,
991 N_("Remove all empty revisions found in dumpstream\n"
992 " except revision 0.")},
993 {"renumber-revs", svndumpfilter__renumber_revs, 0,
994 N_("Renumber revisions left after filtering.") },
995 {"skip-missing-merge-sources",
996 svndumpfilter__skip_missing_merge_sources, 0,
997 N_("Skip missing merge sources.") },
998 {"preserve-revprops", svndumpfilter__preserve_revprops, 0,
999 N_("Don't filter revision properties.") },
1000 {"targets", svndumpfilter__targets, 1,
1001 N_("Read additional prefixes, one per line, from\n"
1007 /* Array of available subcommands.
1008 * The entire list must be terminated with an entry of nulls.
1010 static const svn_opt_subcommand_desc2_t cmd_table[] =
1012 {"exclude", subcommand_exclude, {0},
1013 N_("Filter out nodes with given prefixes from dumpstream.\n"
1014 "usage: svndumpfilter exclude PATH_PREFIX...\n"),
1015 {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1016 svndumpfilter__renumber_revs,
1017 svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1018 svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1019 svndumpfilter__glob} },
1021 {"include", subcommand_include, {0},
1022 N_("Filter out nodes without given prefixes from dumpstream.\n"
1023 "usage: svndumpfilter include PATH_PREFIX...\n"),
1024 {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1025 svndumpfilter__renumber_revs,
1026 svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1027 svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1028 svndumpfilter__glob} },
1030 {"help", subcommand_help, {"?", "h"},
1031 N_("Describe the usage of this program or its subcommands.\n"
1032 "usage: svndumpfilter help [SUBCOMMAND...]\n"),
1035 { NULL, NULL, {0}, NULL, {0} }
1039 /* Baton for passing option/argument state to a subcommand function. */
1040 struct svndumpfilter_opt_state
1042 svn_opt_revision_t start_revision; /* -r X[:Y] is */
1043 svn_opt_revision_t end_revision; /* not implemented. */
1044 svn_boolean_t quiet; /* --quiet */
1045 svn_boolean_t glob; /* --pattern */
1046 svn_boolean_t version; /* --version */
1047 svn_boolean_t drop_empty_revs; /* --drop-empty-revs */
1048 svn_boolean_t drop_all_empty_revs; /* --drop-all-empty-revs */
1049 svn_boolean_t help; /* --help or -? */
1050 svn_boolean_t renumber_revs; /* --renumber-revs */
1051 svn_boolean_t preserve_revprops; /* --preserve-revprops */
1052 svn_boolean_t skip_missing_merge_sources;
1053 /* --skip-missing-merge-sources */
1054 const char *targets_file; /* --targets-file */
1055 apr_array_header_t *prefixes; /* mainargs. */
1059 static svn_error_t *
1060 parse_baton_initialize(struct parse_baton_t **pb,
1061 struct svndumpfilter_opt_state *opt_state,
1062 svn_boolean_t do_exclude,
1065 struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1067 /* Read the stream from STDIN. Users can redirect a file. */
1068 SVN_ERR(create_stdio_stream(&(baton->in_stream),
1069 apr_file_open_stdin, pool));
1071 /* Have the parser dump results to STDOUT. Users can redirect a file. */
1072 SVN_ERR(create_stdio_stream(&(baton->out_stream),
1073 apr_file_open_stdout, pool));
1075 baton->do_exclude = do_exclude;
1077 /* Ignore --renumber-revs if there can't possibly be
1078 anything to renumber. */
1079 baton->do_renumber_revs =
1080 (opt_state->renumber_revs && (opt_state->drop_empty_revs
1081 || opt_state->drop_all_empty_revs));
1083 baton->drop_empty_revs = opt_state->drop_empty_revs;
1084 baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
1085 baton->preserve_revprops = opt_state->preserve_revprops;
1086 baton->quiet = opt_state->quiet;
1087 baton->glob = opt_state->glob;
1088 baton->prefixes = opt_state->prefixes;
1089 baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
1090 baton->rev_drop_count = 0; /* used to shift revnums while filtering */
1091 baton->dropped_nodes = apr_hash_make(pool);
1092 baton->renumber_history = apr_hash_make(pool);
1093 baton->last_live_revision = SVN_INVALID_REVNUM;
1094 baton->oldest_original_rev = SVN_INVALID_REVNUM;
1095 baton->allow_deltas = FALSE;
1098 return SVN_NO_ERROR;
1101 /* This implements `help` subcommand. */
1102 static svn_error_t *
1103 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1105 struct svndumpfilter_opt_state *opt_state = baton;
1106 const char *header =
1107 _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
1108 "Subversion repository dump filtering tool.\n"
1109 "Type 'svndumpfilter help <subcommand>' for help on a "
1110 "specific subcommand.\n"
1111 "Type 'svndumpfilter --version' to see the program version.\n"
1113 "Available subcommands:\n");
1115 SVN_ERR(svn_opt_print_help4(os, "svndumpfilter",
1116 opt_state ? opt_state->version : FALSE,
1117 opt_state ? opt_state->quiet : FALSE,
1118 /*###opt_state ? opt_state->verbose :*/ FALSE,
1119 NULL, header, cmd_table, options_table,
1122 return SVN_NO_ERROR;
1126 /* Version compatibility check */
1127 static svn_error_t *
1128 check_lib_versions(void)
1130 static const svn_version_checklist_t checklist[] =
1132 { "svn_subr", svn_subr_version },
1133 { "svn_repos", svn_repos_version },
1134 { "svn_delta", svn_delta_version },
1137 SVN_VERSION_DEFINE(my_version);
1139 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1143 /* Do the real work of filtering. */
1144 static svn_error_t *
1145 do_filter(apr_getopt_t *os,
1147 svn_boolean_t do_exclude,
1150 struct svndumpfilter_opt_state *opt_state = baton;
1151 struct parse_baton_t *pb;
1152 apr_hash_index_t *hi;
1153 apr_array_header_t *keys;
1156 if (! opt_state->quiet)
1158 apr_pool_t *subpool = svn_pool_create(pool);
1160 if (opt_state->glob)
1162 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1164 ? (opt_state->drop_empty_revs
1165 || opt_state->drop_all_empty_revs)
1166 ? _("Excluding (and dropping empty "
1167 "revisions for) prefix patterns:\n")
1168 : _("Excluding prefix patterns:\n")
1169 : (opt_state->drop_empty_revs
1170 || opt_state->drop_all_empty_revs)
1171 ? _("Including (and dropping empty "
1172 "revisions for) prefix patterns:\n")
1173 : _("Including prefix patterns:\n")));
1177 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1179 ? (opt_state->drop_empty_revs
1180 || opt_state->drop_all_empty_revs)
1181 ? _("Excluding (and dropping empty "
1182 "revisions for) prefixes:\n")
1183 : _("Excluding prefixes:\n")
1184 : (opt_state->drop_empty_revs
1185 || opt_state->drop_all_empty_revs)
1186 ? _("Including (and dropping empty "
1187 "revisions for) prefixes:\n")
1188 : _("Including prefixes:\n")));
1191 for (i = 0; i < opt_state->prefixes->nelts; i++)
1193 svn_pool_clear(subpool);
1194 SVN_ERR(svn_cmdline_fprintf
1195 (stderr, subpool, " '%s'\n",
1196 APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
1199 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1200 svn_pool_destroy(subpool);
1203 SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
1204 SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
1205 TRUE, NULL, NULL, pool));
1207 /* The rest of this is just reporting. If we aren't reporting, get
1209 if (opt_state->quiet)
1210 return SVN_NO_ERROR;
1212 SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1214 if (pb->rev_drop_count)
1215 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1216 Q_("Dropped %d revision.\n\n",
1217 "Dropped %d revisions.\n\n",
1218 pb->rev_drop_count),
1219 pb->rev_drop_count));
1221 if (pb->do_renumber_revs)
1223 apr_pool_t *subpool = svn_pool_create(pool);
1224 SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1227 /* Get the keys of the hash, sort them, then print the hash keys
1228 and values, sorted by keys. */
1229 num_keys = apr_hash_count(pb->renumber_history);
1230 keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
1231 for (hi = apr_hash_first(pool, pb->renumber_history);
1233 hi = apr_hash_next(hi))
1235 const svn_revnum_t *revnum = apr_hash_this_key(hi);
1237 APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1239 svn_sort__array(keys, svn_sort_compare_revisions);
1240 for (i = 0; i < keys->nelts; i++)
1242 svn_revnum_t this_key;
1243 struct revmap_t *this_val;
1245 svn_pool_clear(subpool);
1246 this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
1247 this_val = apr_hash_get(pb->renumber_history, &this_key,
1249 if (this_val->was_dropped)
1250 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1251 _(" %ld => (dropped)\n"),
1254 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1256 this_key, this_val->rev));
1258 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1259 svn_pool_destroy(subpool);
1262 if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1264 apr_pool_t *subpool = svn_pool_create(pool);
1265 SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1266 Q_("Dropped %d node:\n",
1267 "Dropped %d nodes:\n",
1271 /* Get the keys of the hash, sort them, then print the hash keys
1272 and values, sorted by keys. */
1273 keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
1274 for (hi = apr_hash_first(pool, pb->dropped_nodes);
1276 hi = apr_hash_next(hi))
1278 const char *path = apr_hash_this_key(hi);
1280 APR_ARRAY_PUSH(keys, const char *) = path;
1282 svn_sort__array(keys, svn_sort_compare_paths);
1283 for (i = 0; i < keys->nelts; i++)
1285 svn_pool_clear(subpool);
1286 SVN_ERR(svn_cmdline_fprintf
1287 (stderr, subpool, " '%s'\n",
1288 (const char *)APR_ARRAY_IDX(keys, i, const char *)));
1290 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1291 svn_pool_destroy(subpool);
1294 return SVN_NO_ERROR;
1297 /* This implements `exclude' subcommand. */
1298 static svn_error_t *
1299 subcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1301 return do_filter(os, baton, TRUE, pool);
1305 /* This implements `include` subcommand. */
1306 static svn_error_t *
1307 subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1309 return do_filter(os, baton, FALSE, pool);
1317 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
1318 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
1319 * return SVN_NO_ERROR.
1321 static svn_error_t *
1322 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1325 apr_status_t apr_err;
1327 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1328 struct svndumpfilter_opt_state opt_state;
1331 apr_array_header_t *received_opts;
1334 /* Check library versions */
1335 SVN_ERR(check_lib_versions());
1337 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1339 /* Initialize the FS library. */
1340 SVN_ERR(svn_fs_initialize(pool));
1344 SVN_ERR(subcommand_help(NULL, NULL, pool));
1345 *exit_code = EXIT_FAILURE;
1346 return SVN_NO_ERROR;
1349 /* Initialize opt_state. */
1350 memset(&opt_state, 0, sizeof(opt_state));
1351 opt_state.start_revision.kind = svn_opt_revision_unspecified;
1352 opt_state.end_revision.kind = svn_opt_revision_unspecified;
1354 /* Parse options. */
1355 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
1360 const char *opt_arg;
1362 /* Parse the next option. */
1363 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1364 if (APR_STATUS_IS_EOF(apr_err))
1368 SVN_ERR(subcommand_help(NULL, NULL, pool));
1369 *exit_code = EXIT_FAILURE;
1370 return SVN_NO_ERROR;
1373 /* Stash the option code in an array before parsing it. */
1374 APR_ARRAY_PUSH(received_opts, int) = opt_id;
1380 opt_state.help = TRUE;
1382 case svndumpfilter__version:
1383 opt_state.version = TRUE;
1385 case svndumpfilter__quiet:
1386 opt_state.quiet = TRUE;
1388 case svndumpfilter__glob:
1389 opt_state.glob = TRUE;
1391 case svndumpfilter__drop_empty_revs:
1392 opt_state.drop_empty_revs = TRUE;
1394 case svndumpfilter__drop_all_empty_revs:
1395 opt_state.drop_all_empty_revs = TRUE;
1397 case svndumpfilter__renumber_revs:
1398 opt_state.renumber_revs = TRUE;
1400 case svndumpfilter__preserve_revprops:
1401 opt_state.preserve_revprops = TRUE;
1403 case svndumpfilter__skip_missing_merge_sources:
1404 opt_state.skip_missing_merge_sources = TRUE;
1406 case svndumpfilter__targets:
1407 opt_state.targets_file = opt_arg;
1411 SVN_ERR(subcommand_help(NULL, NULL, pool));
1412 *exit_code = EXIT_FAILURE;
1413 return SVN_NO_ERROR;
1415 } /* close `switch' */
1416 } /* close `while' */
1418 /* Disallow simultaneous use of both --drop-empty-revs and
1419 --drop-all-empty-revs. */
1420 if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
1422 return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS,
1424 _("--drop-empty-revs cannot be used with "
1425 "--drop-all-empty-revs"));
1428 /* If the user asked for help, then the rest of the arguments are
1429 the names of subcommands to get help on (if any), or else they're
1430 just typos/mistakes. Whatever the case, the subcommand to
1431 actually run is subcommand_help(). */
1433 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
1435 /* If we're not running the `help' subcommand, then look for a
1436 subcommand in the first argument. */
1437 if (subcommand == NULL)
1439 if (os->ind >= os->argc)
1441 if (opt_state.version)
1443 /* Use the "help" subcommand to handle the "--version" option. */
1444 static const svn_opt_subcommand_desc2_t pseudo_cmd =
1445 { "--version", subcommand_help, {0}, "",
1446 {svndumpfilter__version, /* must accept its own option */
1447 svndumpfilter__quiet,
1450 subcommand = &pseudo_cmd;
1454 svn_error_clear(svn_cmdline_fprintf
1456 _("Subcommand argument required\n")));
1457 SVN_ERR(subcommand_help(NULL, NULL, pool));
1458 *exit_code = EXIT_FAILURE;
1459 return SVN_NO_ERROR;
1464 const char *first_arg = os->argv[os->ind++];
1465 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
1466 if (subcommand == NULL)
1468 const char* first_arg_utf8;
1469 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
1473 svn_cmdline_fprintf(stderr, pool,
1474 _("Unknown subcommand: '%s'\n"),
1476 SVN_ERR(subcommand_help(NULL, NULL, pool));
1477 *exit_code = EXIT_FAILURE;
1478 return SVN_NO_ERROR;
1483 /* If there's a second argument, it's probably [one of] prefixes.
1484 Every subcommand except `help' requires at least one, so we parse
1485 them out here and store in opt_state. */
1487 if (subcommand->cmd_func != subcommand_help)
1490 opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
1491 sizeof(const char *));
1492 for (i = os->ind ; i< os->argc; i++)
1496 /* Ensure that each prefix is UTF8-encoded, in internal
1497 style, and absolute. */
1498 SVN_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
1499 prefix = svn_relpath__internal_style(prefix, pool);
1500 if (prefix[0] != '/')
1501 prefix = apr_pstrcat(pool, "/", prefix, SVN_VA_NULL);
1502 APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1505 if (opt_state.targets_file)
1507 svn_stringbuf_t *buffer, *buffer_utf8;
1508 const char *utf8_targets_file;
1509 apr_array_header_t *targets = apr_array_make(pool, 0,
1510 sizeof(const char *));
1512 /* We need to convert to UTF-8 now, even before we divide
1513 the targets into an array, because otherwise we wouldn't
1514 know what delimiter to use for svn_cstring_split(). */
1516 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_targets_file,
1517 opt_state.targets_file, pool));
1519 SVN_ERR(svn_stringbuf_from_file2(&buffer, utf8_targets_file,
1521 SVN_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1523 targets = apr_array_append(pool,
1524 svn_cstring_split(buffer_utf8->data, "\n\r",
1528 for (i = 0; i < targets->nelts; i++)
1530 const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
1531 if (prefix[0] != '/')
1532 prefix = apr_pstrcat(pool, "/", prefix, SVN_VA_NULL);
1533 APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1537 if (apr_is_empty_array(opt_state.prefixes))
1539 svn_error_clear(svn_cmdline_fprintf
1541 _("\nError: no prefixes supplied.\n")));
1542 *exit_code = EXIT_FAILURE;
1543 return SVN_NO_ERROR;
1548 /* Check that the subcommand wasn't passed any inappropriate options. */
1549 for (i = 0; i < received_opts->nelts; i++)
1551 opt_id = APR_ARRAY_IDX(received_opts, i, int);
1553 /* All commands implicitly accept --help, so just skip over this
1554 when we see it. Note that we don't want to include this option
1555 in their "accepted options" list because it would be awfully
1556 redundant to display it in every commands' help text. */
1557 if (opt_id == 'h' || opt_id == '?')
1560 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1563 const apr_getopt_option_t *badopt =
1564 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
1566 svn_opt_format_option(&optstr, badopt, FALSE, pool);
1567 if (subcommand->name[0] == '-')
1568 SVN_ERR(subcommand_help(NULL, NULL, pool));
1570 svn_error_clear(svn_cmdline_fprintf
1572 _("Subcommand '%s' doesn't accept option '%s'\n"
1573 "Type 'svndumpfilter help %s' for usage.\n"),
1574 subcommand->name, optstr, subcommand->name));
1575 *exit_code = EXIT_FAILURE;
1576 return SVN_NO_ERROR;
1580 /* Run the subcommand. */
1581 err = (*subcommand->cmd_func)(os, &opt_state, pool);
1584 /* For argument-related problems, suggest using the 'help'
1586 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1587 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1589 err = svn_error_quick_wrap(err,
1590 _("Try 'svndumpfilter help' for more "
1596 return SVN_NO_ERROR;
1600 main(int argc, const char *argv[])
1603 int exit_code = EXIT_SUCCESS;
1606 /* Initialize the app. */
1607 if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1608 return EXIT_FAILURE;
1610 /* Create our top-level pool. Use a separate mutexless allocator,
1611 * given this application is single threaded.
1613 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1615 err = sub_main(&exit_code, argc, argv, pool);
1617 /* Flush stdout and report if it fails. It would be flushed on exit anyway
1618 but this makes sure that output is not silently lost if it fails. */
1619 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1623 exit_code = EXIT_FAILURE;
1624 svn_cmdline_handle_exit_error(err, NULL, "svndumpfilter: ");
1627 svn_pool_destroy(pool);