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