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