svndumpfilter.c revision 251881
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
50typedef apr_status_t (__stdcall *open_fn_t)(apr_file_t **, apr_pool_t *);
51#else
52typedef 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*/
69static svn_error_t *
70create_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. */
86static void
87write_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. */
119static void
120write_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. */
144static svn_boolean_t
145ary_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. */
169static APR_INLINE svn_boolean_t
170skip_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
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
201struct 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
207struct 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
235struct 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
259struct 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
292
293/* Filtering vtable members */
294
295/* File-format stamp. */
296static svn_error_t *
297magic_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. */
313static svn_error_t *
314new_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. */
374static svn_error_t *
375output_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. */
520static svn_error_t *
521uuid_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. */
531static svn_error_t *
532new_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. */
686static svn_error_t *
687output_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). */
745static svn_error_t *
746adjust_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
833static svn_error_t *
834set_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
849static svn_error_t *
850set_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
882static svn_error_t *
883delete_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
905static svn_error_t *
906remove_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
919static svn_error_t *
920set_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 */
937static svn_error_t *
938close_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 */
959static svn_error_t *
960close_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 */
973svn_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
991/** Subcommands. **/
992
993static svn_opt_subcommand_t
994  subcommand_help,
995  subcommand_exclude,
996  subcommand_include;
997
998enum
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 */
1015static 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 */
1051static 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. */
1081struct 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
1100static svn_error_t *
1101parse_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. */
1143static svn_error_t *
1144subcommand_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 */
1167static svn_error_t *
1168check_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. */
1184static svn_error_t *
1185do_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. */
1339static svn_error_t *
1340subcommand_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. */
1347static svn_error_t *
1348subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1349{
1350  return do_filter(os, baton, FALSE, pool);
1351}
1352
1353
1354
1355/** Main. **/
1356
1357int
1358main(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}
1659