]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/svndumpfilter/svndumpfilter.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.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       const char *kind;
574       const char *action;
575
576       tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
577
578       /* Test if this node was copied from dropped source. */
579       if (copyfrom_path &&
580           skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
581         {
582           /* This node was copied from a dropped source.
583              We have a problem, since we did not want to drop this node too.
584
585              However, there is one special case we'll handle.  If the node is
586              a file, and this was a copy-and-modify operation, then the
587              dumpfile should contain the new contents of the file.  In this
588              scenario, we'll just do an add without history using the new
589              contents.  */
590           kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
591
592           /* If there is a Text-content-length header, and the kind is
593              "file", we just fallback to an add without history. */
594           if (tcl && (strcmp(kind, "file") == 0))
595             {
596               svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
597                             NULL);
598               svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
599                             NULL);
600               copyfrom_path = NULL;
601             }
602           /* Else, this is either a directory or a file whose contents we
603              don't have readily available.  */
604           else
605             {
606               return svn_error_createf
607                 (SVN_ERR_INCOMPLETE_DATA, 0,
608                  _("Invalid copy source path '%s'"), copyfrom_path);
609             }
610         }
611
612       nb->has_props = FALSE;
613       nb->has_text = FALSE;
614       nb->has_prop_delta = FALSE;
615       nb->has_text_delta = FALSE;
616       nb->writing_begun = FALSE;
617       nb->tcl = tcl ? svn__atoui64(tcl) : 0;
618       nb->header = svn_stringbuf_create_empty(pool);
619       nb->props = svn_stringbuf_create_empty(pool);
620       nb->node_path = apr_pstrdup(pool, node_path);
621
622       /* Now we know for sure that we have a node that will not be
623          skipped, flush the revision if it has not already been done. */
624       nb->rb->has_nodes = TRUE;
625       if (! nb->rb->writing_begun)
626         SVN_ERR(output_revision(nb->rb));
627
628       /* A node record is required to begin with 'Node-path', skip the
629          leading '/' to match the form used by 'svnadmin dump'. */
630       SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
631                                 pool, "%s: %s\n",
632                                 SVN_REPOS_DUMPFILE_NODE_PATH, node_path + 1));
633
634       /* Node-kind is next and is optional. */
635       kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
636       if (kind)
637         SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
638                                   pool, "%s: %s\n",
639                                   SVN_REPOS_DUMPFILE_NODE_KIND, kind));
640
641       /* Node-action is next and required. */
642       action = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION);
643       if (action)
644         SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
645                                   pool, "%s: %s\n",
646                                   SVN_REPOS_DUMPFILE_NODE_ACTION, action));
647       else
648         return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
649                                  _("Missing Node-action for path '%s'"),
650                                  node_path);
651
652       for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
653         {
654           const char *key = svn__apr_hash_index_key(hi);
655           const char *val = svn__apr_hash_index_val(hi);
656
657           if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
658               && (!strcmp(val, "true")))
659             nb->has_prop_delta = TRUE;
660
661           if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
662               && (!strcmp(val, "true")))
663             nb->has_text_delta = TRUE;
664
665           if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
666               || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
667               || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH))
668               || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_PATH))
669               || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_KIND))
670               || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_ACTION)))
671             continue;
672
673           /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
674              The number points to some revision in the past. We keep track
675              of revision renumbering in an apr_hash, which maps original
676              revisions to new ones. Dropped revision are mapped to -1.
677              This should never happen here.
678           */
679           if (pb->do_renumber_revs
680               && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
681             {
682               svn_revnum_t cf_orig_rev;
683               struct revmap_t *cf_renum_val;
684
685               cf_orig_rev = SVN_STR_TO_REV(val);
686               cf_renum_val = apr_hash_get(pb->renumber_history,
687                                           &cf_orig_rev,
688                                           sizeof(svn_revnum_t));
689               if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
690                 return svn_error_createf
691                   (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
692                    _("No valid copyfrom revision in filtered stream"));
693               SVN_ERR(svn_stream_printf
694                       (nb->rb->pb->out_stream, pool,
695                        SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV ": %ld\n",
696                        cf_renum_val->rev));
697               continue;
698             }
699
700           /* passthru: put header straight to output */
701
702           SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
703                                     pool, "%s: %s\n",
704                                     key, val));
705         }
706     }
707
708   return SVN_NO_ERROR;
709 }
710
711
712 /* Output node header and props to dumpstream
713    This will be called by set_fulltext() after setting nb->has_text to TRUE,
714    if the node has any text, or by close_node() otherwise. This must only
715    be called if nb->writing_begun is FALSE. */
716 static svn_error_t *
717 output_node(struct node_baton_t *nb)
718 {
719   int bytes_used;
720   char buf[SVN_KEYLINE_MAXLEN];
721
722   nb->writing_begun = TRUE;
723
724   /* when there are no props nb->props->len would be zero and won't mess up
725      Content-Length. */
726   if (nb->has_props)
727     svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
728
729   /* 1. recalculate & check text-md5 if present. Passed through right now. */
730
731   /* 2. recalculate and add content-lengths */
732
733   if (nb->has_props)
734     {
735       svn_stringbuf_appendcstr(nb->header,
736                                SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
737       bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
738                                 nb->props->len);
739       svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
740       svn_stringbuf_appendbyte(nb->header, '\n');
741     }
742   if (nb->has_text)
743     {
744       svn_stringbuf_appendcstr(nb->header,
745                                SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
746       bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
747                                 nb->tcl);
748       svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
749       svn_stringbuf_appendbyte(nb->header, '\n');
750     }
751   svn_stringbuf_appendcstr(nb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
752   bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
753                             (svn_filesize_t) (nb->props->len + nb->tcl));
754   svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
755   svn_stringbuf_appendbyte(nb->header, '\n');
756
757   /* put an end to headers */
758   svn_stringbuf_appendbyte(nb->header, '\n');
759
760   /* 3. output all the stuff */
761
762   SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
763                            nb->header->data , &(nb->header->len)));
764   SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
765                            nb->props->data , &(nb->props->len)));
766
767   return SVN_NO_ERROR;
768 }
769
770
771 /* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
772    sources or renumbering revisions in rangelists as appropriate, and
773    return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
774    POOL). */
775 static svn_error_t *
776 adjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
777                  struct revision_baton_t *rb, apr_pool_t *pool)
778 {
779   apr_hash_t *mergeinfo;
780   apr_hash_t *final_mergeinfo = apr_hash_make(pool);
781   apr_hash_index_t *hi;
782   apr_pool_t *subpool = svn_pool_create(pool);
783
784   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
785
786   /* Issue #3020: If we are skipping missing merge sources, then also
787      filter mergeinfo ranges as old or older than the oldest revision in the
788      dump stream.  Those older than the oldest obviously refer to history
789      outside of the dump stream.  The oldest rev itself is present in the
790      dump, but cannot be a valid merge source revision since it is the
791      start of all history.  E.g. if we dump -r100:400 then dumpfilter the
792      result with --skip-missing-merge-sources, any mergeinfo with revision
793      100 implies a change of -r99:100, but r99 is part of the history we
794      want filtered.
795
796      If the oldest rev is r0 then there is nothing to filter. */
797   if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
798     SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
799       &mergeinfo, mergeinfo,
800       rb->pb->oldest_original_rev, 0,
801       FALSE, subpool, subpool));
802
803   for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
804     {
805       const char *merge_source = svn__apr_hash_index_key(hi);
806       svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
807       struct parse_baton_t *pb = rb->pb;
808
809       /* Determine whether the merge_source is a part of the prefix. */
810       if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
811         {
812           if (pb->skip_missing_merge_sources)
813             continue;
814           else
815             return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
816                                      _("Missing merge source path '%s'; try "
817                                        "with --skip-missing-merge-sources"),
818                                      merge_source);
819         }
820
821       /* Possibly renumber revisions in merge source's rangelist. */
822       if (pb->do_renumber_revs)
823         {
824           int i;
825
826           for (i = 0; i < rangelist->nelts; i++)
827             {
828               struct revmap_t *revmap_start;
829               struct revmap_t *revmap_end;
830               svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
831                                                        svn_merge_range_t *);
832
833               revmap_start = apr_hash_get(pb->renumber_history,
834                                           &range->start, sizeof(svn_revnum_t));
835               if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
836                 return svn_error_createf
837                   (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
838                    _("No valid revision range 'start' in filtered stream"));
839
840               revmap_end = apr_hash_get(pb->renumber_history,
841                                         &range->end, sizeof(svn_revnum_t));
842               if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
843                 return svn_error_createf
844                   (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
845                    _("No valid revision range 'end' in filtered stream"));
846
847               range->start = revmap_start->rev;
848               range->end = revmap_end->rev;
849             }
850         }
851       svn_hash_sets(final_mergeinfo, merge_source, rangelist);
852     }
853
854   SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
855   SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
856   svn_pool_destroy(subpool);
857
858   return SVN_NO_ERROR;
859 }
860
861
862 static svn_error_t *
863 set_revision_property(void *revision_baton,
864                       const char *name,
865                       const svn_string_t *value)
866 {
867   struct revision_baton_t *rb = revision_baton;
868   apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
869
870   rb->has_props = TRUE;
871   svn_hash_sets(rb->props,
872                 apr_pstrdup(hash_pool, name),
873                 svn_string_dup(value, hash_pool));
874   return SVN_NO_ERROR;
875 }
876
877
878 static svn_error_t *
879 set_node_property(void *node_baton,
880                   const char *name,
881                   const svn_string_t *value)
882 {
883   struct node_baton_t *nb = node_baton;
884   struct revision_baton_t *rb = nb->rb;
885
886   if (nb->do_skip)
887     return SVN_NO_ERROR;
888
889   if (! (nb->has_props || nb->has_prop_delta))
890     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
891                              _("Delta property block detected, but deltas "
892                                "are not enabled for node '%s' in original "
893                                "revision %ld"),
894                              nb->node_path, rb->rev_orig);
895
896   if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
897     {
898       svn_string_t *filtered_mergeinfo;  /* Avoid compiler warning. */
899       apr_pool_t *pool = apr_hash_pool_get(rb->props);
900       SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
901       value = filtered_mergeinfo;
902     }
903
904   nb->has_props = TRUE;
905   write_prop_to_stringbuf(nb->props, name, value);
906
907   return SVN_NO_ERROR;
908 }
909
910
911 static svn_error_t *
912 delete_node_property(void *node_baton, const char *name)
913 {
914   struct node_baton_t *nb = node_baton;
915   struct revision_baton_t *rb = nb->rb;
916
917   if (nb->do_skip)
918     return SVN_NO_ERROR;
919
920   if (!nb->has_prop_delta)
921     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
922                              _("Delta property block detected, but deltas "
923                                "are not enabled for node '%s' in original "
924                                "revision %ld"),
925                              nb->node_path, rb->rev_orig);
926
927   nb->has_props = TRUE;
928   write_propdel_to_stringbuf(&(nb->props), name);
929
930   return SVN_NO_ERROR;
931 }
932
933
934 static svn_error_t *
935 remove_node_props(void *node_baton)
936 {
937   struct node_baton_t *nb = node_baton;
938
939   /* In this case, not actually indicating that the node *has* props,
940      rather that we know about all the props that it has, since it now
941      has none. */
942   nb->has_props = TRUE;
943
944   return SVN_NO_ERROR;
945 }
946
947
948 static svn_error_t *
949 set_fulltext(svn_stream_t **stream, void *node_baton)
950 {
951   struct node_baton_t *nb = node_baton;
952
953   if (!nb->do_skip)
954     {
955       nb->has_text = TRUE;
956       if (! nb->writing_begun)
957         SVN_ERR(output_node(nb));
958       *stream = nb->rb->pb->out_stream;
959     }
960
961   return SVN_NO_ERROR;
962 }
963
964
965 /* Finalize node */
966 static svn_error_t *
967 close_node(void *node_baton)
968 {
969   struct node_baton_t *nb = node_baton;
970   apr_size_t len = 2;
971
972   /* Get out of here if we can. */
973   if (nb->do_skip)
974     return SVN_NO_ERROR;
975
976   /* If the node was not flushed already to output its text, do it now. */
977   if (! nb->writing_begun)
978     SVN_ERR(output_node(nb));
979
980   /* put an end to node. */
981   SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
982
983   return SVN_NO_ERROR;
984 }
985
986
987 /* Finalize revision */
988 static svn_error_t *
989 close_revision(void *revision_baton)
990 {
991   struct revision_baton_t *rb = revision_baton;
992
993   /* If no node has yet flushed the revision, do it now. */
994   if (! rb->writing_begun)
995     return output_revision(rb);
996   else
997     return SVN_NO_ERROR;
998 }
999
1000
1001 /* Filtering vtable */
1002 svn_repos_parse_fns3_t filtering_vtable =
1003   {
1004     magic_header_record,
1005     uuid_record,
1006     new_revision_record,
1007     new_node_record,
1008     set_revision_property,
1009     set_node_property,
1010     delete_node_property,
1011     remove_node_props,
1012     set_fulltext,
1013     NULL,
1014     close_node,
1015     close_revision
1016   };
1017
1018
1019 \f
1020 /** Subcommands. **/
1021
1022 static svn_opt_subcommand_t
1023   subcommand_help,
1024   subcommand_exclude,
1025   subcommand_include;
1026
1027 enum
1028   {
1029     svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
1030     svndumpfilter__drop_all_empty_revs,
1031     svndumpfilter__renumber_revs,
1032     svndumpfilter__preserve_revprops,
1033     svndumpfilter__skip_missing_merge_sources,
1034     svndumpfilter__targets,
1035     svndumpfilter__quiet,
1036     svndumpfilter__glob,
1037     svndumpfilter__version
1038   };
1039
1040 /* Option codes and descriptions.
1041  *
1042  * The entire list must be terminated with an entry of nulls.
1043  */
1044 static const apr_getopt_option_t options_table[] =
1045   {
1046     {"help",          'h', 0,
1047      N_("show help on a subcommand")},
1048
1049     {NULL,            '?', 0,
1050      N_("show help on a subcommand")},
1051
1052     {"version",            svndumpfilter__version, 0,
1053      N_("show program version information") },
1054     {"quiet",              svndumpfilter__quiet, 0,
1055      N_("Do not display filtering statistics.") },
1056     {"pattern",            svndumpfilter__glob, 0,
1057      N_("Treat the path prefixes as file glob patterns.") },
1058     {"drop-empty-revs",    svndumpfilter__drop_empty_revs, 0,
1059      N_("Remove revisions emptied by filtering.")},
1060     {"drop-all-empty-revs",    svndumpfilter__drop_all_empty_revs, 0,
1061      N_("Remove all empty revisions found in dumpstream\n"
1062         "                             except revision 0.")},
1063     {"renumber-revs",      svndumpfilter__renumber_revs, 0,
1064      N_("Renumber revisions left after filtering.") },
1065     {"skip-missing-merge-sources",
1066      svndumpfilter__skip_missing_merge_sources, 0,
1067      N_("Skip missing merge sources.") },
1068     {"preserve-revprops",  svndumpfilter__preserve_revprops, 0,
1069      N_("Don't filter revision properties.") },
1070     {"targets", svndumpfilter__targets, 1,
1071      N_("Read additional prefixes, one per line, from\n"
1072         "                             file ARG.")},
1073     {NULL}
1074   };
1075
1076
1077 /* Array of available subcommands.
1078  * The entire list must be terminated with an entry of nulls.
1079  */
1080 static const svn_opt_subcommand_desc2_t cmd_table[] =
1081   {
1082     {"exclude", subcommand_exclude, {0},
1083      N_("Filter out nodes with given prefixes from dumpstream.\n"
1084         "usage: svndumpfilter exclude PATH_PREFIX...\n"),
1085      {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1086       svndumpfilter__renumber_revs,
1087       svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1088       svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1089       svndumpfilter__glob} },
1090
1091     {"include", subcommand_include, {0},
1092      N_("Filter out nodes without given prefixes from dumpstream.\n"
1093         "usage: svndumpfilter include PATH_PREFIX...\n"),
1094      {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1095       svndumpfilter__renumber_revs,
1096       svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1097       svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1098       svndumpfilter__glob} },
1099
1100     {"help", subcommand_help, {"?", "h"},
1101      N_("Describe the usage of this program or its subcommands.\n"
1102         "usage: svndumpfilter help [SUBCOMMAND...]\n"),
1103      {0} },
1104
1105     { NULL, NULL, {0}, NULL, {0} }
1106   };
1107
1108
1109 /* Baton for passing option/argument state to a subcommand function. */
1110 struct svndumpfilter_opt_state
1111 {
1112   svn_opt_revision_t start_revision;     /* -r X[:Y] is         */
1113   svn_opt_revision_t end_revision;       /* not implemented.    */
1114   svn_boolean_t quiet;                   /* --quiet             */
1115   svn_boolean_t glob;                    /* --pattern           */
1116   svn_boolean_t version;                 /* --version           */
1117   svn_boolean_t drop_empty_revs;         /* --drop-empty-revs   */
1118   svn_boolean_t drop_all_empty_revs;     /* --drop-all-empty-revs */
1119   svn_boolean_t help;                    /* --help or -?        */
1120   svn_boolean_t renumber_revs;           /* --renumber-revs     */
1121   svn_boolean_t preserve_revprops;       /* --preserve-revprops */
1122   svn_boolean_t skip_missing_merge_sources;
1123                                          /* --skip-missing-merge-sources */
1124   const char *targets_file;              /* --targets-file       */
1125   apr_array_header_t *prefixes;          /* mainargs.           */
1126 };
1127
1128
1129 static svn_error_t *
1130 parse_baton_initialize(struct parse_baton_t **pb,
1131                        struct svndumpfilter_opt_state *opt_state,
1132                        svn_boolean_t do_exclude,
1133                        apr_pool_t *pool)
1134 {
1135   struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1136
1137   /* Read the stream from STDIN.  Users can redirect a file. */
1138   SVN_ERR(create_stdio_stream(&(baton->in_stream),
1139                               apr_file_open_stdin, pool));
1140
1141   /* Have the parser dump results to STDOUT. Users can redirect a file. */
1142   SVN_ERR(create_stdio_stream(&(baton->out_stream),
1143                               apr_file_open_stdout, pool));
1144
1145   baton->do_exclude = do_exclude;
1146
1147   /* Ignore --renumber-revs if there can't possibly be
1148      anything to renumber. */
1149   baton->do_renumber_revs =
1150     (opt_state->renumber_revs && (opt_state->drop_empty_revs
1151                                   || opt_state->drop_all_empty_revs));
1152
1153   baton->drop_empty_revs = opt_state->drop_empty_revs;
1154   baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
1155   baton->preserve_revprops = opt_state->preserve_revprops;
1156   baton->quiet = opt_state->quiet;
1157   baton->glob = opt_state->glob;
1158   baton->prefixes = opt_state->prefixes;
1159   baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
1160   baton->rev_drop_count = 0; /* used to shift revnums while filtering */
1161   baton->dropped_nodes = apr_hash_make(pool);
1162   baton->renumber_history = apr_hash_make(pool);
1163   baton->last_live_revision = SVN_INVALID_REVNUM;
1164   baton->oldest_original_rev = SVN_INVALID_REVNUM;
1165   baton->allow_deltas = FALSE;
1166
1167   *pb = baton;
1168   return SVN_NO_ERROR;
1169 }
1170
1171 /* This implements `help` subcommand. */
1172 static svn_error_t *
1173 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1174 {
1175   struct svndumpfilter_opt_state *opt_state = baton;
1176   const char *header =
1177     _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
1178       "Type 'svndumpfilter help <subcommand>' for help on a "
1179       "specific subcommand.\n"
1180       "Type 'svndumpfilter --version' to see the program version.\n"
1181       "\n"
1182       "Available subcommands:\n");
1183
1184   SVN_ERR(svn_opt_print_help4(os, "svndumpfilter",
1185                               opt_state ? opt_state->version : FALSE,
1186                               opt_state ? opt_state->quiet : FALSE,
1187                               /*###opt_state ? opt_state->verbose :*/ FALSE,
1188                               NULL, header, cmd_table, options_table,
1189                               NULL, NULL, pool));
1190
1191   return SVN_NO_ERROR;
1192 }
1193
1194
1195 /* Version compatibility check */
1196 static svn_error_t *
1197 check_lib_versions(void)
1198 {
1199   static const svn_version_checklist_t checklist[] =
1200     {
1201       { "svn_subr",  svn_subr_version },
1202       { "svn_repos", svn_repos_version },
1203       { "svn_delta", svn_delta_version },
1204       { NULL, NULL }
1205     };
1206   SVN_VERSION_DEFINE(my_version);
1207
1208   return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1209 }
1210
1211
1212 /* Do the real work of filtering. */
1213 static svn_error_t *
1214 do_filter(apr_getopt_t *os,
1215           void *baton,
1216           svn_boolean_t do_exclude,
1217           apr_pool_t *pool)
1218 {
1219   struct svndumpfilter_opt_state *opt_state = baton;
1220   struct parse_baton_t *pb;
1221   apr_hash_index_t *hi;
1222   apr_array_header_t *keys;
1223   int i, num_keys;
1224
1225   if (! opt_state->quiet)
1226     {
1227       apr_pool_t *subpool = svn_pool_create(pool);
1228
1229       if (opt_state->glob)
1230         {
1231           SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1232                                       do_exclude
1233                                       ? (opt_state->drop_empty_revs
1234                                          || opt_state->drop_all_empty_revs)
1235                                         ? _("Excluding (and dropping empty "
1236                                             "revisions for) prefix patterns:\n")
1237                                         : _("Excluding prefix patterns:\n")
1238                                       : (opt_state->drop_empty_revs
1239                                          || opt_state->drop_all_empty_revs)
1240                                         ? _("Including (and dropping empty "
1241                                             "revisions for) prefix patterns:\n")
1242                                         : _("Including prefix patterns:\n")));
1243         }
1244       else
1245         {
1246           SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1247                                       do_exclude
1248                                       ? (opt_state->drop_empty_revs
1249                                          || opt_state->drop_all_empty_revs)
1250                                         ? _("Excluding (and dropping empty "
1251                                             "revisions for) prefixes:\n")
1252                                         : _("Excluding prefixes:\n")
1253                                       : (opt_state->drop_empty_revs
1254                                          || opt_state->drop_all_empty_revs)
1255                                         ? _("Including (and dropping empty "
1256                                             "revisions for) prefixes:\n")
1257                                         : _("Including prefixes:\n")));
1258         }
1259
1260       for (i = 0; i < opt_state->prefixes->nelts; i++)
1261         {
1262           svn_pool_clear(subpool);
1263           SVN_ERR(svn_cmdline_fprintf
1264                   (stderr, subpool, "   '%s'\n",
1265                    APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
1266         }
1267
1268       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1269       svn_pool_destroy(subpool);
1270     }
1271
1272   SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
1273   SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
1274                                       TRUE, NULL, NULL, pool));
1275
1276   /* The rest of this is just reporting.  If we aren't reporting, get
1277      outta here. */
1278   if (opt_state->quiet)
1279     return SVN_NO_ERROR;
1280
1281   SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1282
1283   if (pb->rev_drop_count)
1284     SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1285                                 Q_("Dropped %d revision.\n\n",
1286                                    "Dropped %d revisions.\n\n",
1287                                    pb->rev_drop_count),
1288                                 pb->rev_drop_count));
1289
1290   if (pb->do_renumber_revs)
1291     {
1292       apr_pool_t *subpool = svn_pool_create(pool);
1293       SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1294                                 stderr, subpool));
1295
1296       /* Get the keys of the hash, sort them, then print the hash keys
1297          and values, sorted by keys. */
1298       num_keys = apr_hash_count(pb->renumber_history);
1299       keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
1300       for (hi = apr_hash_first(pool, pb->renumber_history);
1301            hi;
1302            hi = apr_hash_next(hi))
1303         {
1304           const svn_revnum_t *revnum = svn__apr_hash_index_key(hi);
1305
1306           APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1307         }
1308       qsort(keys->elts, keys->nelts,
1309             keys->elt_size, svn_sort_compare_revisions);
1310       for (i = 0; i < keys->nelts; i++)
1311         {
1312           svn_revnum_t this_key;
1313           struct revmap_t *this_val;
1314
1315           svn_pool_clear(subpool);
1316           this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
1317           this_val = apr_hash_get(pb->renumber_history, &this_key,
1318                                   sizeof(this_key));
1319           if (this_val->was_dropped)
1320             SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1321                                         _("   %ld => (dropped)\n"),
1322                                         this_key));
1323           else
1324             SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1325                                         "   %ld => %ld\n",
1326                                         this_key, this_val->rev));
1327         }
1328       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1329       svn_pool_destroy(subpool);
1330     }
1331
1332   if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1333     {
1334       apr_pool_t *subpool = svn_pool_create(pool);
1335       SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1336                                   Q_("Dropped %d node:\n",
1337                                      "Dropped %d nodes:\n",
1338                                      num_keys),
1339                                   num_keys));
1340
1341       /* Get the keys of the hash, sort them, then print the hash keys
1342          and values, sorted by keys. */
1343       keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
1344       for (hi = apr_hash_first(pool, pb->dropped_nodes);
1345            hi;
1346            hi = apr_hash_next(hi))
1347         {
1348           const char *path = svn__apr_hash_index_key(hi);
1349
1350           APR_ARRAY_PUSH(keys, const char *) = path;
1351         }
1352       qsort(keys->elts, keys->nelts, keys->elt_size, svn_sort_compare_paths);
1353       for (i = 0; i < keys->nelts; i++)
1354         {
1355           svn_pool_clear(subpool);
1356           SVN_ERR(svn_cmdline_fprintf
1357                   (stderr, subpool, "   '%s'\n",
1358                    (const char *)APR_ARRAY_IDX(keys, i, const char *)));
1359         }
1360       SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1361       svn_pool_destroy(subpool);
1362     }
1363
1364   return SVN_NO_ERROR;
1365 }
1366
1367 /* This implements `exclude' subcommand. */
1368 static svn_error_t *
1369 subcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1370 {
1371   return do_filter(os, baton, TRUE, pool);
1372 }
1373
1374
1375 /* This implements `include` subcommand. */
1376 static svn_error_t *
1377 subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1378 {
1379   return do_filter(os, baton, FALSE, pool);
1380 }
1381
1382
1383 \f
1384 /** Main. **/
1385
1386 int
1387 main(int argc, const char *argv[])
1388 {
1389   svn_error_t *err;
1390   apr_status_t apr_err;
1391   apr_pool_t *pool;
1392
1393   const svn_opt_subcommand_desc2_t *subcommand = NULL;
1394   struct svndumpfilter_opt_state opt_state;
1395   apr_getopt_t *os;
1396   int opt_id;
1397   apr_array_header_t *received_opts;
1398   int i;
1399
1400
1401   /* Initialize the app. */
1402   if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1403     return EXIT_FAILURE;
1404
1405   /* Create our top-level pool.  Use a separate mutexless allocator,
1406    * given this application is single threaded.
1407    */
1408   pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1409
1410   /* Check library versions */
1411   err = check_lib_versions();
1412   if (err)
1413     return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1414
1415   received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1416
1417   /* Initialize the FS library. */
1418   err = svn_fs_initialize(pool);
1419   if (err)
1420     return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1421
1422   if (argc <= 1)
1423     {
1424       SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1425       svn_pool_destroy(pool);
1426       return EXIT_FAILURE;
1427     }
1428
1429   /* Initialize opt_state. */
1430   memset(&opt_state, 0, sizeof(opt_state));
1431   opt_state.start_revision.kind = svn_opt_revision_unspecified;
1432   opt_state.end_revision.kind = svn_opt_revision_unspecified;
1433
1434   /* Parse options. */
1435   err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1436   if (err)
1437     return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1438
1439   os->interleave = 1;
1440   while (1)
1441     {
1442       const char *opt_arg;
1443
1444       /* Parse the next option. */
1445       apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1446       if (APR_STATUS_IS_EOF(apr_err))
1447         break;
1448       else if (apr_err)
1449         {
1450           SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1451           svn_pool_destroy(pool);
1452           return EXIT_FAILURE;
1453         }
1454
1455       /* Stash the option code in an array before parsing it. */
1456       APR_ARRAY_PUSH(received_opts, int) = opt_id;
1457
1458       switch (opt_id)
1459         {
1460         case 'h':
1461         case '?':
1462           opt_state.help = TRUE;
1463           break;
1464         case svndumpfilter__version:
1465           opt_state.version = TRUE;
1466           break;
1467         case svndumpfilter__quiet:
1468           opt_state.quiet = TRUE;
1469           break;
1470         case svndumpfilter__glob:
1471           opt_state.glob = TRUE;
1472           break;
1473         case svndumpfilter__drop_empty_revs:
1474           opt_state.drop_empty_revs = TRUE;
1475           break;
1476         case svndumpfilter__drop_all_empty_revs:
1477           opt_state.drop_all_empty_revs = TRUE;
1478           break;
1479         case svndumpfilter__renumber_revs:
1480           opt_state.renumber_revs = TRUE;
1481           break;
1482         case svndumpfilter__preserve_revprops:
1483           opt_state.preserve_revprops = TRUE;
1484           break;
1485         case svndumpfilter__skip_missing_merge_sources:
1486           opt_state.skip_missing_merge_sources = TRUE;
1487           break;
1488         case svndumpfilter__targets:
1489           opt_state.targets_file = opt_arg;
1490           break;
1491         default:
1492           {
1493             SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1494             svn_pool_destroy(pool);
1495             return EXIT_FAILURE;
1496           }
1497         }  /* close `switch' */
1498     }  /* close `while' */
1499
1500   /* Disallow simultaneous use of both --drop-empty-revs and
1501      --drop-all-empty-revs. */
1502   if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
1503     {
1504       err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
1505                              _("--drop-empty-revs cannot be used with "
1506                                "--drop-all-empty-revs"));
1507       return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1508     }
1509
1510   /* If the user asked for help, then the rest of the arguments are
1511      the names of subcommands to get help on (if any), or else they're
1512      just typos/mistakes.  Whatever the case, the subcommand to
1513      actually run is subcommand_help(). */
1514   if (opt_state.help)
1515     subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
1516
1517   /* If we're not running the `help' subcommand, then look for a
1518      subcommand in the first argument. */
1519   if (subcommand == NULL)
1520     {
1521       if (os->ind >= os->argc)
1522         {
1523           if (opt_state.version)
1524             {
1525               /* Use the "help" subcommand to handle the "--version" option. */
1526               static const svn_opt_subcommand_desc2_t pseudo_cmd =
1527                 { "--version", subcommand_help, {0}, "",
1528                   {svndumpfilter__version,  /* must accept its own option */
1529                    svndumpfilter__quiet,
1530                   } };
1531
1532               subcommand = &pseudo_cmd;
1533             }
1534           else
1535             {
1536               svn_error_clear(svn_cmdline_fprintf
1537                               (stderr, pool,
1538                                _("Subcommand argument required\n")));
1539               SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1540               svn_pool_destroy(pool);
1541               return EXIT_FAILURE;
1542             }
1543         }
1544       else
1545         {
1546           const char *first_arg = os->argv[os->ind++];
1547           subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
1548           if (subcommand == NULL)
1549             {
1550               const char* first_arg_utf8;
1551               if ((err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
1552                                                  pool)))
1553                 return svn_cmdline_handle_exit_error(err, pool,
1554                                                      "svndumpfilter: ");
1555
1556               svn_error_clear(
1557                 svn_cmdline_fprintf(stderr, pool,
1558                                     _("Unknown subcommand: '%s'\n"),
1559                                     first_arg_utf8));
1560               SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1561               svn_pool_destroy(pool);
1562               return EXIT_FAILURE;
1563             }
1564         }
1565     }
1566
1567   /* If there's a second argument, it's probably [one of] prefixes.
1568      Every subcommand except `help' requires at least one, so we parse
1569      them out here and store in opt_state. */
1570
1571   if (subcommand->cmd_func != subcommand_help)
1572     {
1573
1574       opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
1575                                           sizeof(const char *));
1576       for (i = os->ind ; i< os->argc; i++)
1577         {
1578           const char *prefix;
1579
1580           /* Ensure that each prefix is UTF8-encoded, in internal
1581              style, and absolute. */
1582           SVN_INT_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
1583           prefix = svn_relpath__internal_style(prefix, pool);
1584           if (prefix[0] != '/')
1585             prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
1586           APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1587         }
1588
1589       if (opt_state.targets_file)
1590         {
1591           svn_stringbuf_t *buffer, *buffer_utf8;
1592           const char *utf8_targets_file;
1593           apr_array_header_t *targets = apr_array_make(pool, 0,
1594                                                        sizeof(const char *));
1595
1596           /* We need to convert to UTF-8 now, even before we divide
1597              the targets into an array, because otherwise we wouldn't
1598              know what delimiter to use for svn_cstring_split().  */
1599
1600           SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_targets_file,
1601                                               opt_state.targets_file, pool));
1602
1603           SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_targets_file,
1604                                                pool));
1605           SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1606
1607           targets = apr_array_append(pool,
1608                          svn_cstring_split(buffer_utf8->data, "\n\r",
1609                                            TRUE, pool),
1610                          targets);
1611
1612           for (i = 0; i < targets->nelts; i++)
1613             {
1614               const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
1615               if (prefix[0] != '/')
1616                 prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
1617               APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1618             }
1619         }
1620
1621       if (apr_is_empty_array(opt_state.prefixes))
1622         {
1623           svn_error_clear(svn_cmdline_fprintf
1624                           (stderr, pool,
1625                            _("\nError: no prefixes supplied.\n")));
1626           svn_pool_destroy(pool);
1627           return EXIT_FAILURE;
1628         }
1629     }
1630
1631
1632   /* Check that the subcommand wasn't passed any inappropriate options. */
1633   for (i = 0; i < received_opts->nelts; i++)
1634     {
1635       opt_id = APR_ARRAY_IDX(received_opts, i, int);
1636
1637       /* All commands implicitly accept --help, so just skip over this
1638          when we see it. Note that we don't want to include this option
1639          in their "accepted options" list because it would be awfully
1640          redundant to display it in every commands' help text. */
1641       if (opt_id == 'h' || opt_id == '?')
1642         continue;
1643
1644       if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1645         {
1646           const char *optstr;
1647           const apr_getopt_option_t *badopt =
1648             svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
1649                                           pool);
1650           svn_opt_format_option(&optstr, badopt, FALSE, pool);
1651           if (subcommand->name[0] == '-')
1652             SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1653           else
1654             svn_error_clear(svn_cmdline_fprintf
1655                             (stderr, pool,
1656                              _("Subcommand '%s' doesn't accept option '%s'\n"
1657                                "Type 'svndumpfilter help %s' for usage.\n"),
1658                              subcommand->name, optstr, subcommand->name));
1659           svn_pool_destroy(pool);
1660           return EXIT_FAILURE;
1661         }
1662     }
1663
1664   /* Run the subcommand. */
1665   err = (*subcommand->cmd_func)(os, &opt_state, pool);
1666   if (err)
1667     {
1668       /* For argument-related problems, suggest using the 'help'
1669          subcommand. */
1670       if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1671           || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1672         {
1673           err = svn_error_quick_wrap(err,
1674                                      _("Try 'svndumpfilter help' for more "
1675                                        "info"));
1676         }
1677       return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1678     }
1679   else
1680     {
1681       svn_pool_destroy(pool);
1682
1683       /* Flush stdout, making sure the user will see any print errors. */
1684       SVN_INT_ERR(svn_cmdline_fflush(stdout));
1685       return EXIT_SUCCESS;
1686     }
1687 }