]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svndumpfilter/svndumpfilter.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / svndumpfilter / svndumpfilter.c
1 /*
2  * svndumpfilter.c: Subversion dump stream filtering tool main file.
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 #include <stdlib.h>
26
27 #include <apr_file_io.h>
28
29 #include "svn_private_config.h"
30 #include "svn_cmdline.h"
31 #include "svn_error.h"
32 #include "svn_string.h"
33 #include "svn_opt.h"
34 #include "svn_utf.h"
35 #include "svn_dirent_uri.h"
36 #include "svn_path.h"
37 #include "svn_hash.h"
38 #include "svn_repos.h"
39 #include "svn_fs.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"
45
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"
50
51 /*** Code. ***/
52
53 /* Writes a property in dumpfile format to given stringbuf. */
54 static void
55 write_prop_to_stringbuf(svn_stringbuf_t *strbuf,
56                         const char *name,
57                         const svn_string_t *value)
58 {
59   int bytes_used;
60   size_t namelen;
61   char buf[SVN_KEYLINE_MAXLEN];
62
63   /* Output name length, then name. */
64   namelen = strlen(name);
65   svn_stringbuf_appendbytes(strbuf, "K ", 2);
66
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');
70
71   svn_stringbuf_appendbytes(strbuf, name, namelen);
72   svn_stringbuf_appendbyte(strbuf, '\n');
73
74   /* Output value length, then value. */
75   svn_stringbuf_appendbytes(strbuf, "V ", 2);
76
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');
80
81   svn_stringbuf_appendbytes(strbuf, value->data, value->len);
82   svn_stringbuf_appendbyte(strbuf, '\n');
83 }
84
85
86 /* Writes a property deletion in dumpfile format to given stringbuf. */
87 static void
88 write_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
89                            const char *name)
90 {
91   int bytes_used;
92   size_t namelen;
93   char buf[SVN_KEYLINE_MAXLEN];
94
95   /* Output name length, then name. */
96   namelen = strlen(name);
97   svn_stringbuf_appendbytes(*strbuf, "D ", 2);
98
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');
102
103   svn_stringbuf_appendbytes(*strbuf, name, namelen);
104   svn_stringbuf_appendbyte(*strbuf, '\n');
105 }
106
107
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(). */
113 static svn_boolean_t
114 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
115 {
116   int i;
117   size_t path_len = strlen(path);
118
119   for (i = 0; i < pfxlist->nelts; i++)
120     {
121       const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
122       size_t pfx_len = strlen(pfx);
123
124       if (path_len < pfx_len)
125         continue;
126       if (strncmp(path, pfx, pfx_len) == 0
127           && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
128         return TRUE;
129     }
130
131   return FALSE;
132 }
133
134
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)
141 {
142   const svn_boolean_t matches =
143     (glob
144      ? svn_cstring_match_glob_list(path, prefixes)
145      : ary_prefix_match(prefixes, path));
146
147   /* NXOR */
148   return (matches ? do_exclude : !do_exclude);
149 }
150
151
152 \f
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.
165 */
166
167
168 /* Filtering batons */
169
170 struct revmap_t
171 {
172   svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
173   svn_boolean_t was_dropped; /* Was this revision dropped? */
174 };
175
176 struct parse_baton_t
177 {
178   /* Command-line options values. */
179   svn_boolean_t do_exclude;
180   svn_boolean_t quiet;
181   svn_boolean_t glob;
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;
189
190   /* Input and output streams. */
191   svn_stream_t *in_stream;
192   svn_stream_t *out_stream;
193
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;
202 };
203
204 struct revision_baton_t
205 {
206   /* Reference to the global parse baton. */
207   struct parse_baton_t *pb;
208
209   /* Does this revision have node or prop changes? */
210   svn_boolean_t has_nodes;
211
212   /* Did we drop any nodes? */
213   svn_boolean_t had_dropped_nodes;
214
215   /* Written to output stream? */
216   svn_boolean_t writing_begun;
217
218   /* The original and new (re-mapped) revision numbers. */
219   svn_revnum_t rev_orig;
220   svn_revnum_t rev_actual;
221
222   /* Pointers to dumpfile data. */
223   apr_hash_t *original_headers;
224   apr_hash_t *props;
225 };
226
227 struct node_baton_t
228 {
229   /* Reference to the current revision baton. */
230   struct revision_baton_t *rb;
231
232   /* Are we skipping this node? */
233   svn_boolean_t do_skip;
234
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;
239
240   /* Written to output stream? */
241   svn_boolean_t writing_begun;
242
243   /* The text content length according to the dumpfile headers, because we
244      need the length before we have the actual text. */
245   svn_filesize_t tcl;
246
247   /* Pointers to dumpfile data. */
248   svn_repos__dumpfile_headers_t *headers;
249   svn_stringbuf_t *props;
250
251   /* Expect deltas? */
252   svn_boolean_t has_prop_delta;
253   svn_boolean_t has_text_delta;
254
255   /* We might need the node path in a parse error message. */
256   char *node_path;
257
258   apr_pool_t *node_pool;
259 };
260
261 \f
262
263 /* Filtering vtable members */
264
265 /* File-format stamp. */
266 static svn_error_t *
267 magic_header_record(int version, void *parse_baton, apr_pool_t *pool)
268 {
269   struct parse_baton_t *pb = parse_baton;
270
271   if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
272     pb->allow_deltas = TRUE;
273
274   SVN_ERR(svn_stream_printf(pb->out_stream, pool,
275                             SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
276                             version));
277
278   return SVN_NO_ERROR;
279 }
280
281
282 /* Return a deep copy of a (char * -> char *) hash. */
283 static apr_hash_t *
284 headers_dup(apr_hash_t *headers,
285             apr_pool_t *pool)
286 {
287   apr_hash_t *new_hash = apr_hash_make(pool);
288   apr_hash_index_t *hi;
289
290   for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
291     {
292       const char *key = apr_hash_this_key(hi);
293       const char *val = apr_hash_this_val(hi);
294
295       svn_hash_sets(new_hash, apr_pstrdup(pool, key), apr_pstrdup(pool, val));
296     }
297   return new_hash;
298 }
299
300 /* New revision: set up revision_baton, decide if we skip it. */
301 static svn_error_t *
302 new_revision_record(void **revision_baton,
303                     apr_hash_t *headers,
304                     void *parse_baton,
305                     apr_pool_t *pool)
306 {
307   struct revision_baton_t *rb;
308   const char *rev_orig;
309
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);
318
319   rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
320   rb->rev_orig = SVN_STR_TO_REV(rev_orig);
321
322   if (rb->pb->do_renumber_revs)
323     rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
324   else
325     rb->rev_actual = rb->rev_orig;
326
327   return SVN_NO_ERROR;
328 }
329
330
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. */
335 static svn_error_t *
336 output_revision(struct revision_baton_t *rb)
337 {
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);
341
342   rb->writing_begun = TRUE;
343
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:
348        - the date
349        - a log message that reports that this revision is just stuffing. */
350   if ((! rb->pb->preserve_revprops)
351       && (! rb->has_nodes)
352       && rb->had_dropped_nodes
353       && (! rb->pb->drop_empty_revs)
354       && (! rb->pb->drop_all_empty_revs))
355     {
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));
363     }
364
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.
373   */
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;
380
381   if (write_out_rev)
382     {
383       /* This revision is a keeper. */
384       SVN_ERR(svn_repos__dump_revision_record(rb->pb->out_stream,
385                                               rb->rev_actual,
386                                               rb->original_headers,
387                                               rb->props,
388                                               FALSE /*props_section_always*/,
389                                               subpool));
390
391       /* Stash the oldest original rev not dropped. */
392       if (rb->rev_orig > 0
393           && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
394         rb->pb->oldest_original_rev = rb->rev_orig;
395
396       if (rb->pb->do_renumber_revs)
397         {
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;
409         }
410
411       if (! rb->pb->quiet)
412         SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
413                                     _("Revision %ld committed as %ld.\n"),
414                                     rb->rev_orig, rb->rev_actual));
415     }
416   else
417     {
418       /* We're dropping this revision. */
419       rb->pb->rev_drop_count++;
420       if (rb->pb->do_renumber_revs)
421         {
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);
432         }
433
434       if (! rb->pb->quiet)
435         SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
436                                     _("Revision %ld skipped.\n"),
437                                     rb->rev_orig));
438     }
439   svn_pool_destroy(subpool);
440   return SVN_NO_ERROR;
441 }
442
443
444 /* UUID record here: dump it, as we do not filter them. */
445 static svn_error_t *
446 uuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
447 {
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));
451   return SVN_NO_ERROR;
452 }
453
454
455 /* New node here. Set up node_baton by copying headers. */
456 static svn_error_t *
457 new_node_record(void **node_baton,
458                 apr_hash_t *headers,
459                 void *rev_baton,
460                 apr_pool_t *pool)
461 {
462   struct parse_baton_t *pb;
463   struct node_baton_t *nb;
464   char *node_path, *copyfrom_path;
465   apr_hash_index_t *hi;
466   const char *tcl;
467
468   *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
469   nb          = *node_baton;
470   nb->rb      = rev_baton;
471   nb->node_pool = pool;
472   pb          = nb->rb->pb;
473
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);
476
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);
482
483   nb->do_skip = skip_path(node_path, pb->prefixes,
484                           pb->do_exclude, pb->glob);
485
486   /* If we're skipping the node, take note of path, discarding the
487      rest.  */
488   if (nb->do_skip)
489     {
490       svn_hash_sets(pb->dropped_nodes,
491                     apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
492                                 node_path),
493                     (void *)1);
494       nb->rb->had_dropped_nodes = TRUE;
495     }
496   else
497     {
498       const char *kind;
499       const char *action;
500
501       tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
502
503       /* Test if this node was copied from dropped source. */
504       if (copyfrom_path &&
505           skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
506         {
507           /* This node was copied from a dropped source.
508              We have a problem, since we did not want to drop this node too.
509
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
514              contents.  */
515           kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
516
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))
520             {
521               svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
522                             NULL);
523               svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
524                             NULL);
525               copyfrom_path = NULL;
526             }
527           /* Else, this is either a directory or a file whose contents we
528              don't have readily available.  */
529           else
530             {
531               return svn_error_createf
532                 (SVN_ERR_INCOMPLETE_DATA, 0,
533                  _("Invalid copy source path '%s'"), copyfrom_path);
534             }
535         }
536
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);
546
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));
552
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);
557
558       /* Node-kind is next and is optional. */
559       kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
560       if (kind)
561         svn_repos__dumpfile_header_push(
562           nb->headers, SVN_REPOS_DUMPFILE_NODE_KIND, kind);
563
564       /* Node-action is next and required. */
565       action = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION);
566       if (action)
567         svn_repos__dumpfile_header_push(
568           nb->headers, SVN_REPOS_DUMPFILE_NODE_ACTION, action);
569       else
570         return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
571                                  _("Missing Node-action for path '%s'"),
572                                  node_path);
573
574       for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
575         {
576           const char *key = apr_hash_this_key(hi);
577           const char *val = apr_hash_this_val(hi);
578
579           if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
580               && (!strcmp(val, "true")))
581             nb->has_prop_delta = TRUE;
582
583           if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
584               && (!strcmp(val, "true")))
585             nb->has_text_delta = TRUE;
586
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)))
593             continue;
594
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.
600           */
601           if (pb->do_renumber_revs
602               && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
603             {
604               svn_revnum_t cf_orig_rev;
605               struct revmap_t *cf_renum_val;
606
607               cf_orig_rev = SVN_STR_TO_REV(val);
608               cf_renum_val = apr_hash_get(pb->renumber_history,
609                                           &cf_orig_rev,
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);
618               continue;
619             }
620
621           /* passthru: put header straight to output */
622           svn_repos__dumpfile_header_push(nb->headers, key, val);
623         }
624     }
625
626   return SVN_NO_ERROR;
627 }
628
629
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
633    POOL). */
634 static svn_error_t *
635 adjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
636                  struct revision_baton_t *rb, apr_pool_t *pool)
637 {
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);
642
643   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
644
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
653      want filtered.
654
655      If the oldest rev is r0 then there is nothing to filter. */
656
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. */
662
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));
668
669   for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
670     {
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;
674
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))
677         {
678           if (pb->skip_missing_merge_sources)
679             continue;
680           else
681             return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
682                                      _("Missing merge source path '%s'; try "
683                                        "with --skip-missing-merge-sources"),
684                                      merge_source);
685         }
686
687       /* Possibly renumber revisions in merge source's rangelist. */
688       if (pb->do_renumber_revs)
689         {
690           int i;
691
692           for (i = 0; i < rangelist->nelts; i++)
693             {
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 *);
698
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"));
705
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"));
712
713               range->start = revmap_start->rev;
714               range->end = revmap_end->rev;
715             }
716         }
717       svn_hash_sets(final_mergeinfo, merge_source, rangelist);
718     }
719
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);
723
724   return SVN_NO_ERROR;
725 }
726
727
728 static svn_error_t *
729 set_revision_property(void *revision_baton,
730                       const char *name,
731                       const svn_string_t *value)
732 {
733   struct revision_baton_t *rb = revision_baton;
734   apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
735
736   svn_hash_sets(rb->props,
737                 apr_pstrdup(hash_pool, name),
738                 svn_string_dup(value, hash_pool));
739   return SVN_NO_ERROR;
740 }
741
742
743 static svn_error_t *
744 set_node_property(void *node_baton,
745                   const char *name,
746                   const svn_string_t *value)
747 {
748   struct node_baton_t *nb = node_baton;
749   struct revision_baton_t *rb = nb->rb;
750
751   if (nb->do_skip)
752     return SVN_NO_ERROR;
753
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 "
761                                "revision %ld"),
762                              nb->node_path, rb->rev_orig);
763
764   if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
765     {
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;
770     }
771
772   nb->has_props = TRUE;
773   write_prop_to_stringbuf(nb->props, name, value);
774
775   return SVN_NO_ERROR;
776 }
777
778
779 static svn_error_t *
780 delete_node_property(void *node_baton, const char *name)
781 {
782   struct node_baton_t *nb = node_baton;
783   struct revision_baton_t *rb = nb->rb;
784
785   if (nb->do_skip)
786     return SVN_NO_ERROR;
787
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 "
792                                "revision %ld"),
793                              nb->node_path, rb->rev_orig);
794
795   nb->has_props = TRUE;
796   write_propdel_to_stringbuf(&(nb->props), name);
797
798   return SVN_NO_ERROR;
799 }
800
801
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.
805  */
806 static svn_error_t *
807 remove_node_props(void *node_baton)
808 {
809   struct node_baton_t *nb = node_baton;
810
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;
814
815   return SVN_NO_ERROR;
816 }
817
818
819 static svn_error_t *
820 set_fulltext(svn_stream_t **stream, void *node_baton)
821 {
822   struct node_baton_t *nb = node_baton;
823
824   if (!nb->do_skip)
825     {
826       nb->has_text = TRUE;
827       if (! nb->writing_begun)
828         {
829           nb->writing_begun = TRUE;
830           if (nb->has_props)
831             {
832               svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
833             }
834           SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
835                                               nb->headers,
836                                               nb->has_props ? nb->props : NULL,
837                                               nb->has_text,
838                                               nb->tcl,
839                                               TRUE /*content_length_always*/,
840                                               nb->node_pool));
841         }
842       *stream = nb->rb->pb->out_stream;
843     }
844
845   return SVN_NO_ERROR;
846 }
847
848
849 /* Finalize node */
850 static svn_error_t *
851 close_node(void *node_baton)
852 {
853   struct node_baton_t *nb = node_baton;
854   apr_size_t len = 2;
855
856   /* Get out of here if we can. */
857   if (nb->do_skip)
858     return SVN_NO_ERROR;
859
860   /* If the node was not flushed already to output its text, do it now. */
861   if (! nb->writing_begun)
862     {
863       nb->writing_begun = TRUE;
864       if (nb->has_props)
865         {
866           svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
867         }
868       SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
869                                           nb->headers,
870                                           nb->has_props ? nb->props : NULL,
871                                           nb->has_text,
872                                           nb->tcl,
873                                           TRUE /*content_length_always*/,
874                                           nb->node_pool));
875     }
876
877   /* put an end to node. */
878   SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
879
880   return SVN_NO_ERROR;
881 }
882
883
884 /* Finalize revision */
885 static svn_error_t *
886 close_revision(void *revision_baton)
887 {
888   struct revision_baton_t *rb = revision_baton;
889
890   /* If no node has yet flushed the revision, do it now. */
891   if (! rb->writing_begun)
892     return output_revision(rb);
893   else
894     return SVN_NO_ERROR;
895 }
896
897
898 /* Filtering vtable */
899 static svn_repos_parse_fns3_t filtering_vtable =
900   {
901     magic_header_record,
902     uuid_record,
903     new_revision_record,
904     new_node_record,
905     set_revision_property,
906     set_node_property,
907     delete_node_property,
908     remove_node_props,
909     set_fulltext,
910     NULL,
911     close_node,
912     close_revision
913   };
914
915
916 \f
917 /** Subcommands. **/
918
919 static svn_opt_subcommand_t
920   subcommand_help,
921   subcommand_exclude,
922   subcommand_include;
923
924 enum
925   {
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,
933     svndumpfilter__glob,
934     svndumpfilter__version
935   };
936
937 /* Option codes and descriptions.
938  *
939  * The entire list must be terminated with an entry of nulls.
940  */
941 static const apr_getopt_option_t options_table[] =
942   {
943     {"help",          'h', 0,
944      N_("show help on a subcommand")},
945
946     {NULL,            '?', 0,
947      N_("show help on a subcommand")},
948
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"
972         "                             file ARG.")},
973     {NULL}
974   };
975
976
977 /* Array of available subcommands.
978  * The entire list must be terminated with an entry of nulls.
979  */
980 static const svn_opt_subcommand_desc2_t cmd_table[] =
981   {
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} },
990
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} },
999
1000     {"help", subcommand_help, {"?", "h"},
1001      N_("Describe the usage of this program or its subcommands.\n"
1002         "usage: svndumpfilter help [SUBCOMMAND...]\n"),
1003      {0} },
1004
1005     { NULL, NULL, {0}, NULL, {0} }
1006   };
1007
1008
1009 /* Baton for passing option/argument state to a subcommand function. */
1010 struct svndumpfilter_opt_state
1011 {
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.           */
1026 };
1027
1028
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,
1033                        apr_pool_t *pool)
1034 {
1035   struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1036
1037   /* Read the stream from STDIN.  Users can redirect a file. */
1038   SVN_ERR(svn_stream_for_stdin2(&baton->in_stream, TRUE, pool));
1039
1040   /* Have the parser dump results to STDOUT. Users can redirect a file. */
1041   SVN_ERR(svn_stream_for_stdout(&baton->out_stream, pool));
1042
1043   baton->do_exclude = do_exclude;
1044
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));
1050
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;
1064
1065   *pb = baton;
1066   return SVN_NO_ERROR;
1067 }
1068
1069 /* This implements `help` subcommand. */
1070 static svn_error_t *
1071 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1072 {
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"
1080       "\n"
1081       "Available subcommands:\n");
1082
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,
1088                               NULL, NULL, pool));
1089
1090   return SVN_NO_ERROR;
1091 }
1092
1093
1094 /* Version compatibility check */
1095 static svn_error_t *
1096 check_lib_versions(void)
1097 {
1098   static const svn_version_checklist_t checklist[] =
1099     {
1100       { "svn_subr",  svn_subr_version },
1101       { "svn_repos", svn_repos_version },
1102       { "svn_delta", svn_delta_version },
1103       { NULL, NULL }
1104     };
1105   SVN_VERSION_DEFINE(my_version);
1106
1107   return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1108 }
1109
1110
1111 /* Do the real work of filtering. */
1112 static svn_error_t *
1113 do_filter(apr_getopt_t *os,
1114           void *baton,
1115           svn_boolean_t do_exclude,
1116           apr_pool_t *pool)
1117 {
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;
1122   int i, num_keys;
1123
1124   if (! opt_state->quiet)
1125     {
1126       apr_pool_t *subpool = svn_pool_create(pool);
1127
1128       if (opt_state->glob)
1129         {
1130           SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1131                                       do_exclude
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")));
1142         }
1143       else
1144         {
1145           SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1146                                       do_exclude
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")));
1157         }
1158
1159       for (i = 0; i < opt_state->prefixes->nelts; i++)
1160         {
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 *)));
1165         }
1166
1167       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1168       svn_pool_destroy(subpool);
1169     }
1170
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));
1174
1175   /* The rest of this is just reporting.  If we aren't reporting, get
1176      outta here. */
1177   if (opt_state->quiet)
1178     return SVN_NO_ERROR;
1179
1180   SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1181
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));
1188
1189   if (pb->do_renumber_revs)
1190     {
1191       apr_pool_t *subpool = svn_pool_create(pool);
1192       SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1193                                 stderr, subpool));
1194
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);
1200            hi;
1201            hi = apr_hash_next(hi))
1202         {
1203           const svn_revnum_t *revnum = apr_hash_this_key(hi);
1204
1205           APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1206         }
1207       svn_sort__array(keys, svn_sort_compare_revisions);
1208       for (i = 0; i < keys->nelts; i++)
1209         {
1210           svn_revnum_t this_key;
1211           struct revmap_t *this_val;
1212
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,
1216                                   sizeof(this_key));
1217           if (this_val->was_dropped)
1218             SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1219                                         _("   %ld => (dropped)\n"),
1220                                         this_key));
1221           else
1222             SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1223                                         "   %ld => %ld\n",
1224                                         this_key, this_val->rev));
1225         }
1226       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1227       svn_pool_destroy(subpool);
1228     }
1229
1230   if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1231     {
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",
1236                                      num_keys),
1237                                   num_keys));
1238
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);
1243            hi;
1244            hi = apr_hash_next(hi))
1245         {
1246           const char *path = apr_hash_this_key(hi);
1247
1248           APR_ARRAY_PUSH(keys, const char *) = path;
1249         }
1250       svn_sort__array(keys, svn_sort_compare_paths);
1251       for (i = 0; i < keys->nelts; i++)
1252         {
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 *)));
1257         }
1258       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1259       svn_pool_destroy(subpool);
1260     }
1261
1262   return SVN_NO_ERROR;
1263 }
1264
1265 /* This implements `exclude' subcommand. */
1266 static svn_error_t *
1267 subcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1268 {
1269   return do_filter(os, baton, TRUE, pool);
1270 }
1271
1272
1273 /* This implements `include` subcommand. */
1274 static svn_error_t *
1275 subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1276 {
1277   return do_filter(os, baton, FALSE, pool);
1278 }
1279
1280
1281 \f
1282 /** Main. **/
1283
1284 /*
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.
1288  */
1289 static svn_error_t *
1290 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1291 {
1292   svn_error_t *err;
1293   apr_status_t apr_err;
1294
1295   const svn_opt_subcommand_desc2_t *subcommand = NULL;
1296   struct svndumpfilter_opt_state opt_state;
1297   apr_getopt_t *os;
1298   int opt_id;
1299   apr_array_header_t *received_opts;
1300   int i;
1301
1302   /* Check library versions */
1303   SVN_ERR(check_lib_versions());
1304
1305   received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1306
1307   /* Initialize the FS library. */
1308   SVN_ERR(svn_fs_initialize(pool));
1309
1310   if (argc <= 1)
1311     {
1312       SVN_ERR(subcommand_help(NULL, NULL, pool));
1313       *exit_code = EXIT_FAILURE;
1314       return SVN_NO_ERROR;
1315     }
1316
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;
1321
1322   /* Parse options. */
1323   SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
1324
1325   os->interleave = 1;
1326   while (1)
1327     {
1328       const char *opt_arg;
1329
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))
1333         break;
1334       else if (apr_err)
1335         {
1336           SVN_ERR(subcommand_help(NULL, NULL, pool));
1337           *exit_code = EXIT_FAILURE;
1338           return SVN_NO_ERROR;
1339         }
1340
1341       /* Stash the option code in an array before parsing it. */
1342       APR_ARRAY_PUSH(received_opts, int) = opt_id;
1343
1344       switch (opt_id)
1345         {
1346         case 'h':
1347         case '?':
1348           opt_state.help = TRUE;
1349           break;
1350         case svndumpfilter__version:
1351           opt_state.version = TRUE;
1352           break;
1353         case svndumpfilter__quiet:
1354           opt_state.quiet = TRUE;
1355           break;
1356         case svndumpfilter__glob:
1357           opt_state.glob = TRUE;
1358           break;
1359         case svndumpfilter__drop_empty_revs:
1360           opt_state.drop_empty_revs = TRUE;
1361           break;
1362         case svndumpfilter__drop_all_empty_revs:
1363           opt_state.drop_all_empty_revs = TRUE;
1364           break;
1365         case svndumpfilter__renumber_revs:
1366           opt_state.renumber_revs = TRUE;
1367           break;
1368         case svndumpfilter__preserve_revprops:
1369           opt_state.preserve_revprops = TRUE;
1370           break;
1371         case svndumpfilter__skip_missing_merge_sources:
1372           opt_state.skip_missing_merge_sources = TRUE;
1373           break;
1374         case svndumpfilter__targets:
1375           SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.targets_file,
1376                                           opt_arg, pool));
1377           break;
1378         default:
1379           {
1380             SVN_ERR(subcommand_help(NULL, NULL, pool));
1381             *exit_code = EXIT_FAILURE;
1382             return SVN_NO_ERROR;
1383           }
1384         }  /* close `switch' */
1385     }  /* close `while' */
1386
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)
1390     {
1391       return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS,
1392                               NULL,
1393                               _("--drop-empty-revs cannot be used with "
1394                                 "--drop-all-empty-revs"));
1395     }
1396
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(). */
1401   if (opt_state.help)
1402     subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
1403
1404   /* If we're not running the `help' subcommand, then look for a
1405      subcommand in the first argument. */
1406   if (subcommand == NULL)
1407     {
1408       if (os->ind >= os->argc)
1409         {
1410           if (opt_state.version)
1411             {
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,
1417                   } };
1418
1419               subcommand = &pseudo_cmd;
1420             }
1421           else
1422             {
1423               svn_error_clear(svn_cmdline_fprintf
1424                               (stderr, pool,
1425                                _("Subcommand argument required\n")));
1426               SVN_ERR(subcommand_help(NULL, NULL, pool));
1427               *exit_code = EXIT_FAILURE;
1428               return SVN_NO_ERROR;
1429             }
1430         }
1431       else
1432         {
1433           const char *first_arg;
1434
1435           SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
1436                                           pool));
1437           subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
1438           if (subcommand == NULL)
1439             {
1440               svn_error_clear(
1441                 svn_cmdline_fprintf(stderr, pool,
1442                                     _("Unknown subcommand: '%s'\n"),
1443                                     first_arg));
1444               SVN_ERR(subcommand_help(NULL, NULL, pool));
1445               *exit_code = EXIT_FAILURE;
1446               return SVN_NO_ERROR;
1447             }
1448         }
1449     }
1450
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. */
1454
1455   if (subcommand->cmd_func != subcommand_help)
1456     {
1457
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++)
1461         {
1462           const char *prefix;
1463
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;
1471         }
1472
1473       if (opt_state.targets_file)
1474         {
1475           svn_stringbuf_t *buffer, *buffer_utf8;
1476           apr_array_header_t *targets = apr_array_make(pool, 0,
1477                                                        sizeof(const char *));
1478
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,
1483                                            pool));
1484           SVN_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1485
1486           targets = apr_array_append(pool,
1487                          svn_cstring_split(buffer_utf8->data, "\n\r",
1488                                            TRUE, pool),
1489                          targets);
1490
1491           for (i = 0; i < targets->nelts; i++)
1492             {
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;
1497             }
1498         }
1499
1500       if (apr_is_empty_array(opt_state.prefixes))
1501         {
1502           svn_error_clear(svn_cmdline_fprintf
1503                           (stderr, pool,
1504                            _("\nError: no prefixes supplied.\n")));
1505           *exit_code = EXIT_FAILURE;
1506           return SVN_NO_ERROR;
1507         }
1508     }
1509
1510
1511   /* Check that the subcommand wasn't passed any inappropriate options. */
1512   for (i = 0; i < received_opts->nelts; i++)
1513     {
1514       opt_id = APR_ARRAY_IDX(received_opts, i, int);
1515
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 == '?')
1521         continue;
1522
1523       if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1524         {
1525           const char *optstr;
1526           const apr_getopt_option_t *badopt =
1527             svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
1528                                           pool);
1529           svn_opt_format_option(&optstr, badopt, FALSE, pool);
1530           if (subcommand->name[0] == '-')
1531             SVN_ERR(subcommand_help(NULL, NULL, pool));
1532           else
1533             svn_error_clear(svn_cmdline_fprintf
1534                             (stderr, pool,
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;
1540         }
1541     }
1542
1543   /* Run the subcommand. */
1544   err = (*subcommand->cmd_func)(os, &opt_state, pool);
1545   if (err)
1546     {
1547       /* For argument-related problems, suggest using the 'help'
1548          subcommand. */
1549       if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1550           || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1551         {
1552           err = svn_error_quick_wrap(err,
1553                                      _("Try 'svndumpfilter help' for more "
1554                                        "info"));
1555         }
1556       return err;
1557     }
1558
1559   return SVN_NO_ERROR;
1560 }
1561
1562 int
1563 main(int argc, const char *argv[])
1564 {
1565   apr_pool_t *pool;
1566   int exit_code = EXIT_SUCCESS;
1567   svn_error_t *err;
1568
1569   /* Initialize the app. */
1570   if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1571     return EXIT_FAILURE;
1572
1573   /* Create our top-level pool.  Use a separate mutexless allocator,
1574    * given this application is single threaded.
1575    */
1576   pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1577
1578   err = sub_main(&exit_code, argc, argv, pool);
1579
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));
1583
1584   if (err)
1585     {
1586       exit_code = EXIT_FAILURE;
1587       svn_cmdline_handle_exit_error(err, NULL, "svndumpfilter: ");
1588     }
1589
1590   svn_pool_destroy(pool);
1591   return exit_code;
1592 }