]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svndumpfilter/svndumpfilter.c
MFC r257129,257936,258084,258569,258602,262250,262251
[FreeBSD/stable/10.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_mergeinfo_private.h"
47 #include "private/svn_cmdline_private.h"
48 #include "private/svn_subr_private.h"
49
50 #ifdef _WIN32
51 typedef apr_status_t (__stdcall *open_fn_t)(apr_file_t **, apr_pool_t *);
52 #else
53 typedef apr_status_t (*open_fn_t)(apr_file_t **, apr_pool_t *);
54 #endif
55
56 /*** Code. ***/
57
58 /* Helper to open stdio streams */
59
60 /* NOTE: we used to call svn_stream_from_stdio(), which wraps a stream
61    around a standard stdio.h FILE pointer.  The problem is that these
62    pointers operate through C Run Time (CRT) on Win32, which does all
63    sorts of translation on them: LF's become CRLF's, and ctrl-Z's
64    embedded in Word documents are interpreted as premature EOF's.
65
66    So instead, we use apr_file_open_std*, which bypass the CRT and
67    directly wrap the OS's file-handles, which don't know or care about
68    translation.  Thus dump/load works correctly on Win32.
69 */
70 static svn_error_t *
71 create_stdio_stream(svn_stream_t **stream,
72                     open_fn_t open_fn,
73                     apr_pool_t *pool)
74 {
75   apr_file_t *stdio_file;
76   apr_status_t apr_err = open_fn(&stdio_file, pool);
77
78   if (apr_err)
79     return svn_error_wrap_apr(apr_err, _("Can't open stdio file"));
80
81   *stream = svn_stream_from_aprfile2(stdio_file, TRUE, pool);
82   return SVN_NO_ERROR;
83 }
84
85
86 /* Writes a property in dumpfile format to given stringbuf. */
87 static void
88 write_prop_to_stringbuf(svn_stringbuf_t *strbuf,
89                         const char *name,
90                         const svn_string_t *value)
91 {
92   int bytes_used;
93   size_t namelen;
94   char buf[SVN_KEYLINE_MAXLEN];
95
96   /* Output name length, then name. */
97   namelen = strlen(name);
98   svn_stringbuf_appendbytes(strbuf, "K ", 2);
99
100   bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
101   svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
102   svn_stringbuf_appendbyte(strbuf, '\n');
103
104   svn_stringbuf_appendbytes(strbuf, name, namelen);
105   svn_stringbuf_appendbyte(strbuf, '\n');
106
107   /* Output value length, then value. */
108   svn_stringbuf_appendbytes(strbuf, "V ", 2);
109
110   bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len);
111   svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
112   svn_stringbuf_appendbyte(strbuf, '\n');
113
114   svn_stringbuf_appendbytes(strbuf, value->data, value->len);
115   svn_stringbuf_appendbyte(strbuf, '\n');
116 }
117
118
119 /* Writes a property deletion in dumpfile format to given stringbuf. */
120 static void
121 write_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
122                            const char *name)
123 {
124   int bytes_used;
125   size_t namelen;
126   char buf[SVN_KEYLINE_MAXLEN];
127
128   /* Output name length, then name. */
129   namelen = strlen(name);
130   svn_stringbuf_appendbytes(*strbuf, "D ", 2);
131
132   bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
133   svn_stringbuf_appendbytes(*strbuf, buf, bytes_used);
134   svn_stringbuf_appendbyte(*strbuf, '\n');
135
136   svn_stringbuf_appendbytes(*strbuf, name, namelen);
137   svn_stringbuf_appendbyte(*strbuf, '\n');
138 }
139
140
141 /* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
142  * Return TRUE if any prefix is a prefix of PATH (matching whole path
143  * components); FALSE otherwise.
144  * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
145 static svn_boolean_t
146 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
147 {
148   int i;
149   size_t path_len = strlen(path);
150
151   for (i = 0; i < pfxlist->nelts; i++)
152     {
153       const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
154       size_t pfx_len = strlen(pfx);
155
156       if (path_len < pfx_len)
157         continue;
158       if (strncmp(path, pfx, pfx_len) == 0
159           && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
160         return TRUE;
161     }
162
163   return FALSE;
164 }
165
166
167 /* Check whether we need to skip this PATH based on its presence in
168    the PREFIXES list, and the DO_EXCLUDE option.
169    PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
170 static APR_INLINE svn_boolean_t
171 skip_path(const char *path, const apr_array_header_t *prefixes,
172           svn_boolean_t do_exclude, svn_boolean_t glob)
173 {
174   const svn_boolean_t matches =
175     (glob
176      ? svn_cstring_match_glob_list(path, prefixes)
177      : ary_prefix_match(prefixes, path));
178
179   /* NXOR */
180   return (matches ? do_exclude : !do_exclude);
181 }
182
183
184 \f
185 /* Note: the input stream parser calls us with events.
186    Output of the filtered dump occurs for the most part streamily with the
187    event callbacks, to avoid caching large quantities of data in memory.
188    The exceptions this are:
189    - All revision data (headers and props) must be cached until a non-skipped
190      node within the revision is found, or the revision is closed.
191    - Node headers and props must be cached until all props have been received
192      (to allow the Prop-content-length to be found). This is signalled either
193      by the node text arriving, or the node being closed.
194    The writing_begun members of the associated object batons track the state.
195    output_revision() and output_node() are called to cause this flushing of
196    cached data to occur.
197 */
198
199
200 /* Filtering batons */
201
202 struct revmap_t
203 {
204   svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
205   svn_boolean_t was_dropped; /* Was this revision dropped? */
206 };
207
208 struct parse_baton_t
209 {
210   /* Command-line options values. */
211   svn_boolean_t do_exclude;
212   svn_boolean_t quiet;
213   svn_boolean_t glob;
214   svn_boolean_t drop_empty_revs;
215   svn_boolean_t drop_all_empty_revs;
216   svn_boolean_t do_renumber_revs;
217   svn_boolean_t preserve_revprops;
218   svn_boolean_t skip_missing_merge_sources;
219   svn_boolean_t allow_deltas;
220   apr_array_header_t *prefixes;
221
222   /* Input and output streams. */
223   svn_stream_t *in_stream;
224   svn_stream_t *out_stream;
225
226   /* State for the filtering process. */
227   apr_int32_t rev_drop_count;
228   apr_hash_t *dropped_nodes;
229   apr_hash_t *renumber_history;  /* svn_revnum_t -> struct revmap_t */
230   svn_revnum_t last_live_revision;
231   /* The oldest original revision, greater than r0, in the input
232      stream which was not filtered. */
233   svn_revnum_t oldest_original_rev;
234 };
235
236 struct revision_baton_t
237 {
238   /* Reference to the global parse baton. */
239   struct parse_baton_t *pb;
240
241   /* Does this revision have node or prop changes? */
242   svn_boolean_t has_nodes;
243   svn_boolean_t has_props;
244
245   /* Did we drop any nodes? */
246   svn_boolean_t had_dropped_nodes;
247
248   /* Written to output stream? */
249   svn_boolean_t writing_begun;
250
251   /* The original and new (re-mapped) revision numbers. */
252   svn_revnum_t rev_orig;
253   svn_revnum_t rev_actual;
254
255   /* Pointers to dumpfile data. */
256   svn_stringbuf_t *header;
257   apr_hash_t *props;
258 };
259
260 struct node_baton_t
261 {
262   /* Reference to the current revision baton. */
263   struct revision_baton_t *rb;
264
265   /* Are we skipping this node? */
266   svn_boolean_t do_skip;
267
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;
272
273   /* Written to output stream? */
274   svn_boolean_t writing_begun;
275
276   /* The text content length according to the dumpfile headers, because we
277      need the length before we have the actual text. */
278   svn_filesize_t tcl;
279
280   /* Pointers to dumpfile data. */
281   svn_stringbuf_t *header;
282   svn_stringbuf_t *props;
283
284   /* Expect deltas? */
285   svn_boolean_t has_prop_delta;
286   svn_boolean_t has_text_delta;
287
288   /* We might need the node path in a parse error message. */
289   char *node_path;
290 };
291
292 \f
293
294 /* Filtering vtable members */
295
296 /* File-format stamp. */
297 static svn_error_t *
298 magic_header_record(int version, void *parse_baton, apr_pool_t *pool)
299 {
300   struct parse_baton_t *pb = parse_baton;
301
302   if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
303     pb->allow_deltas = TRUE;
304
305   SVN_ERR(svn_stream_printf(pb->out_stream, pool,
306                             SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
307                             version));
308
309   return SVN_NO_ERROR;
310 }
311
312
313 /* New revision: set up revision_baton, decide if we skip it. */
314 static svn_error_t *
315 new_revision_record(void **revision_baton,
316                     apr_hash_t *headers,
317                     void *parse_baton,
318                     apr_pool_t *pool)
319 {
320   struct revision_baton_t *rb;
321   apr_hash_index_t *hi;
322   const char *rev_orig;
323   svn_stream_t *header_stream;
324
325   *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t));
326   rb = *revision_baton;
327   rb->pb = parse_baton;
328   rb->has_nodes = FALSE;
329   rb->has_props = FALSE;
330   rb->had_dropped_nodes = FALSE;
331   rb->writing_begun = FALSE;
332   rb->header = svn_stringbuf_create_empty(pool);
333   rb->props = apr_hash_make(pool);
334
335   header_stream = svn_stream_from_stringbuf(rb->header, pool);
336
337   rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
338   rb->rev_orig = SVN_STR_TO_REV(rev_orig);
339
340   if (rb->pb->do_renumber_revs)
341     rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
342   else
343     rb->rev_actual = rb->rev_orig;
344
345   SVN_ERR(svn_stream_printf(header_stream, pool,
346                             SVN_REPOS_DUMPFILE_REVISION_NUMBER ": %ld\n",
347                             rb->rev_actual));
348
349   for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
350     {
351       const char *key = svn__apr_hash_index_key(hi);
352       const char *val = svn__apr_hash_index_val(hi);
353
354       if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
355           || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
356           || (!strcmp(key, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
357         continue;
358
359       /* passthru: put header into header stringbuf. */
360
361       SVN_ERR(svn_stream_printf(header_stream, pool, "%s: %s\n",
362                                 key, val));
363     }
364
365   SVN_ERR(svn_stream_close(header_stream));
366
367   return SVN_NO_ERROR;
368 }
369
370
371 /* Output revision to dumpstream
372    This may be called by new_node_record(), iff rb->has_nodes has been set
373    to TRUE, or by close_revision() otherwise. This must only be called
374    if rb->writing_begun is FALSE. */
375 static svn_error_t *
376 output_revision(struct revision_baton_t *rb)
377 {
378   int bytes_used;
379   char buf[SVN_KEYLINE_MAXLEN];
380   apr_hash_index_t *hi;
381   svn_boolean_t write_out_rev = FALSE;
382   apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
383   svn_stringbuf_t *props = svn_stringbuf_create_empty(hash_pool);
384   apr_pool_t *subpool = svn_pool_create(hash_pool);
385
386   rb->writing_begun = TRUE;
387
388   /* If this revision has no nodes left because the ones it had were
389      dropped, and we are not dropping empty revisions, and we were not
390      told to preserve revision props, then we want to fixup the
391      revision props to only contain:
392        - the date
393        - a log message that reports that this revision is just stuffing. */
394   if ((! rb->pb->preserve_revprops)
395       && (! rb->has_nodes)
396       && rb->had_dropped_nodes
397       && (! rb->pb->drop_empty_revs)
398       && (! rb->pb->drop_all_empty_revs))
399     {
400       apr_hash_t *old_props = rb->props;
401       rb->has_props = TRUE;
402       rb->props = apr_hash_make(hash_pool);
403       svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE,
404                     svn_hash_gets(old_props, SVN_PROP_REVISION_DATE));
405       svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG,
406                     svn_string_create(_("This is an empty revision for "
407                                         "padding."), hash_pool));
408     }
409
410   /* Now, "rasterize" the props to a string, and append the property
411      information to the header string.  */
412   if (rb->has_props)
413     {
414       for (hi = apr_hash_first(subpool, rb->props);
415            hi;
416            hi = apr_hash_next(hi))
417         {
418           const char *pname = svn__apr_hash_index_key(hi);
419           const svn_string_t *pval = svn__apr_hash_index_val(hi);
420
421           write_prop_to_stringbuf(props, pname, pval);
422         }
423       svn_stringbuf_appendcstr(props, "PROPS-END\n");
424       svn_stringbuf_appendcstr(rb->header,
425                                SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
426       bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
427                                 props->len);
428       svn_stringbuf_appendbytes(rb->header, buf, bytes_used);
429       svn_stringbuf_appendbyte(rb->header, '\n');
430     }
431
432   svn_stringbuf_appendcstr(rb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
433   bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT, props->len);
434   svn_stringbuf_appendbytes(rb->header, buf, bytes_used);
435   svn_stringbuf_appendbyte(rb->header, '\n');
436
437   /* put an end to headers */
438   svn_stringbuf_appendbyte(rb->header, '\n');
439
440   /* put an end to revision */
441   svn_stringbuf_appendbyte(props, '\n');
442
443   /* write out the revision */
444   /* Revision is written out in the following cases:
445      1. If the revision has nodes or
446      it is revision 0 (Special case: To preserve the props on r0).
447      2. --drop-empty-revs has been supplied,
448      but revision has not all nodes dropped.
449      3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied,
450      write out the revision which has no nodes to begin with.
451   */
452   if (rb->has_nodes || (rb->rev_orig == 0))
453     write_out_rev = TRUE;
454   else if (rb->pb->drop_empty_revs)
455     write_out_rev = ! rb->had_dropped_nodes;
456   else if (! rb->pb->drop_all_empty_revs)
457     write_out_rev = TRUE;
458
459   if (write_out_rev)
460     {
461       /* This revision is a keeper. */
462       SVN_ERR(svn_stream_write(rb->pb->out_stream,
463                                rb->header->data, &(rb->header->len)));
464       SVN_ERR(svn_stream_write(rb->pb->out_stream,
465                                props->data, &(props->len)));
466
467       /* Stash the oldest original rev not dropped. */
468       if (rb->rev_orig > 0
469           && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
470         rb->pb->oldest_original_rev = rb->rev_orig;
471
472       if (rb->pb->do_renumber_revs)
473         {
474           svn_revnum_t *rr_key;
475           struct revmap_t *rr_val;
476           apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
477           rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
478           rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
479           *rr_key = rb->rev_orig;
480           rr_val->rev = rb->rev_actual;
481           rr_val->was_dropped = FALSE;
482           apr_hash_set(rb->pb->renumber_history, rr_key,
483                        sizeof(*rr_key), rr_val);
484           rb->pb->last_live_revision = rb->rev_actual;
485         }
486
487       if (! rb->pb->quiet)
488         SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
489                                     _("Revision %ld committed as %ld.\n"),
490                                     rb->rev_orig, rb->rev_actual));
491     }
492   else
493     {
494       /* We're dropping this revision. */
495       rb->pb->rev_drop_count++;
496       if (rb->pb->do_renumber_revs)
497         {
498           svn_revnum_t *rr_key;
499           struct revmap_t *rr_val;
500           apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
501           rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
502           rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
503           *rr_key = rb->rev_orig;
504           rr_val->rev = rb->pb->last_live_revision;
505           rr_val->was_dropped = TRUE;
506           apr_hash_set(rb->pb->renumber_history, rr_key,
507                        sizeof(*rr_key), rr_val);
508         }
509
510       if (! rb->pb->quiet)
511         SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
512                                     _("Revision %ld skipped.\n"),
513                                     rb->rev_orig));
514     }
515   svn_pool_destroy(subpool);
516   return SVN_NO_ERROR;
517 }
518
519
520 /* UUID record here: dump it, as we do not filter them. */
521 static svn_error_t *
522 uuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
523 {
524   struct parse_baton_t *pb = parse_baton;
525   SVN_ERR(svn_stream_printf(pb->out_stream, pool,
526                             SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
527   return SVN_NO_ERROR;
528 }
529
530
531 /* New node here. Set up node_baton by copying headers. */
532 static svn_error_t *
533 new_node_record(void **node_baton,
534                 apr_hash_t *headers,
535                 void *rev_baton,
536                 apr_pool_t *pool)
537 {
538   struct parse_baton_t *pb;
539   struct node_baton_t *nb;
540   char *node_path, *copyfrom_path;
541   apr_hash_index_t *hi;
542   const char *tcl;
543
544   *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
545   nb          = *node_baton;
546   nb->rb      = rev_baton;
547   pb          = nb->rb->pb;
548
549   node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH);
550   copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH);
551
552   /* Ensure that paths start with a leading '/'. */
553   if (node_path[0] != '/')
554     node_path = apr_pstrcat(pool, "/", node_path, (char *)NULL);
555   if (copyfrom_path && copyfrom_path[0] != '/')
556     copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, (char *)NULL);
557
558   nb->do_skip = skip_path(node_path, pb->prefixes,
559                           pb->do_exclude, pb->glob);
560
561   /* If we're skipping the node, take note of path, discarding the
562      rest.  */
563   if (nb->do_skip)
564     {
565       svn_hash_sets(pb->dropped_nodes,
566                     apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
567                                 node_path),
568                     (void *)1);
569       nb->rb->had_dropped_nodes = TRUE;
570     }
571   else
572     {
573       tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
574
575       /* Test if this node was copied from dropped source. */
576       if (copyfrom_path &&
577           skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
578         {
579           /* This node was copied from a dropped source.
580              We have a problem, since we did not want to drop this node too.
581
582              However, there is one special case we'll handle.  If the node is
583              a file, and this was a copy-and-modify operation, then the
584              dumpfile should contain the new contents of the file.  In this
585              scenario, we'll just do an add without history using the new
586              contents.  */
587           const char *kind;
588           kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
589
590           /* If there is a Text-content-length header, and the kind is
591              "file", we just fallback to an add without history. */
592           if (tcl && (strcmp(kind, "file") == 0))
593             {
594               svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
595                             NULL);
596               svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
597                             NULL);
598               copyfrom_path = NULL;
599             }
600           /* Else, this is either a directory or a file whose contents we
601              don't have readily available.  */
602           else
603             {
604               return svn_error_createf
605                 (SVN_ERR_INCOMPLETE_DATA, 0,
606                  _("Invalid copy source path '%s'"), copyfrom_path);
607             }
608         }
609
610       nb->has_props = FALSE;
611       nb->has_text = FALSE;
612       nb->has_prop_delta = FALSE;
613       nb->has_text_delta = FALSE;
614       nb->writing_begun = FALSE;
615       nb->tcl = tcl ? svn__atoui64(tcl) : 0;
616       nb->header = svn_stringbuf_create_empty(pool);
617       nb->props = svn_stringbuf_create_empty(pool);
618       nb->node_path = apr_pstrdup(pool, node_path);
619
620       /* Now we know for sure that we have a node that will not be
621          skipped, flush the revision if it has not already been done. */
622       nb->rb->has_nodes = TRUE;
623       if (! nb->rb->writing_begun)
624         SVN_ERR(output_revision(nb->rb));
625
626       for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
627         {
628           const char *key = svn__apr_hash_index_key(hi);
629           const char *val = svn__apr_hash_index_val(hi);
630
631           if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
632               && (!strcmp(val, "true")))
633             nb->has_prop_delta = TRUE;
634
635           if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
636               && (!strcmp(val, "true")))
637             nb->has_text_delta = TRUE;
638
639           if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
640               || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
641               || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH)))
642             continue;
643
644           /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
645              The number points to some revision in the past. We keep track
646              of revision renumbering in an apr_hash, which maps original
647              revisions to new ones. Dropped revision are mapped to -1.
648              This should never happen here.
649           */
650           if (pb->do_renumber_revs
651               && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
652             {
653               svn_revnum_t cf_orig_rev;
654               struct revmap_t *cf_renum_val;
655
656               cf_orig_rev = SVN_STR_TO_REV(val);
657               cf_renum_val = apr_hash_get(pb->renumber_history,
658                                           &cf_orig_rev,
659                                           sizeof(svn_revnum_t));
660               if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
661                 return svn_error_createf
662                   (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
663                    _("No valid copyfrom revision in filtered stream"));
664               SVN_ERR(svn_stream_printf
665                       (nb->rb->pb->out_stream, pool,
666                        SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV ": %ld\n",
667                        cf_renum_val->rev));
668               continue;
669             }
670
671           /* passthru: put header straight to output */
672
673           SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
674                                     pool, "%s: %s\n",
675                                     key, val));
676         }
677     }
678
679   return SVN_NO_ERROR;
680 }
681
682
683 /* Output node header and props to dumpstream
684    This will be called by set_fulltext() after setting nb->has_text to TRUE,
685    if the node has any text, or by close_node() otherwise. This must only
686    be called if nb->writing_begun is FALSE. */
687 static svn_error_t *
688 output_node(struct node_baton_t *nb)
689 {
690   int bytes_used;
691   char buf[SVN_KEYLINE_MAXLEN];
692
693   nb->writing_begun = TRUE;
694
695   /* when there are no props nb->props->len would be zero and won't mess up
696      Content-Length. */
697   if (nb->has_props)
698     svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
699
700   /* 1. recalculate & check text-md5 if present. Passed through right now. */
701
702   /* 2. recalculate and add content-lengths */
703
704   if (nb->has_props)
705     {
706       svn_stringbuf_appendcstr(nb->header,
707                                SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
708       bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
709                                 nb->props->len);
710       svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
711       svn_stringbuf_appendbyte(nb->header, '\n');
712     }
713   if (nb->has_text)
714     {
715       svn_stringbuf_appendcstr(nb->header,
716                                SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
717       bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
718                                 nb->tcl);
719       svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
720       svn_stringbuf_appendbyte(nb->header, '\n');
721     }
722   svn_stringbuf_appendcstr(nb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
723   bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
724                             (svn_filesize_t) (nb->props->len + nb->tcl));
725   svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
726   svn_stringbuf_appendbyte(nb->header, '\n');
727
728   /* put an end to headers */
729   svn_stringbuf_appendbyte(nb->header, '\n');
730
731   /* 3. output all the stuff */
732
733   SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
734                            nb->header->data , &(nb->header->len)));
735   SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
736                            nb->props->data , &(nb->props->len)));
737
738   return SVN_NO_ERROR;
739 }
740
741
742 /* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
743    sources or renumbering revisions in rangelists as appropriate, and
744    return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
745    POOL). */
746 static svn_error_t *
747 adjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
748                  struct revision_baton_t *rb, apr_pool_t *pool)
749 {
750   apr_hash_t *mergeinfo;
751   apr_hash_t *final_mergeinfo = apr_hash_make(pool);
752   apr_hash_index_t *hi;
753   apr_pool_t *subpool = svn_pool_create(pool);
754
755   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
756
757   /* Issue #3020: If we are skipping missing merge sources, then also
758      filter mergeinfo ranges as old or older than the oldest revision in the
759      dump stream.  Those older than the oldest obviously refer to history
760      outside of the dump stream.  The oldest rev itself is present in the
761      dump, but cannot be a valid merge source revision since it is the
762      start of all history.  E.g. if we dump -r100:400 then dumpfilter the
763      result with --skip-missing-merge-sources, any mergeinfo with revision
764      100 implies a change of -r99:100, but r99 is part of the history we
765      want filtered.  This is analogous to how r1 is always meaningless as
766      a merge source revision.
767
768      If the oldest rev is r0 then there is nothing to filter. */
769   if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
770     SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
771       &mergeinfo, mergeinfo,
772       rb->pb->oldest_original_rev, 0,
773       FALSE, subpool, subpool));
774
775   for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
776     {
777       const char *merge_source = svn__apr_hash_index_key(hi);
778       svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
779       struct parse_baton_t *pb = rb->pb;
780
781       /* Determine whether the merge_source is a part of the prefix. */
782       if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
783         {
784           if (pb->skip_missing_merge_sources)
785             continue;
786           else
787             return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
788                                      _("Missing merge source path '%s'; try "
789                                        "with --skip-missing-merge-sources"),
790                                      merge_source);
791         }
792
793       /* Possibly renumber revisions in merge source's rangelist. */
794       if (pb->do_renumber_revs)
795         {
796           int i;
797
798           for (i = 0; i < rangelist->nelts; i++)
799             {
800               struct revmap_t *revmap_start;
801               struct revmap_t *revmap_end;
802               svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
803                                                        svn_merge_range_t *);
804
805               revmap_start = apr_hash_get(pb->renumber_history,
806                                           &range->start, sizeof(svn_revnum_t));
807               if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
808                 return svn_error_createf
809                   (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
810                    _("No valid revision range 'start' in filtered stream"));
811
812               revmap_end = apr_hash_get(pb->renumber_history,
813                                         &range->end, sizeof(svn_revnum_t));
814               if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
815                 return svn_error_createf
816                   (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
817                    _("No valid revision range 'end' in filtered stream"));
818
819               range->start = revmap_start->rev;
820               range->end = revmap_end->rev;
821             }
822         }
823       svn_hash_sets(final_mergeinfo, merge_source, rangelist);
824     }
825
826   SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
827   SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
828   svn_pool_destroy(subpool);
829
830   return SVN_NO_ERROR;
831 }
832
833
834 static svn_error_t *
835 set_revision_property(void *revision_baton,
836                       const char *name,
837                       const svn_string_t *value)
838 {
839   struct revision_baton_t *rb = revision_baton;
840   apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
841
842   rb->has_props = TRUE;
843   svn_hash_sets(rb->props,
844                 apr_pstrdup(hash_pool, name),
845                 svn_string_dup(value, hash_pool));
846   return SVN_NO_ERROR;
847 }
848
849
850 static svn_error_t *
851 set_node_property(void *node_baton,
852                   const char *name,
853                   const svn_string_t *value)
854 {
855   struct node_baton_t *nb = node_baton;
856   struct revision_baton_t *rb = nb->rb;
857
858   if (nb->do_skip)
859     return SVN_NO_ERROR;
860
861   if (! (nb->has_props || nb->has_prop_delta))
862     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
863                              _("Delta property block detected, but deltas "
864                                "are not enabled for node '%s' in original "
865                                "revision %ld"),
866                              nb->node_path, rb->rev_orig);
867
868   if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
869     {
870       svn_string_t *filtered_mergeinfo;  /* Avoid compiler warning. */
871       apr_pool_t *pool = apr_hash_pool_get(rb->props);
872       SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
873       value = filtered_mergeinfo;
874     }
875
876   nb->has_props = TRUE;
877   write_prop_to_stringbuf(nb->props, name, value);
878
879   return SVN_NO_ERROR;
880 }
881
882
883 static svn_error_t *
884 delete_node_property(void *node_baton, const char *name)
885 {
886   struct node_baton_t *nb = node_baton;
887   struct revision_baton_t *rb = nb->rb;
888
889   if (nb->do_skip)
890     return SVN_NO_ERROR;
891
892   if (!nb->has_prop_delta)
893     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
894                              _("Delta property block detected, but deltas "
895                                "are not enabled for node '%s' in original "
896                                "revision %ld"),
897                              nb->node_path, rb->rev_orig);
898
899   nb->has_props = TRUE;
900   write_propdel_to_stringbuf(&(nb->props), name);
901
902   return SVN_NO_ERROR;
903 }
904
905
906 static svn_error_t *
907 remove_node_props(void *node_baton)
908 {
909   struct node_baton_t *nb = node_baton;
910
911   /* In this case, not actually indicating that the node *has* props,
912      rather that we know about all the props that it has, since it now
913      has none. */
914   nb->has_props = TRUE;
915
916   return SVN_NO_ERROR;
917 }
918
919
920 static svn_error_t *
921 set_fulltext(svn_stream_t **stream, void *node_baton)
922 {
923   struct node_baton_t *nb = node_baton;
924
925   if (!nb->do_skip)
926     {
927       nb->has_text = TRUE;
928       if (! nb->writing_begun)
929         SVN_ERR(output_node(nb));
930       *stream = nb->rb->pb->out_stream;
931     }
932
933   return SVN_NO_ERROR;
934 }
935
936
937 /* Finalize node */
938 static svn_error_t *
939 close_node(void *node_baton)
940 {
941   struct node_baton_t *nb = node_baton;
942   apr_size_t len = 2;
943
944   /* Get out of here if we can. */
945   if (nb->do_skip)
946     return SVN_NO_ERROR;
947
948   /* If the node was not flushed already to output its text, do it now. */
949   if (! nb->writing_begun)
950     SVN_ERR(output_node(nb));
951
952   /* put an end to node. */
953   SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
954
955   return SVN_NO_ERROR;
956 }
957
958
959 /* Finalize revision */
960 static svn_error_t *
961 close_revision(void *revision_baton)
962 {
963   struct revision_baton_t *rb = revision_baton;
964
965   /* If no node has yet flushed the revision, do it now. */
966   if (! rb->writing_begun)
967     return output_revision(rb);
968   else
969     return SVN_NO_ERROR;
970 }
971
972
973 /* Filtering vtable */
974 svn_repos_parse_fns3_t filtering_vtable =
975   {
976     magic_header_record,
977     uuid_record,
978     new_revision_record,
979     new_node_record,
980     set_revision_property,
981     set_node_property,
982     delete_node_property,
983     remove_node_props,
984     set_fulltext,
985     NULL,
986     close_node,
987     close_revision
988   };
989
990
991 \f
992 /** Subcommands. **/
993
994 static svn_opt_subcommand_t
995   subcommand_help,
996   subcommand_exclude,
997   subcommand_include;
998
999 enum
1000   {
1001     svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
1002     svndumpfilter__drop_all_empty_revs,
1003     svndumpfilter__renumber_revs,
1004     svndumpfilter__preserve_revprops,
1005     svndumpfilter__skip_missing_merge_sources,
1006     svndumpfilter__targets,
1007     svndumpfilter__quiet,
1008     svndumpfilter__glob,
1009     svndumpfilter__version
1010   };
1011
1012 /* Option codes and descriptions.
1013  *
1014  * The entire list must be terminated with an entry of nulls.
1015  */
1016 static const apr_getopt_option_t options_table[] =
1017   {
1018     {"help",          'h', 0,
1019      N_("show help on a subcommand")},
1020
1021     {NULL,            '?', 0,
1022      N_("show help on a subcommand")},
1023
1024     {"version",            svndumpfilter__version, 0,
1025      N_("show program version information") },
1026     {"quiet",              svndumpfilter__quiet, 0,
1027      N_("Do not display filtering statistics.") },
1028     {"pattern",            svndumpfilter__glob, 0,
1029      N_("Treat the path prefixes as file glob patterns.") },
1030     {"drop-empty-revs",    svndumpfilter__drop_empty_revs, 0,
1031      N_("Remove revisions emptied by filtering.")},
1032     {"drop-all-empty-revs",    svndumpfilter__drop_all_empty_revs, 0,
1033      N_("Remove all empty revisions found in dumpstream\n"
1034         "                             except revision 0.")},
1035     {"renumber-revs",      svndumpfilter__renumber_revs, 0,
1036      N_("Renumber revisions left after filtering.") },
1037     {"skip-missing-merge-sources",
1038      svndumpfilter__skip_missing_merge_sources, 0,
1039      N_("Skip missing merge sources.") },
1040     {"preserve-revprops",  svndumpfilter__preserve_revprops, 0,
1041      N_("Don't filter revision properties.") },
1042     {"targets", svndumpfilter__targets, 1,
1043      N_("Read additional prefixes, one per line, from\n"
1044         "                             file ARG.")},
1045     {NULL}
1046   };
1047
1048
1049 /* Array of available subcommands.
1050  * The entire list must be terminated with an entry of nulls.
1051  */
1052 static const svn_opt_subcommand_desc2_t cmd_table[] =
1053   {
1054     {"exclude", subcommand_exclude, {0},
1055      N_("Filter out nodes with given prefixes from dumpstream.\n"
1056         "usage: svndumpfilter exclude PATH_PREFIX...\n"),
1057      {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1058       svndumpfilter__renumber_revs,
1059       svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1060       svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1061       svndumpfilter__glob} },
1062
1063     {"include", subcommand_include, {0},
1064      N_("Filter out nodes without given prefixes from dumpstream.\n"
1065         "usage: svndumpfilter include PATH_PREFIX...\n"),
1066      {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1067       svndumpfilter__renumber_revs,
1068       svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1069       svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1070       svndumpfilter__glob} },
1071
1072     {"help", subcommand_help, {"?", "h"},
1073      N_("Describe the usage of this program or its subcommands.\n"
1074         "usage: svndumpfilter help [SUBCOMMAND...]\n"),
1075      {0} },
1076
1077     { NULL, NULL, {0}, NULL, {0} }
1078   };
1079
1080
1081 /* Baton for passing option/argument state to a subcommand function. */
1082 struct svndumpfilter_opt_state
1083 {
1084   svn_opt_revision_t start_revision;     /* -r X[:Y] is         */
1085   svn_opt_revision_t end_revision;       /* not implemented.    */
1086   svn_boolean_t quiet;                   /* --quiet             */
1087   svn_boolean_t glob;                    /* --pattern           */
1088   svn_boolean_t version;                 /* --version           */
1089   svn_boolean_t drop_empty_revs;         /* --drop-empty-revs   */
1090   svn_boolean_t drop_all_empty_revs;     /* --drop-all-empty-revs */
1091   svn_boolean_t help;                    /* --help or -?        */
1092   svn_boolean_t renumber_revs;           /* --renumber-revs     */
1093   svn_boolean_t preserve_revprops;       /* --preserve-revprops */
1094   svn_boolean_t skip_missing_merge_sources;
1095                                          /* --skip-missing-merge-sources */
1096   const char *targets_file;              /* --targets-file       */
1097   apr_array_header_t *prefixes;          /* mainargs.           */
1098 };
1099
1100
1101 static svn_error_t *
1102 parse_baton_initialize(struct parse_baton_t **pb,
1103                        struct svndumpfilter_opt_state *opt_state,
1104                        svn_boolean_t do_exclude,
1105                        apr_pool_t *pool)
1106 {
1107   struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1108
1109   /* Read the stream from STDIN.  Users can redirect a file. */
1110   SVN_ERR(create_stdio_stream(&(baton->in_stream),
1111                               apr_file_open_stdin, pool));
1112
1113   /* Have the parser dump results to STDOUT. Users can redirect a file. */
1114   SVN_ERR(create_stdio_stream(&(baton->out_stream),
1115                               apr_file_open_stdout, pool));
1116
1117   baton->do_exclude = do_exclude;
1118
1119   /* Ignore --renumber-revs if there can't possibly be
1120      anything to renumber. */
1121   baton->do_renumber_revs =
1122     (opt_state->renumber_revs && (opt_state->drop_empty_revs
1123                                   || opt_state->drop_all_empty_revs));
1124
1125   baton->drop_empty_revs = opt_state->drop_empty_revs;
1126   baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
1127   baton->preserve_revprops = opt_state->preserve_revprops;
1128   baton->quiet = opt_state->quiet;
1129   baton->glob = opt_state->glob;
1130   baton->prefixes = opt_state->prefixes;
1131   baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
1132   baton->rev_drop_count = 0; /* used to shift revnums while filtering */
1133   baton->dropped_nodes = apr_hash_make(pool);
1134   baton->renumber_history = apr_hash_make(pool);
1135   baton->last_live_revision = SVN_INVALID_REVNUM;
1136   baton->oldest_original_rev = SVN_INVALID_REVNUM;
1137   baton->allow_deltas = FALSE;
1138
1139   *pb = baton;
1140   return SVN_NO_ERROR;
1141 }
1142
1143 /* This implements `help` subcommand. */
1144 static svn_error_t *
1145 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1146 {
1147   struct svndumpfilter_opt_state *opt_state = baton;
1148   const char *header =
1149     _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
1150       "Type 'svndumpfilter help <subcommand>' for help on a "
1151       "specific subcommand.\n"
1152       "Type 'svndumpfilter --version' to see the program version.\n"
1153       "\n"
1154       "Available subcommands:\n");
1155
1156   SVN_ERR(svn_opt_print_help4(os, "svndumpfilter",
1157                               opt_state ? opt_state->version : FALSE,
1158                               opt_state ? opt_state->quiet : FALSE,
1159                               /*###opt_state ? opt_state->verbose :*/ FALSE,
1160                               NULL, header, cmd_table, options_table,
1161                               NULL, NULL, pool));
1162
1163   return SVN_NO_ERROR;
1164 }
1165
1166
1167 /* Version compatibility check */
1168 static svn_error_t *
1169 check_lib_versions(void)
1170 {
1171   static const svn_version_checklist_t checklist[] =
1172     {
1173       { "svn_subr",  svn_subr_version },
1174       { "svn_repos", svn_repos_version },
1175       { "svn_delta", svn_delta_version },
1176       { NULL, NULL }
1177     };
1178   SVN_VERSION_DEFINE(my_version);
1179
1180   return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1181 }
1182
1183
1184 /* Do the real work of filtering. */
1185 static svn_error_t *
1186 do_filter(apr_getopt_t *os,
1187           void *baton,
1188           svn_boolean_t do_exclude,
1189           apr_pool_t *pool)
1190 {
1191   struct svndumpfilter_opt_state *opt_state = baton;
1192   struct parse_baton_t *pb;
1193   apr_hash_index_t *hi;
1194   apr_array_header_t *keys;
1195   int i, num_keys;
1196
1197   if (! opt_state->quiet)
1198     {
1199       apr_pool_t *subpool = svn_pool_create(pool);
1200
1201       if (opt_state->glob)
1202         {
1203           SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1204                                       do_exclude
1205                                       ? (opt_state->drop_empty_revs
1206                                          || opt_state->drop_all_empty_revs)
1207                                         ? _("Excluding (and dropping empty "
1208                                             "revisions for) prefix patterns:\n")
1209                                         : _("Excluding prefix patterns:\n")
1210                                       : (opt_state->drop_empty_revs
1211                                          || opt_state->drop_all_empty_revs)
1212                                         ? _("Including (and dropping empty "
1213                                             "revisions for) prefix patterns:\n")
1214                                         : _("Including prefix patterns:\n")));
1215         }
1216       else
1217         {
1218           SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1219                                       do_exclude
1220                                       ? (opt_state->drop_empty_revs
1221                                          || opt_state->drop_all_empty_revs)
1222                                         ? _("Excluding (and dropping empty "
1223                                             "revisions for) prefixes:\n")
1224                                         : _("Excluding prefixes:\n")
1225                                       : (opt_state->drop_empty_revs
1226                                          || opt_state->drop_all_empty_revs)
1227                                         ? _("Including (and dropping empty "
1228                                             "revisions for) prefixes:\n")
1229                                         : _("Including prefixes:\n")));
1230         }
1231
1232       for (i = 0; i < opt_state->prefixes->nelts; i++)
1233         {
1234           svn_pool_clear(subpool);
1235           SVN_ERR(svn_cmdline_fprintf
1236                   (stderr, subpool, "   '%s'\n",
1237                    APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
1238         }
1239
1240       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1241       svn_pool_destroy(subpool);
1242     }
1243
1244   SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
1245   SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
1246                                       TRUE, NULL, NULL, pool));
1247
1248   /* The rest of this is just reporting.  If we aren't reporting, get
1249      outta here. */
1250   if (opt_state->quiet)
1251     return SVN_NO_ERROR;
1252
1253   SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1254
1255   if (pb->rev_drop_count)
1256     SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1257                                 Q_("Dropped %d revision.\n\n",
1258                                    "Dropped %d revisions.\n\n",
1259                                    pb->rev_drop_count),
1260                                 pb->rev_drop_count));
1261
1262   if (pb->do_renumber_revs)
1263     {
1264       apr_pool_t *subpool = svn_pool_create(pool);
1265       SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1266                                 stderr, subpool));
1267
1268       /* Get the keys of the hash, sort them, then print the hash keys
1269          and values, sorted by keys. */
1270       num_keys = apr_hash_count(pb->renumber_history);
1271       keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
1272       for (hi = apr_hash_first(pool, pb->renumber_history);
1273            hi;
1274            hi = apr_hash_next(hi))
1275         {
1276           const svn_revnum_t *revnum = svn__apr_hash_index_key(hi);
1277
1278           APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1279         }
1280       qsort(keys->elts, keys->nelts,
1281             keys->elt_size, svn_sort_compare_revisions);
1282       for (i = 0; i < keys->nelts; i++)
1283         {
1284           svn_revnum_t this_key;
1285           struct revmap_t *this_val;
1286
1287           svn_pool_clear(subpool);
1288           this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
1289           this_val = apr_hash_get(pb->renumber_history, &this_key,
1290                                   sizeof(this_key));
1291           if (this_val->was_dropped)
1292             SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1293                                         _("   %ld => (dropped)\n"),
1294                                         this_key));
1295           else
1296             SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1297                                         "   %ld => %ld\n",
1298                                         this_key, this_val->rev));
1299         }
1300       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1301       svn_pool_destroy(subpool);
1302     }
1303
1304   if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1305     {
1306       apr_pool_t *subpool = svn_pool_create(pool);
1307       SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1308                                   Q_("Dropped %d node:\n",
1309                                      "Dropped %d nodes:\n",
1310                                      num_keys),
1311                                   num_keys));
1312
1313       /* Get the keys of the hash, sort them, then print the hash keys
1314          and values, sorted by keys. */
1315       keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
1316       for (hi = apr_hash_first(pool, pb->dropped_nodes);
1317            hi;
1318            hi = apr_hash_next(hi))
1319         {
1320           const char *path = svn__apr_hash_index_key(hi);
1321
1322           APR_ARRAY_PUSH(keys, const char *) = path;
1323         }
1324       qsort(keys->elts, keys->nelts, keys->elt_size, svn_sort_compare_paths);
1325       for (i = 0; i < keys->nelts; i++)
1326         {
1327           svn_pool_clear(subpool);
1328           SVN_ERR(svn_cmdline_fprintf
1329                   (stderr, subpool, "   '%s'\n",
1330                    (const char *)APR_ARRAY_IDX(keys, i, const char *)));
1331         }
1332       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1333       svn_pool_destroy(subpool);
1334     }
1335
1336   return SVN_NO_ERROR;
1337 }
1338
1339 /* This implements `exclude' subcommand. */
1340 static svn_error_t *
1341 subcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1342 {
1343   return do_filter(os, baton, TRUE, pool);
1344 }
1345
1346
1347 /* This implements `include` subcommand. */
1348 static svn_error_t *
1349 subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1350 {
1351   return do_filter(os, baton, FALSE, pool);
1352 }
1353
1354
1355 \f
1356 /** Main. **/
1357
1358 int
1359 main(int argc, const char *argv[])
1360 {
1361   svn_error_t *err;
1362   apr_status_t apr_err;
1363   apr_pool_t *pool;
1364
1365   const svn_opt_subcommand_desc2_t *subcommand = NULL;
1366   struct svndumpfilter_opt_state opt_state;
1367   apr_getopt_t *os;
1368   int opt_id;
1369   apr_array_header_t *received_opts;
1370   int i;
1371
1372
1373   /* Initialize the app. */
1374   if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1375     return EXIT_FAILURE;
1376
1377   /* Create our top-level pool.  Use a separate mutexless allocator,
1378    * given this application is single threaded.
1379    */
1380   pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1381
1382   /* Check library versions */
1383   err = check_lib_versions();
1384   if (err)
1385     return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1386
1387   received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1388
1389   /* Initialize the FS library. */
1390   err = svn_fs_initialize(pool);
1391   if (err)
1392     return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1393
1394   if (argc <= 1)
1395     {
1396       SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1397       svn_pool_destroy(pool);
1398       return EXIT_FAILURE;
1399     }
1400
1401   /* Initialize opt_state. */
1402   memset(&opt_state, 0, sizeof(opt_state));
1403   opt_state.start_revision.kind = svn_opt_revision_unspecified;
1404   opt_state.end_revision.kind = svn_opt_revision_unspecified;
1405
1406   /* Parse options. */
1407   err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1408   if (err)
1409     return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1410
1411   os->interleave = 1;
1412   while (1)
1413     {
1414       const char *opt_arg;
1415
1416       /* Parse the next option. */
1417       apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1418       if (APR_STATUS_IS_EOF(apr_err))
1419         break;
1420       else if (apr_err)
1421         {
1422           SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1423           svn_pool_destroy(pool);
1424           return EXIT_FAILURE;
1425         }
1426
1427       /* Stash the option code in an array before parsing it. */
1428       APR_ARRAY_PUSH(received_opts, int) = opt_id;
1429
1430       switch (opt_id)
1431         {
1432         case 'h':
1433         case '?':
1434           opt_state.help = TRUE;
1435           break;
1436         case svndumpfilter__version:
1437           opt_state.version = TRUE;
1438           break;
1439         case svndumpfilter__quiet:
1440           opt_state.quiet = TRUE;
1441           break;
1442         case svndumpfilter__glob:
1443           opt_state.glob = TRUE;
1444           break;
1445         case svndumpfilter__drop_empty_revs:
1446           opt_state.drop_empty_revs = TRUE;
1447           break;
1448         case svndumpfilter__drop_all_empty_revs:
1449           opt_state.drop_all_empty_revs = TRUE;
1450           break;
1451         case svndumpfilter__renumber_revs:
1452           opt_state.renumber_revs = TRUE;
1453           break;
1454         case svndumpfilter__preserve_revprops:
1455           opt_state.preserve_revprops = TRUE;
1456           break;
1457         case svndumpfilter__skip_missing_merge_sources:
1458           opt_state.skip_missing_merge_sources = TRUE;
1459           break;
1460         case svndumpfilter__targets:
1461           opt_state.targets_file = opt_arg;
1462           break;
1463         default:
1464           {
1465             SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1466             svn_pool_destroy(pool);
1467             return EXIT_FAILURE;
1468           }
1469         }  /* close `switch' */
1470     }  /* close `while' */
1471
1472   /* Disallow simultaneous use of both --drop-empty-revs and
1473      --drop-all-empty-revs. */
1474   if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
1475     {
1476       err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
1477                              _("--drop-empty-revs cannot be used with "
1478                                "--drop-all-empty-revs"));
1479       return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1480     }
1481
1482   /* If the user asked for help, then the rest of the arguments are
1483      the names of subcommands to get help on (if any), or else they're
1484      just typos/mistakes.  Whatever the case, the subcommand to
1485      actually run is subcommand_help(). */
1486   if (opt_state.help)
1487     subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
1488
1489   /* If we're not running the `help' subcommand, then look for a
1490      subcommand in the first argument. */
1491   if (subcommand == NULL)
1492     {
1493       if (os->ind >= os->argc)
1494         {
1495           if (opt_state.version)
1496             {
1497               /* Use the "help" subcommand to handle the "--version" option. */
1498               static const svn_opt_subcommand_desc2_t pseudo_cmd =
1499                 { "--version", subcommand_help, {0}, "",
1500                   {svndumpfilter__version,  /* must accept its own option */
1501                    svndumpfilter__quiet,
1502                   } };
1503
1504               subcommand = &pseudo_cmd;
1505             }
1506           else
1507             {
1508               svn_error_clear(svn_cmdline_fprintf
1509                               (stderr, pool,
1510                                _("Subcommand argument required\n")));
1511               SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1512               svn_pool_destroy(pool);
1513               return EXIT_FAILURE;
1514             }
1515         }
1516       else
1517         {
1518           const char *first_arg = os->argv[os->ind++];
1519           subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
1520           if (subcommand == NULL)
1521             {
1522               const char* first_arg_utf8;
1523               if ((err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
1524                                                  pool)))
1525                 return svn_cmdline_handle_exit_error(err, pool,
1526                                                      "svndumpfilter: ");
1527
1528               svn_error_clear(
1529                 svn_cmdline_fprintf(stderr, pool,
1530                                     _("Unknown subcommand: '%s'\n"),
1531                                     first_arg_utf8));
1532               SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1533               svn_pool_destroy(pool);
1534               return EXIT_FAILURE;
1535             }
1536         }
1537     }
1538
1539   /* If there's a second argument, it's probably [one of] prefixes.
1540      Every subcommand except `help' requires at least one, so we parse
1541      them out here and store in opt_state. */
1542
1543   if (subcommand->cmd_func != subcommand_help)
1544     {
1545
1546       opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
1547                                           sizeof(const char *));
1548       for (i = os->ind ; i< os->argc; i++)
1549         {
1550           const char *prefix;
1551
1552           /* Ensure that each prefix is UTF8-encoded, in internal
1553              style, and absolute. */
1554           SVN_INT_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
1555           prefix = svn_relpath__internal_style(prefix, pool);
1556           if (prefix[0] != '/')
1557             prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
1558           APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1559         }
1560
1561       if (opt_state.targets_file)
1562         {
1563           svn_stringbuf_t *buffer, *buffer_utf8;
1564           const char *utf8_targets_file;
1565           apr_array_header_t *targets = apr_array_make(pool, 0,
1566                                                        sizeof(const char *));
1567
1568           /* We need to convert to UTF-8 now, even before we divide
1569              the targets into an array, because otherwise we wouldn't
1570              know what delimiter to use for svn_cstring_split().  */
1571
1572           SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_targets_file,
1573                                               opt_state.targets_file, pool));
1574
1575           SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_targets_file,
1576                                                pool));
1577           SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1578
1579           targets = apr_array_append(pool,
1580                          svn_cstring_split(buffer_utf8->data, "\n\r",
1581                                            TRUE, pool),
1582                          targets);
1583
1584           for (i = 0; i < targets->nelts; i++)
1585             {
1586               const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
1587               if (prefix[0] != '/')
1588                 prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
1589               APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1590             }
1591         }
1592
1593       if (apr_is_empty_array(opt_state.prefixes))
1594         {
1595           svn_error_clear(svn_cmdline_fprintf
1596                           (stderr, pool,
1597                            _("\nError: no prefixes supplied.\n")));
1598           svn_pool_destroy(pool);
1599           return EXIT_FAILURE;
1600         }
1601     }
1602
1603
1604   /* Check that the subcommand wasn't passed any inappropriate options. */
1605   for (i = 0; i < received_opts->nelts; i++)
1606     {
1607       opt_id = APR_ARRAY_IDX(received_opts, i, int);
1608
1609       /* All commands implicitly accept --help, so just skip over this
1610          when we see it. Note that we don't want to include this option
1611          in their "accepted options" list because it would be awfully
1612          redundant to display it in every commands' help text. */
1613       if (opt_id == 'h' || opt_id == '?')
1614         continue;
1615
1616       if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1617         {
1618           const char *optstr;
1619           const apr_getopt_option_t *badopt =
1620             svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
1621                                           pool);
1622           svn_opt_format_option(&optstr, badopt, FALSE, pool);
1623           if (subcommand->name[0] == '-')
1624             SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1625           else
1626             svn_error_clear(svn_cmdline_fprintf
1627                             (stderr, pool,
1628                              _("Subcommand '%s' doesn't accept option '%s'\n"
1629                                "Type 'svndumpfilter help %s' for usage.\n"),
1630                              subcommand->name, optstr, subcommand->name));
1631           svn_pool_destroy(pool);
1632           return EXIT_FAILURE;
1633         }
1634     }
1635
1636   /* Run the subcommand. */
1637   err = (*subcommand->cmd_func)(os, &opt_state, pool);
1638   if (err)
1639     {
1640       /* For argument-related problems, suggest using the 'help'
1641          subcommand. */
1642       if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1643           || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1644         {
1645           err = svn_error_quick_wrap(err,
1646                                      _("Try 'svndumpfilter help' for more "
1647                                        "info"));
1648         }
1649       return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1650     }
1651   else
1652     {
1653       svn_pool_destroy(pool);
1654
1655       /* Flush stdout, making sure the user will see any print errors. */
1656       SVN_INT_ERR(svn_cmdline_fflush(stdout));
1657       return EXIT_SUCCESS;
1658     }
1659 }