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