svndumpfilter.c revision 289180
1251881Speter/*
2251881Speter * svndumpfilter.c: Subversion dump stream filtering tool main file.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter#include <stdlib.h>
26251881Speter
27251881Speter#include <apr_file_io.h>
28251881Speter
29251881Speter#include "svn_private_config.h"
30251881Speter#include "svn_cmdline.h"
31251881Speter#include "svn_error.h"
32251881Speter#include "svn_string.h"
33251881Speter#include "svn_opt.h"
34251881Speter#include "svn_utf.h"
35251881Speter#include "svn_dirent_uri.h"
36251881Speter#include "svn_path.h"
37251881Speter#include "svn_hash.h"
38251881Speter#include "svn_repos.h"
39251881Speter#include "svn_fs.h"
40251881Speter#include "svn_pools.h"
41251881Speter#include "svn_sorts.h"
42251881Speter#include "svn_props.h"
43251881Speter#include "svn_mergeinfo.h"
44251881Speter#include "svn_version.h"
45251881Speter
46289180Speter#include "private/svn_repos_private.h"
47251881Speter#include "private/svn_mergeinfo_private.h"
48251881Speter#include "private/svn_cmdline_private.h"
49289180Speter#include "private/svn_sorts_private.h"
50251881Speter
51251881Speter#ifdef _WIN32
52251881Spetertypedef apr_status_t (__stdcall *open_fn_t)(apr_file_t **, apr_pool_t *);
53251881Speter#else
54251881Spetertypedef apr_status_t (*open_fn_t)(apr_file_t **, apr_pool_t *);
55251881Speter#endif
56251881Speter
57251881Speter/*** Code. ***/
58251881Speter
59251881Speter/* Helper to open stdio streams */
60251881Speter
61251881Speter/* NOTE: we used to call svn_stream_from_stdio(), which wraps a stream
62251881Speter   around a standard stdio.h FILE pointer.  The problem is that these
63251881Speter   pointers operate through C Run Time (CRT) on Win32, which does all
64251881Speter   sorts of translation on them: LF's become CRLF's, and ctrl-Z's
65251881Speter   embedded in Word documents are interpreted as premature EOF's.
66251881Speter
67251881Speter   So instead, we use apr_file_open_std*, which bypass the CRT and
68251881Speter   directly wrap the OS's file-handles, which don't know or care about
69251881Speter   translation.  Thus dump/load works correctly on Win32.
70251881Speter*/
71251881Speterstatic svn_error_t *
72251881Spetercreate_stdio_stream(svn_stream_t **stream,
73251881Speter                    open_fn_t open_fn,
74251881Speter                    apr_pool_t *pool)
75251881Speter{
76251881Speter  apr_file_t *stdio_file;
77251881Speter  apr_status_t apr_err = open_fn(&stdio_file, pool);
78251881Speter
79251881Speter  if (apr_err)
80251881Speter    return svn_error_wrap_apr(apr_err, _("Can't open stdio file"));
81251881Speter
82251881Speter  *stream = svn_stream_from_aprfile2(stdio_file, TRUE, pool);
83251881Speter  return SVN_NO_ERROR;
84251881Speter}
85251881Speter
86251881Speter
87251881Speter/* Writes a property in dumpfile format to given stringbuf. */
88251881Speterstatic void
89251881Speterwrite_prop_to_stringbuf(svn_stringbuf_t *strbuf,
90251881Speter                        const char *name,
91251881Speter                        const svn_string_t *value)
92251881Speter{
93251881Speter  int bytes_used;
94251881Speter  size_t namelen;
95251881Speter  char buf[SVN_KEYLINE_MAXLEN];
96251881Speter
97251881Speter  /* Output name length, then name. */
98251881Speter  namelen = strlen(name);
99251881Speter  svn_stringbuf_appendbytes(strbuf, "K ", 2);
100251881Speter
101251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
102251881Speter  svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
103251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
104251881Speter
105251881Speter  svn_stringbuf_appendbytes(strbuf, name, namelen);
106251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
107251881Speter
108251881Speter  /* Output value length, then value. */
109251881Speter  svn_stringbuf_appendbytes(strbuf, "V ", 2);
110251881Speter
111251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len);
112251881Speter  svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
113251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
114251881Speter
115251881Speter  svn_stringbuf_appendbytes(strbuf, value->data, value->len);
116251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
117251881Speter}
118251881Speter
119251881Speter
120251881Speter/* Writes a property deletion in dumpfile format to given stringbuf. */
121251881Speterstatic void
122251881Speterwrite_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
123251881Speter                           const char *name)
124251881Speter{
125251881Speter  int bytes_used;
126251881Speter  size_t namelen;
127251881Speter  char buf[SVN_KEYLINE_MAXLEN];
128251881Speter
129251881Speter  /* Output name length, then name. */
130251881Speter  namelen = strlen(name);
131251881Speter  svn_stringbuf_appendbytes(*strbuf, "D ", 2);
132251881Speter
133251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
134251881Speter  svn_stringbuf_appendbytes(*strbuf, buf, bytes_used);
135251881Speter  svn_stringbuf_appendbyte(*strbuf, '\n');
136251881Speter
137251881Speter  svn_stringbuf_appendbytes(*strbuf, name, namelen);
138251881Speter  svn_stringbuf_appendbyte(*strbuf, '\n');
139251881Speter}
140251881Speter
141251881Speter
142251881Speter/* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
143251881Speter * Return TRUE if any prefix is a prefix of PATH (matching whole path
144251881Speter * components); FALSE otherwise.
145251881Speter * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
146251881Speterstatic svn_boolean_t
147251881Speterary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
148251881Speter{
149251881Speter  int i;
150251881Speter  size_t path_len = strlen(path);
151251881Speter
152251881Speter  for (i = 0; i < pfxlist->nelts; i++)
153251881Speter    {
154251881Speter      const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
155251881Speter      size_t pfx_len = strlen(pfx);
156251881Speter
157251881Speter      if (path_len < pfx_len)
158251881Speter        continue;
159251881Speter      if (strncmp(path, pfx, pfx_len) == 0
160251881Speter          && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
161251881Speter        return TRUE;
162251881Speter    }
163251881Speter
164251881Speter  return FALSE;
165251881Speter}
166251881Speter
167251881Speter
168251881Speter/* Check whether we need to skip this PATH based on its presence in
169251881Speter   the PREFIXES list, and the DO_EXCLUDE option.
170251881Speter   PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
171251881Speterstatic APR_INLINE svn_boolean_t
172251881Speterskip_path(const char *path, const apr_array_header_t *prefixes,
173251881Speter          svn_boolean_t do_exclude, svn_boolean_t glob)
174251881Speter{
175251881Speter  const svn_boolean_t matches =
176251881Speter    (glob
177251881Speter     ? svn_cstring_match_glob_list(path, prefixes)
178251881Speter     : ary_prefix_match(prefixes, path));
179251881Speter
180251881Speter  /* NXOR */
181251881Speter  return (matches ? do_exclude : !do_exclude);
182251881Speter}
183251881Speter
184251881Speter
185251881Speter
186251881Speter/* Note: the input stream parser calls us with events.
187251881Speter   Output of the filtered dump occurs for the most part streamily with the
188251881Speter   event callbacks, to avoid caching large quantities of data in memory.
189251881Speter   The exceptions this are:
190251881Speter   - All revision data (headers and props) must be cached until a non-skipped
191251881Speter     node within the revision is found, or the revision is closed.
192251881Speter   - Node headers and props must be cached until all props have been received
193251881Speter     (to allow the Prop-content-length to be found). This is signalled either
194251881Speter     by the node text arriving, or the node being closed.
195251881Speter   The writing_begun members of the associated object batons track the state.
196251881Speter   output_revision() and output_node() are called to cause this flushing of
197251881Speter   cached data to occur.
198251881Speter*/
199251881Speter
200251881Speter
201251881Speter/* Filtering batons */
202251881Speter
203251881Speterstruct revmap_t
204251881Speter{
205251881Speter  svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
206251881Speter  svn_boolean_t was_dropped; /* Was this revision dropped? */
207251881Speter};
208251881Speter
209251881Speterstruct parse_baton_t
210251881Speter{
211251881Speter  /* Command-line options values. */
212251881Speter  svn_boolean_t do_exclude;
213251881Speter  svn_boolean_t quiet;
214251881Speter  svn_boolean_t glob;
215251881Speter  svn_boolean_t drop_empty_revs;
216251881Speter  svn_boolean_t drop_all_empty_revs;
217251881Speter  svn_boolean_t do_renumber_revs;
218251881Speter  svn_boolean_t preserve_revprops;
219251881Speter  svn_boolean_t skip_missing_merge_sources;
220251881Speter  svn_boolean_t allow_deltas;
221251881Speter  apr_array_header_t *prefixes;
222251881Speter
223251881Speter  /* Input and output streams. */
224251881Speter  svn_stream_t *in_stream;
225251881Speter  svn_stream_t *out_stream;
226251881Speter
227251881Speter  /* State for the filtering process. */
228251881Speter  apr_int32_t rev_drop_count;
229251881Speter  apr_hash_t *dropped_nodes;
230251881Speter  apr_hash_t *renumber_history;  /* svn_revnum_t -> struct revmap_t */
231251881Speter  svn_revnum_t last_live_revision;
232251881Speter  /* The oldest original revision, greater than r0, in the input
233251881Speter     stream which was not filtered. */
234251881Speter  svn_revnum_t oldest_original_rev;
235251881Speter};
236251881Speter
237251881Speterstruct revision_baton_t
238251881Speter{
239251881Speter  /* Reference to the global parse baton. */
240251881Speter  struct parse_baton_t *pb;
241251881Speter
242251881Speter  /* Does this revision have node or prop changes? */
243251881Speter  svn_boolean_t has_nodes;
244251881Speter
245251881Speter  /* Did we drop any nodes? */
246251881Speter  svn_boolean_t had_dropped_nodes;
247251881Speter
248251881Speter  /* Written to output stream? */
249251881Speter  svn_boolean_t writing_begun;
250251881Speter
251251881Speter  /* The original and new (re-mapped) revision numbers. */
252251881Speter  svn_revnum_t rev_orig;
253251881Speter  svn_revnum_t rev_actual;
254251881Speter
255251881Speter  /* Pointers to dumpfile data. */
256289180Speter  apr_hash_t *original_headers;
257251881Speter  apr_hash_t *props;
258251881Speter};
259251881Speter
260251881Speterstruct node_baton_t
261251881Speter{
262251881Speter  /* Reference to the current revision baton. */
263251881Speter  struct revision_baton_t *rb;
264251881Speter
265251881Speter  /* Are we skipping this node? */
266251881Speter  svn_boolean_t do_skip;
267251881Speter
268251881Speter  /* Have we been instructed to change or remove props on, or change
269251881Speter     the text of, this node? */
270251881Speter  svn_boolean_t has_props;
271251881Speter  svn_boolean_t has_text;
272251881Speter
273251881Speter  /* Written to output stream? */
274251881Speter  svn_boolean_t writing_begun;
275251881Speter
276251881Speter  /* The text content length according to the dumpfile headers, because we
277251881Speter     need the length before we have the actual text. */
278251881Speter  svn_filesize_t tcl;
279251881Speter
280251881Speter  /* Pointers to dumpfile data. */
281289180Speter  svn_repos__dumpfile_headers_t *headers;
282251881Speter  svn_stringbuf_t *props;
283251881Speter
284251881Speter  /* Expect deltas? */
285251881Speter  svn_boolean_t has_prop_delta;
286251881Speter  svn_boolean_t has_text_delta;
287251881Speter
288251881Speter  /* We might need the node path in a parse error message. */
289251881Speter  char *node_path;
290289180Speter
291289180Speter  apr_pool_t *node_pool;
292251881Speter};
293251881Speter
294251881Speter
295251881Speter
296251881Speter/* Filtering vtable members */
297251881Speter
298251881Speter/* File-format stamp. */
299251881Speterstatic svn_error_t *
300251881Spetermagic_header_record(int version, void *parse_baton, apr_pool_t *pool)
301251881Speter{
302251881Speter  struct parse_baton_t *pb = parse_baton;
303251881Speter
304251881Speter  if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
305251881Speter    pb->allow_deltas = TRUE;
306251881Speter
307251881Speter  SVN_ERR(svn_stream_printf(pb->out_stream, pool,
308251881Speter                            SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
309251881Speter                            version));
310251881Speter
311251881Speter  return SVN_NO_ERROR;
312251881Speter}
313251881Speter
314251881Speter
315289180Speter/* Return a deep copy of a (char * -> char *) hash. */
316289180Speterstatic apr_hash_t *
317289180Speterheaders_dup(apr_hash_t *headers,
318289180Speter            apr_pool_t *pool)
319289180Speter{
320289180Speter  apr_hash_t *new_hash = apr_hash_make(pool);
321289180Speter  apr_hash_index_t *hi;
322289180Speter
323289180Speter  for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
324289180Speter    {
325289180Speter      const char *key = apr_hash_this_key(hi);
326289180Speter      const char *val = apr_hash_this_val(hi);
327289180Speter
328289180Speter      svn_hash_sets(new_hash, apr_pstrdup(pool, key), apr_pstrdup(pool, val));
329289180Speter    }
330289180Speter  return new_hash;
331289180Speter}
332289180Speter
333251881Speter/* New revision: set up revision_baton, decide if we skip it. */
334251881Speterstatic svn_error_t *
335251881Speternew_revision_record(void **revision_baton,
336251881Speter                    apr_hash_t *headers,
337251881Speter                    void *parse_baton,
338251881Speter                    apr_pool_t *pool)
339251881Speter{
340251881Speter  struct revision_baton_t *rb;
341251881Speter  const char *rev_orig;
342251881Speter
343251881Speter  *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t));
344251881Speter  rb = *revision_baton;
345251881Speter  rb->pb = parse_baton;
346251881Speter  rb->has_nodes = FALSE;
347251881Speter  rb->had_dropped_nodes = FALSE;
348251881Speter  rb->writing_begun = FALSE;
349251881Speter  rb->props = apr_hash_make(pool);
350289180Speter  rb->original_headers = headers_dup(headers, pool);
351251881Speter
352251881Speter  rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
353251881Speter  rb->rev_orig = SVN_STR_TO_REV(rev_orig);
354251881Speter
355251881Speter  if (rb->pb->do_renumber_revs)
356251881Speter    rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
357251881Speter  else
358251881Speter    rb->rev_actual = rb->rev_orig;
359251881Speter
360251881Speter  return SVN_NO_ERROR;
361251881Speter}
362251881Speter
363251881Speter
364251881Speter/* Output revision to dumpstream
365251881Speter   This may be called by new_node_record(), iff rb->has_nodes has been set
366251881Speter   to TRUE, or by close_revision() otherwise. This must only be called
367251881Speter   if rb->writing_begun is FALSE. */
368251881Speterstatic svn_error_t *
369251881Speteroutput_revision(struct revision_baton_t *rb)
370251881Speter{
371251881Speter  svn_boolean_t write_out_rev = FALSE;
372251881Speter  apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
373251881Speter  apr_pool_t *subpool = svn_pool_create(hash_pool);
374251881Speter
375251881Speter  rb->writing_begun = TRUE;
376251881Speter
377251881Speter  /* If this revision has no nodes left because the ones it had were
378251881Speter     dropped, and we are not dropping empty revisions, and we were not
379251881Speter     told to preserve revision props, then we want to fixup the
380251881Speter     revision props to only contain:
381251881Speter       - the date
382251881Speter       - a log message that reports that this revision is just stuffing. */
383251881Speter  if ((! rb->pb->preserve_revprops)
384251881Speter      && (! rb->has_nodes)
385251881Speter      && rb->had_dropped_nodes
386251881Speter      && (! rb->pb->drop_empty_revs)
387251881Speter      && (! rb->pb->drop_all_empty_revs))
388251881Speter    {
389251881Speter      apr_hash_t *old_props = rb->props;
390251881Speter      rb->props = apr_hash_make(hash_pool);
391251881Speter      svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE,
392251881Speter                    svn_hash_gets(old_props, SVN_PROP_REVISION_DATE));
393251881Speter      svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG,
394251881Speter                    svn_string_create(_("This is an empty revision for "
395251881Speter                                        "padding."), hash_pool));
396251881Speter    }
397251881Speter
398251881Speter  /* write out the revision */
399251881Speter  /* Revision is written out in the following cases:
400251881Speter     1. If the revision has nodes or
401251881Speter     it is revision 0 (Special case: To preserve the props on r0).
402251881Speter     2. --drop-empty-revs has been supplied,
403251881Speter     but revision has not all nodes dropped.
404251881Speter     3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied,
405251881Speter     write out the revision which has no nodes to begin with.
406251881Speter  */
407251881Speter  if (rb->has_nodes || (rb->rev_orig == 0))
408251881Speter    write_out_rev = TRUE;
409251881Speter  else if (rb->pb->drop_empty_revs)
410251881Speter    write_out_rev = ! rb->had_dropped_nodes;
411251881Speter  else if (! rb->pb->drop_all_empty_revs)
412251881Speter    write_out_rev = TRUE;
413251881Speter
414251881Speter  if (write_out_rev)
415251881Speter    {
416251881Speter      /* This revision is a keeper. */
417289180Speter      SVN_ERR(svn_repos__dump_revision_record(rb->pb->out_stream,
418289180Speter                                              rb->rev_actual,
419289180Speter                                              rb->original_headers,
420289180Speter                                              rb->props,
421289180Speter                                              FALSE /*props_section_always*/,
422289180Speter                                              subpool));
423251881Speter
424251881Speter      /* Stash the oldest original rev not dropped. */
425251881Speter      if (rb->rev_orig > 0
426251881Speter          && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
427251881Speter        rb->pb->oldest_original_rev = rb->rev_orig;
428251881Speter
429251881Speter      if (rb->pb->do_renumber_revs)
430251881Speter        {
431251881Speter          svn_revnum_t *rr_key;
432251881Speter          struct revmap_t *rr_val;
433251881Speter          apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
434251881Speter          rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
435251881Speter          rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
436251881Speter          *rr_key = rb->rev_orig;
437251881Speter          rr_val->rev = rb->rev_actual;
438251881Speter          rr_val->was_dropped = FALSE;
439251881Speter          apr_hash_set(rb->pb->renumber_history, rr_key,
440251881Speter                       sizeof(*rr_key), rr_val);
441251881Speter          rb->pb->last_live_revision = rb->rev_actual;
442251881Speter        }
443251881Speter
444251881Speter      if (! rb->pb->quiet)
445251881Speter        SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
446251881Speter                                    _("Revision %ld committed as %ld.\n"),
447251881Speter                                    rb->rev_orig, rb->rev_actual));
448251881Speter    }
449251881Speter  else
450251881Speter    {
451251881Speter      /* We're dropping this revision. */
452251881Speter      rb->pb->rev_drop_count++;
453251881Speter      if (rb->pb->do_renumber_revs)
454251881Speter        {
455251881Speter          svn_revnum_t *rr_key;
456251881Speter          struct revmap_t *rr_val;
457251881Speter          apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
458251881Speter          rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
459251881Speter          rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
460251881Speter          *rr_key = rb->rev_orig;
461251881Speter          rr_val->rev = rb->pb->last_live_revision;
462251881Speter          rr_val->was_dropped = TRUE;
463251881Speter          apr_hash_set(rb->pb->renumber_history, rr_key,
464251881Speter                       sizeof(*rr_key), rr_val);
465251881Speter        }
466251881Speter
467251881Speter      if (! rb->pb->quiet)
468251881Speter        SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
469251881Speter                                    _("Revision %ld skipped.\n"),
470251881Speter                                    rb->rev_orig));
471251881Speter    }
472251881Speter  svn_pool_destroy(subpool);
473251881Speter  return SVN_NO_ERROR;
474251881Speter}
475251881Speter
476251881Speter
477251881Speter/* UUID record here: dump it, as we do not filter them. */
478251881Speterstatic svn_error_t *
479251881Speteruuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
480251881Speter{
481251881Speter  struct parse_baton_t *pb = parse_baton;
482251881Speter  SVN_ERR(svn_stream_printf(pb->out_stream, pool,
483251881Speter                            SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
484251881Speter  return SVN_NO_ERROR;
485251881Speter}
486251881Speter
487251881Speter
488251881Speter/* New node here. Set up node_baton by copying headers. */
489251881Speterstatic svn_error_t *
490251881Speternew_node_record(void **node_baton,
491251881Speter                apr_hash_t *headers,
492251881Speter                void *rev_baton,
493251881Speter                apr_pool_t *pool)
494251881Speter{
495251881Speter  struct parse_baton_t *pb;
496251881Speter  struct node_baton_t *nb;
497251881Speter  char *node_path, *copyfrom_path;
498251881Speter  apr_hash_index_t *hi;
499251881Speter  const char *tcl;
500251881Speter
501251881Speter  *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
502251881Speter  nb          = *node_baton;
503251881Speter  nb->rb      = rev_baton;
504289180Speter  nb->node_pool = pool;
505251881Speter  pb          = nb->rb->pb;
506251881Speter
507251881Speter  node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH);
508251881Speter  copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH);
509251881Speter
510251881Speter  /* Ensure that paths start with a leading '/'. */
511251881Speter  if (node_path[0] != '/')
512289180Speter    node_path = apr_pstrcat(pool, "/", node_path, SVN_VA_NULL);
513251881Speter  if (copyfrom_path && copyfrom_path[0] != '/')
514289180Speter    copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, SVN_VA_NULL);
515251881Speter
516251881Speter  nb->do_skip = skip_path(node_path, pb->prefixes,
517251881Speter                          pb->do_exclude, pb->glob);
518251881Speter
519251881Speter  /* If we're skipping the node, take note of path, discarding the
520251881Speter     rest.  */
521251881Speter  if (nb->do_skip)
522251881Speter    {
523251881Speter      svn_hash_sets(pb->dropped_nodes,
524251881Speter                    apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
525251881Speter                                node_path),
526251881Speter                    (void *)1);
527251881Speter      nb->rb->had_dropped_nodes = TRUE;
528251881Speter    }
529251881Speter  else
530251881Speter    {
531266731Speter      const char *kind;
532266731Speter      const char *action;
533266731Speter
534251881Speter      tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
535251881Speter
536251881Speter      /* Test if this node was copied from dropped source. */
537251881Speter      if (copyfrom_path &&
538251881Speter          skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
539251881Speter        {
540251881Speter          /* This node was copied from a dropped source.
541251881Speter             We have a problem, since we did not want to drop this node too.
542251881Speter
543251881Speter             However, there is one special case we'll handle.  If the node is
544251881Speter             a file, and this was a copy-and-modify operation, then the
545251881Speter             dumpfile should contain the new contents of the file.  In this
546251881Speter             scenario, we'll just do an add without history using the new
547251881Speter             contents.  */
548251881Speter          kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
549251881Speter
550251881Speter          /* If there is a Text-content-length header, and the kind is
551251881Speter             "file", we just fallback to an add without history. */
552251881Speter          if (tcl && (strcmp(kind, "file") == 0))
553251881Speter            {
554251881Speter              svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
555251881Speter                            NULL);
556251881Speter              svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
557251881Speter                            NULL);
558251881Speter              copyfrom_path = NULL;
559251881Speter            }
560251881Speter          /* Else, this is either a directory or a file whose contents we
561251881Speter             don't have readily available.  */
562251881Speter          else
563251881Speter            {
564251881Speter              return svn_error_createf
565251881Speter                (SVN_ERR_INCOMPLETE_DATA, 0,
566251881Speter                 _("Invalid copy source path '%s'"), copyfrom_path);
567251881Speter            }
568251881Speter        }
569251881Speter
570251881Speter      nb->has_props = FALSE;
571251881Speter      nb->has_text = FALSE;
572251881Speter      nb->has_prop_delta = FALSE;
573251881Speter      nb->has_text_delta = FALSE;
574251881Speter      nb->writing_begun = FALSE;
575251881Speter      nb->tcl = tcl ? svn__atoui64(tcl) : 0;
576289180Speter      nb->headers = svn_repos__dumpfile_headers_create(pool);
577251881Speter      nb->props = svn_stringbuf_create_empty(pool);
578251881Speter      nb->node_path = apr_pstrdup(pool, node_path);
579251881Speter
580251881Speter      /* Now we know for sure that we have a node that will not be
581251881Speter         skipped, flush the revision if it has not already been done. */
582251881Speter      nb->rb->has_nodes = TRUE;
583251881Speter      if (! nb->rb->writing_begun)
584251881Speter        SVN_ERR(output_revision(nb->rb));
585251881Speter
586266731Speter      /* A node record is required to begin with 'Node-path', skip the
587266731Speter         leading '/' to match the form used by 'svnadmin dump'. */
588289180Speter      svn_repos__dumpfile_header_push(
589289180Speter        nb->headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_path + 1);
590266731Speter
591266731Speter      /* Node-kind is next and is optional. */
592266731Speter      kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
593266731Speter      if (kind)
594289180Speter        svn_repos__dumpfile_header_push(
595289180Speter          nb->headers, SVN_REPOS_DUMPFILE_NODE_KIND, kind);
596266731Speter
597266731Speter      /* Node-action is next and required. */
598266731Speter      action = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION);
599266731Speter      if (action)
600289180Speter        svn_repos__dumpfile_header_push(
601289180Speter          nb->headers, SVN_REPOS_DUMPFILE_NODE_ACTION, action);
602266731Speter      else
603266731Speter        return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
604266731Speter                                 _("Missing Node-action for path '%s'"),
605266731Speter                                 node_path);
606266731Speter
607251881Speter      for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
608251881Speter        {
609289180Speter          const char *key = apr_hash_this_key(hi);
610289180Speter          const char *val = apr_hash_this_val(hi);
611251881Speter
612251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
613251881Speter              && (!strcmp(val, "true")))
614251881Speter            nb->has_prop_delta = TRUE;
615251881Speter
616251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
617251881Speter              && (!strcmp(val, "true")))
618251881Speter            nb->has_text_delta = TRUE;
619251881Speter
620251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
621251881Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
622266731Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH))
623266731Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_PATH))
624266731Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_KIND))
625266731Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_ACTION)))
626251881Speter            continue;
627251881Speter
628251881Speter          /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
629251881Speter             The number points to some revision in the past. We keep track
630251881Speter             of revision renumbering in an apr_hash, which maps original
631251881Speter             revisions to new ones. Dropped revision are mapped to -1.
632251881Speter             This should never happen here.
633251881Speter          */
634251881Speter          if (pb->do_renumber_revs
635251881Speter              && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
636251881Speter            {
637251881Speter              svn_revnum_t cf_orig_rev;
638251881Speter              struct revmap_t *cf_renum_val;
639251881Speter
640251881Speter              cf_orig_rev = SVN_STR_TO_REV(val);
641251881Speter              cf_renum_val = apr_hash_get(pb->renumber_history,
642251881Speter                                          &cf_orig_rev,
643251881Speter                                          sizeof(svn_revnum_t));
644251881Speter              if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
645251881Speter                return svn_error_createf
646251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
647251881Speter                   _("No valid copyfrom revision in filtered stream"));
648289180Speter              svn_repos__dumpfile_header_pushf(
649289180Speter                nb->headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
650289180Speter                "%ld", cf_renum_val->rev);
651251881Speter              continue;
652251881Speter            }
653251881Speter
654251881Speter          /* passthru: put header straight to output */
655289180Speter          svn_repos__dumpfile_header_push(nb->headers, key, val);
656251881Speter        }
657251881Speter    }
658251881Speter
659251881Speter  return SVN_NO_ERROR;
660251881Speter}
661251881Speter
662251881Speter
663251881Speter/* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
664251881Speter   sources or renumbering revisions in rangelists as appropriate, and
665251881Speter   return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
666251881Speter   POOL). */
667251881Speterstatic svn_error_t *
668251881Speteradjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
669251881Speter                 struct revision_baton_t *rb, apr_pool_t *pool)
670251881Speter{
671251881Speter  apr_hash_t *mergeinfo;
672251881Speter  apr_hash_t *final_mergeinfo = apr_hash_make(pool);
673251881Speter  apr_hash_index_t *hi;
674251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
675251881Speter
676251881Speter  SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
677251881Speter
678251881Speter  /* Issue #3020: If we are skipping missing merge sources, then also
679251881Speter     filter mergeinfo ranges as old or older than the oldest revision in the
680251881Speter     dump stream.  Those older than the oldest obviously refer to history
681251881Speter     outside of the dump stream.  The oldest rev itself is present in the
682251881Speter     dump, but cannot be a valid merge source revision since it is the
683251881Speter     start of all history.  E.g. if we dump -r100:400 then dumpfilter the
684251881Speter     result with --skip-missing-merge-sources, any mergeinfo with revision
685251881Speter     100 implies a change of -r99:100, but r99 is part of the history we
686286506Speter     want filtered.
687251881Speter
688251881Speter     If the oldest rev is r0 then there is nothing to filter. */
689289180Speter
690289180Speter  /* ### This seems to cater only for use cases where the revisions being
691289180Speter         processed are not following on from revisions that will already
692289180Speter         exist in the destination repository. If the revisions being
693289180Speter         processed do follow on, then we might want to keep the mergeinfo
694289180Speter         that refers to those older revisions. */
695289180Speter
696251881Speter  if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
697251881Speter    SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
698251881Speter      &mergeinfo, mergeinfo,
699251881Speter      rb->pb->oldest_original_rev, 0,
700251881Speter      FALSE, subpool, subpool));
701251881Speter
702251881Speter  for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
703251881Speter    {
704289180Speter      const char *merge_source = apr_hash_this_key(hi);
705289180Speter      svn_rangelist_t *rangelist = apr_hash_this_val(hi);
706251881Speter      struct parse_baton_t *pb = rb->pb;
707251881Speter
708251881Speter      /* Determine whether the merge_source is a part of the prefix. */
709251881Speter      if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
710251881Speter        {
711251881Speter          if (pb->skip_missing_merge_sources)
712251881Speter            continue;
713251881Speter          else
714251881Speter            return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
715251881Speter                                     _("Missing merge source path '%s'; try "
716251881Speter                                       "with --skip-missing-merge-sources"),
717251881Speter                                     merge_source);
718251881Speter        }
719251881Speter
720251881Speter      /* Possibly renumber revisions in merge source's rangelist. */
721251881Speter      if (pb->do_renumber_revs)
722251881Speter        {
723251881Speter          int i;
724251881Speter
725251881Speter          for (i = 0; i < rangelist->nelts; i++)
726251881Speter            {
727251881Speter              struct revmap_t *revmap_start;
728251881Speter              struct revmap_t *revmap_end;
729251881Speter              svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
730251881Speter                                                       svn_merge_range_t *);
731251881Speter
732251881Speter              revmap_start = apr_hash_get(pb->renumber_history,
733251881Speter                                          &range->start, sizeof(svn_revnum_t));
734251881Speter              if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
735251881Speter                return svn_error_createf
736251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
737251881Speter                   _("No valid revision range 'start' in filtered stream"));
738251881Speter
739251881Speter              revmap_end = apr_hash_get(pb->renumber_history,
740251881Speter                                        &range->end, sizeof(svn_revnum_t));
741251881Speter              if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
742251881Speter                return svn_error_createf
743251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
744251881Speter                   _("No valid revision range 'end' in filtered stream"));
745251881Speter
746251881Speter              range->start = revmap_start->rev;
747251881Speter              range->end = revmap_end->rev;
748251881Speter            }
749251881Speter        }
750251881Speter      svn_hash_sets(final_mergeinfo, merge_source, rangelist);
751251881Speter    }
752251881Speter
753286506Speter  SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
754251881Speter  SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
755251881Speter  svn_pool_destroy(subpool);
756251881Speter
757251881Speter  return SVN_NO_ERROR;
758251881Speter}
759251881Speter
760251881Speter
761251881Speterstatic svn_error_t *
762251881Speterset_revision_property(void *revision_baton,
763251881Speter                      const char *name,
764251881Speter                      const svn_string_t *value)
765251881Speter{
766251881Speter  struct revision_baton_t *rb = revision_baton;
767251881Speter  apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
768251881Speter
769251881Speter  svn_hash_sets(rb->props,
770251881Speter                apr_pstrdup(hash_pool, name),
771251881Speter                svn_string_dup(value, hash_pool));
772251881Speter  return SVN_NO_ERROR;
773251881Speter}
774251881Speter
775251881Speter
776251881Speterstatic svn_error_t *
777251881Speterset_node_property(void *node_baton,
778251881Speter                  const char *name,
779251881Speter                  const svn_string_t *value)
780251881Speter{
781251881Speter  struct node_baton_t *nb = node_baton;
782251881Speter  struct revision_baton_t *rb = nb->rb;
783251881Speter
784251881Speter  if (nb->do_skip)
785251881Speter    return SVN_NO_ERROR;
786251881Speter
787289180Speter  /* Try to detect if a delta-mode property occurs unexpectedly. HAS_PROPS
788289180Speter     can be false here only if the parser didn't call remove_node_props(),
789289180Speter     so this may indicate a bug rather than bad data. */
790251881Speter  if (! (nb->has_props || nb->has_prop_delta))
791251881Speter    return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
792251881Speter                             _("Delta property block detected, but deltas "
793251881Speter                               "are not enabled for node '%s' in original "
794251881Speter                               "revision %ld"),
795251881Speter                             nb->node_path, rb->rev_orig);
796251881Speter
797251881Speter  if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
798251881Speter    {
799251881Speter      svn_string_t *filtered_mergeinfo;  /* Avoid compiler warning. */
800251881Speter      apr_pool_t *pool = apr_hash_pool_get(rb->props);
801251881Speter      SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
802251881Speter      value = filtered_mergeinfo;
803251881Speter    }
804251881Speter
805251881Speter  nb->has_props = TRUE;
806251881Speter  write_prop_to_stringbuf(nb->props, name, value);
807251881Speter
808251881Speter  return SVN_NO_ERROR;
809251881Speter}
810251881Speter
811251881Speter
812251881Speterstatic svn_error_t *
813251881Speterdelete_node_property(void *node_baton, const char *name)
814251881Speter{
815251881Speter  struct node_baton_t *nb = node_baton;
816251881Speter  struct revision_baton_t *rb = nb->rb;
817251881Speter
818251881Speter  if (nb->do_skip)
819251881Speter    return SVN_NO_ERROR;
820251881Speter
821251881Speter  if (!nb->has_prop_delta)
822251881Speter    return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
823251881Speter                             _("Delta property block detected, but deltas "
824251881Speter                               "are not enabled for node '%s' in original "
825251881Speter                               "revision %ld"),
826251881Speter                             nb->node_path, rb->rev_orig);
827251881Speter
828251881Speter  nb->has_props = TRUE;
829251881Speter  write_propdel_to_stringbuf(&(nb->props), name);
830251881Speter
831251881Speter  return SVN_NO_ERROR;
832251881Speter}
833251881Speter
834251881Speter
835289180Speter/* The parser calls this method if the node record has a non-delta
836289180Speter * property content section, before any calls to set_node_property().
837289180Speter * If the node record uses property deltas, this is not called.
838289180Speter */
839251881Speterstatic svn_error_t *
840251881Speterremove_node_props(void *node_baton)
841251881Speter{
842251881Speter  struct node_baton_t *nb = node_baton;
843251881Speter
844251881Speter  /* In this case, not actually indicating that the node *has* props,
845289180Speter     rather that it has a property content section. */
846251881Speter  nb->has_props = TRUE;
847251881Speter
848251881Speter  return SVN_NO_ERROR;
849251881Speter}
850251881Speter
851251881Speter
852251881Speterstatic svn_error_t *
853251881Speterset_fulltext(svn_stream_t **stream, void *node_baton)
854251881Speter{
855251881Speter  struct node_baton_t *nb = node_baton;
856251881Speter
857251881Speter  if (!nb->do_skip)
858251881Speter    {
859251881Speter      nb->has_text = TRUE;
860251881Speter      if (! nb->writing_begun)
861289180Speter        {
862289180Speter          nb->writing_begun = TRUE;
863289180Speter          if (nb->has_props)
864289180Speter            {
865289180Speter              svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
866289180Speter            }
867289180Speter          SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
868289180Speter                                              nb->headers,
869289180Speter                                              nb->has_props ? nb->props : NULL,
870289180Speter                                              nb->has_text,
871289180Speter                                              nb->tcl,
872289180Speter                                              TRUE /*content_length_always*/,
873289180Speter                                              nb->node_pool));
874289180Speter        }
875251881Speter      *stream = nb->rb->pb->out_stream;
876251881Speter    }
877251881Speter
878251881Speter  return SVN_NO_ERROR;
879251881Speter}
880251881Speter
881251881Speter
882251881Speter/* Finalize node */
883251881Speterstatic svn_error_t *
884251881Speterclose_node(void *node_baton)
885251881Speter{
886251881Speter  struct node_baton_t *nb = node_baton;
887251881Speter  apr_size_t len = 2;
888251881Speter
889251881Speter  /* Get out of here if we can. */
890251881Speter  if (nb->do_skip)
891251881Speter    return SVN_NO_ERROR;
892251881Speter
893251881Speter  /* If the node was not flushed already to output its text, do it now. */
894251881Speter  if (! nb->writing_begun)
895289180Speter    {
896289180Speter      nb->writing_begun = TRUE;
897289180Speter      if (nb->has_props)
898289180Speter        {
899289180Speter          svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
900289180Speter        }
901289180Speter      SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
902289180Speter                                          nb->headers,
903289180Speter                                          nb->has_props ? nb->props : NULL,
904289180Speter                                          nb->has_text,
905289180Speter                                          nb->tcl,
906289180Speter                                          TRUE /*content_length_always*/,
907289180Speter                                          nb->node_pool));
908289180Speter    }
909251881Speter
910251881Speter  /* put an end to node. */
911251881Speter  SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
912251881Speter
913251881Speter  return SVN_NO_ERROR;
914251881Speter}
915251881Speter
916251881Speter
917251881Speter/* Finalize revision */
918251881Speterstatic svn_error_t *
919251881Speterclose_revision(void *revision_baton)
920251881Speter{
921251881Speter  struct revision_baton_t *rb = revision_baton;
922251881Speter
923251881Speter  /* If no node has yet flushed the revision, do it now. */
924251881Speter  if (! rb->writing_begun)
925251881Speter    return output_revision(rb);
926251881Speter  else
927251881Speter    return SVN_NO_ERROR;
928251881Speter}
929251881Speter
930251881Speter
931251881Speter/* Filtering vtable */
932289180Speterstatic svn_repos_parse_fns3_t filtering_vtable =
933251881Speter  {
934251881Speter    magic_header_record,
935251881Speter    uuid_record,
936251881Speter    new_revision_record,
937251881Speter    new_node_record,
938251881Speter    set_revision_property,
939251881Speter    set_node_property,
940251881Speter    delete_node_property,
941251881Speter    remove_node_props,
942251881Speter    set_fulltext,
943251881Speter    NULL,
944251881Speter    close_node,
945251881Speter    close_revision
946251881Speter  };
947251881Speter
948251881Speter
949251881Speter
950251881Speter/** Subcommands. **/
951251881Speter
952251881Speterstatic svn_opt_subcommand_t
953251881Speter  subcommand_help,
954251881Speter  subcommand_exclude,
955251881Speter  subcommand_include;
956251881Speter
957251881Speterenum
958251881Speter  {
959251881Speter    svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
960251881Speter    svndumpfilter__drop_all_empty_revs,
961251881Speter    svndumpfilter__renumber_revs,
962251881Speter    svndumpfilter__preserve_revprops,
963251881Speter    svndumpfilter__skip_missing_merge_sources,
964251881Speter    svndumpfilter__targets,
965251881Speter    svndumpfilter__quiet,
966251881Speter    svndumpfilter__glob,
967251881Speter    svndumpfilter__version
968251881Speter  };
969251881Speter
970251881Speter/* Option codes and descriptions.
971251881Speter *
972251881Speter * The entire list must be terminated with an entry of nulls.
973251881Speter */
974251881Speterstatic const apr_getopt_option_t options_table[] =
975251881Speter  {
976251881Speter    {"help",          'h', 0,
977251881Speter     N_("show help on a subcommand")},
978251881Speter
979251881Speter    {NULL,            '?', 0,
980251881Speter     N_("show help on a subcommand")},
981251881Speter
982251881Speter    {"version",            svndumpfilter__version, 0,
983251881Speter     N_("show program version information") },
984251881Speter    {"quiet",              svndumpfilter__quiet, 0,
985251881Speter     N_("Do not display filtering statistics.") },
986251881Speter    {"pattern",            svndumpfilter__glob, 0,
987251881Speter     N_("Treat the path prefixes as file glob patterns.") },
988251881Speter    {"drop-empty-revs",    svndumpfilter__drop_empty_revs, 0,
989251881Speter     N_("Remove revisions emptied by filtering.")},
990251881Speter    {"drop-all-empty-revs",    svndumpfilter__drop_all_empty_revs, 0,
991251881Speter     N_("Remove all empty revisions found in dumpstream\n"
992251881Speter        "                             except revision 0.")},
993251881Speter    {"renumber-revs",      svndumpfilter__renumber_revs, 0,
994251881Speter     N_("Renumber revisions left after filtering.") },
995251881Speter    {"skip-missing-merge-sources",
996251881Speter     svndumpfilter__skip_missing_merge_sources, 0,
997251881Speter     N_("Skip missing merge sources.") },
998251881Speter    {"preserve-revprops",  svndumpfilter__preserve_revprops, 0,
999251881Speter     N_("Don't filter revision properties.") },
1000251881Speter    {"targets", svndumpfilter__targets, 1,
1001251881Speter     N_("Read additional prefixes, one per line, from\n"
1002251881Speter        "                             file ARG.")},
1003251881Speter    {NULL}
1004251881Speter  };
1005251881Speter
1006251881Speter
1007251881Speter/* Array of available subcommands.
1008251881Speter * The entire list must be terminated with an entry of nulls.
1009251881Speter */
1010251881Speterstatic const svn_opt_subcommand_desc2_t cmd_table[] =
1011251881Speter  {
1012251881Speter    {"exclude", subcommand_exclude, {0},
1013251881Speter     N_("Filter out nodes with given prefixes from dumpstream.\n"
1014251881Speter        "usage: svndumpfilter exclude PATH_PREFIX...\n"),
1015251881Speter     {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1016251881Speter      svndumpfilter__renumber_revs,
1017251881Speter      svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1018251881Speter      svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1019251881Speter      svndumpfilter__glob} },
1020251881Speter
1021251881Speter    {"include", subcommand_include, {0},
1022251881Speter     N_("Filter out nodes without given prefixes from dumpstream.\n"
1023251881Speter        "usage: svndumpfilter include PATH_PREFIX...\n"),
1024251881Speter     {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1025251881Speter      svndumpfilter__renumber_revs,
1026251881Speter      svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1027251881Speter      svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1028251881Speter      svndumpfilter__glob} },
1029251881Speter
1030251881Speter    {"help", subcommand_help, {"?", "h"},
1031251881Speter     N_("Describe the usage of this program or its subcommands.\n"
1032251881Speter        "usage: svndumpfilter help [SUBCOMMAND...]\n"),
1033251881Speter     {0} },
1034251881Speter
1035251881Speter    { NULL, NULL, {0}, NULL, {0} }
1036251881Speter  };
1037251881Speter
1038251881Speter
1039251881Speter/* Baton for passing option/argument state to a subcommand function. */
1040251881Speterstruct svndumpfilter_opt_state
1041251881Speter{
1042251881Speter  svn_opt_revision_t start_revision;     /* -r X[:Y] is         */
1043251881Speter  svn_opt_revision_t end_revision;       /* not implemented.    */
1044251881Speter  svn_boolean_t quiet;                   /* --quiet             */
1045251881Speter  svn_boolean_t glob;                    /* --pattern           */
1046251881Speter  svn_boolean_t version;                 /* --version           */
1047251881Speter  svn_boolean_t drop_empty_revs;         /* --drop-empty-revs   */
1048251881Speter  svn_boolean_t drop_all_empty_revs;     /* --drop-all-empty-revs */
1049251881Speter  svn_boolean_t help;                    /* --help or -?        */
1050251881Speter  svn_boolean_t renumber_revs;           /* --renumber-revs     */
1051251881Speter  svn_boolean_t preserve_revprops;       /* --preserve-revprops */
1052251881Speter  svn_boolean_t skip_missing_merge_sources;
1053251881Speter                                         /* --skip-missing-merge-sources */
1054251881Speter  const char *targets_file;              /* --targets-file       */
1055251881Speter  apr_array_header_t *prefixes;          /* mainargs.           */
1056251881Speter};
1057251881Speter
1058251881Speter
1059251881Speterstatic svn_error_t *
1060251881Speterparse_baton_initialize(struct parse_baton_t **pb,
1061251881Speter                       struct svndumpfilter_opt_state *opt_state,
1062251881Speter                       svn_boolean_t do_exclude,
1063251881Speter                       apr_pool_t *pool)
1064251881Speter{
1065251881Speter  struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1066251881Speter
1067251881Speter  /* Read the stream from STDIN.  Users can redirect a file. */
1068251881Speter  SVN_ERR(create_stdio_stream(&(baton->in_stream),
1069251881Speter                              apr_file_open_stdin, pool));
1070251881Speter
1071251881Speter  /* Have the parser dump results to STDOUT. Users can redirect a file. */
1072251881Speter  SVN_ERR(create_stdio_stream(&(baton->out_stream),
1073251881Speter                              apr_file_open_stdout, pool));
1074251881Speter
1075251881Speter  baton->do_exclude = do_exclude;
1076251881Speter
1077251881Speter  /* Ignore --renumber-revs if there can't possibly be
1078251881Speter     anything to renumber. */
1079251881Speter  baton->do_renumber_revs =
1080251881Speter    (opt_state->renumber_revs && (opt_state->drop_empty_revs
1081251881Speter                                  || opt_state->drop_all_empty_revs));
1082251881Speter
1083251881Speter  baton->drop_empty_revs = opt_state->drop_empty_revs;
1084251881Speter  baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
1085251881Speter  baton->preserve_revprops = opt_state->preserve_revprops;
1086251881Speter  baton->quiet = opt_state->quiet;
1087251881Speter  baton->glob = opt_state->glob;
1088251881Speter  baton->prefixes = opt_state->prefixes;
1089251881Speter  baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
1090251881Speter  baton->rev_drop_count = 0; /* used to shift revnums while filtering */
1091251881Speter  baton->dropped_nodes = apr_hash_make(pool);
1092251881Speter  baton->renumber_history = apr_hash_make(pool);
1093251881Speter  baton->last_live_revision = SVN_INVALID_REVNUM;
1094251881Speter  baton->oldest_original_rev = SVN_INVALID_REVNUM;
1095251881Speter  baton->allow_deltas = FALSE;
1096251881Speter
1097251881Speter  *pb = baton;
1098251881Speter  return SVN_NO_ERROR;
1099251881Speter}
1100251881Speter
1101251881Speter/* This implements `help` subcommand. */
1102251881Speterstatic svn_error_t *
1103251881Spetersubcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1104251881Speter{
1105251881Speter  struct svndumpfilter_opt_state *opt_state = baton;
1106251881Speter  const char *header =
1107251881Speter    _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
1108289180Speter      "Subversion repository dump filtering tool.\n"
1109251881Speter      "Type 'svndumpfilter help <subcommand>' for help on a "
1110251881Speter      "specific subcommand.\n"
1111251881Speter      "Type 'svndumpfilter --version' to see the program version.\n"
1112251881Speter      "\n"
1113251881Speter      "Available subcommands:\n");
1114251881Speter
1115251881Speter  SVN_ERR(svn_opt_print_help4(os, "svndumpfilter",
1116251881Speter                              opt_state ? opt_state->version : FALSE,
1117251881Speter                              opt_state ? opt_state->quiet : FALSE,
1118251881Speter                              /*###opt_state ? opt_state->verbose :*/ FALSE,
1119251881Speter                              NULL, header, cmd_table, options_table,
1120251881Speter                              NULL, NULL, pool));
1121251881Speter
1122251881Speter  return SVN_NO_ERROR;
1123251881Speter}
1124251881Speter
1125251881Speter
1126251881Speter/* Version compatibility check */
1127251881Speterstatic svn_error_t *
1128251881Spetercheck_lib_versions(void)
1129251881Speter{
1130251881Speter  static const svn_version_checklist_t checklist[] =
1131251881Speter    {
1132251881Speter      { "svn_subr",  svn_subr_version },
1133251881Speter      { "svn_repos", svn_repos_version },
1134251881Speter      { "svn_delta", svn_delta_version },
1135251881Speter      { NULL, NULL }
1136251881Speter    };
1137251881Speter  SVN_VERSION_DEFINE(my_version);
1138251881Speter
1139257936Speter  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1140251881Speter}
1141251881Speter
1142251881Speter
1143251881Speter/* Do the real work of filtering. */
1144251881Speterstatic svn_error_t *
1145251881Speterdo_filter(apr_getopt_t *os,
1146251881Speter          void *baton,
1147251881Speter          svn_boolean_t do_exclude,
1148251881Speter          apr_pool_t *pool)
1149251881Speter{
1150251881Speter  struct svndumpfilter_opt_state *opt_state = baton;
1151251881Speter  struct parse_baton_t *pb;
1152251881Speter  apr_hash_index_t *hi;
1153251881Speter  apr_array_header_t *keys;
1154251881Speter  int i, num_keys;
1155251881Speter
1156251881Speter  if (! opt_state->quiet)
1157251881Speter    {
1158251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1159251881Speter
1160251881Speter      if (opt_state->glob)
1161251881Speter        {
1162251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1163251881Speter                                      do_exclude
1164251881Speter                                      ? (opt_state->drop_empty_revs
1165251881Speter                                         || opt_state->drop_all_empty_revs)
1166251881Speter                                        ? _("Excluding (and dropping empty "
1167251881Speter                                            "revisions for) prefix patterns:\n")
1168251881Speter                                        : _("Excluding prefix patterns:\n")
1169251881Speter                                      : (opt_state->drop_empty_revs
1170251881Speter                                         || opt_state->drop_all_empty_revs)
1171251881Speter                                        ? _("Including (and dropping empty "
1172251881Speter                                            "revisions for) prefix patterns:\n")
1173251881Speter                                        : _("Including prefix patterns:\n")));
1174251881Speter        }
1175251881Speter      else
1176251881Speter        {
1177251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1178251881Speter                                      do_exclude
1179251881Speter                                      ? (opt_state->drop_empty_revs
1180251881Speter                                         || opt_state->drop_all_empty_revs)
1181251881Speter                                        ? _("Excluding (and dropping empty "
1182251881Speter                                            "revisions for) prefixes:\n")
1183251881Speter                                        : _("Excluding prefixes:\n")
1184251881Speter                                      : (opt_state->drop_empty_revs
1185251881Speter                                         || opt_state->drop_all_empty_revs)
1186251881Speter                                        ? _("Including (and dropping empty "
1187251881Speter                                            "revisions for) prefixes:\n")
1188251881Speter                                        : _("Including prefixes:\n")));
1189251881Speter        }
1190251881Speter
1191251881Speter      for (i = 0; i < opt_state->prefixes->nelts; i++)
1192251881Speter        {
1193251881Speter          svn_pool_clear(subpool);
1194251881Speter          SVN_ERR(svn_cmdline_fprintf
1195251881Speter                  (stderr, subpool, "   '%s'\n",
1196251881Speter                   APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
1197251881Speter        }
1198251881Speter
1199251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1200251881Speter      svn_pool_destroy(subpool);
1201251881Speter    }
1202251881Speter
1203251881Speter  SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
1204251881Speter  SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
1205251881Speter                                      TRUE, NULL, NULL, pool));
1206251881Speter
1207251881Speter  /* The rest of this is just reporting.  If we aren't reporting, get
1208251881Speter     outta here. */
1209251881Speter  if (opt_state->quiet)
1210251881Speter    return SVN_NO_ERROR;
1211251881Speter
1212251881Speter  SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1213251881Speter
1214251881Speter  if (pb->rev_drop_count)
1215251881Speter    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1216251881Speter                                Q_("Dropped %d revision.\n\n",
1217251881Speter                                   "Dropped %d revisions.\n\n",
1218251881Speter                                   pb->rev_drop_count),
1219251881Speter                                pb->rev_drop_count));
1220251881Speter
1221251881Speter  if (pb->do_renumber_revs)
1222251881Speter    {
1223251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1224251881Speter      SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1225251881Speter                                stderr, subpool));
1226251881Speter
1227251881Speter      /* Get the keys of the hash, sort them, then print the hash keys
1228251881Speter         and values, sorted by keys. */
1229251881Speter      num_keys = apr_hash_count(pb->renumber_history);
1230251881Speter      keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
1231251881Speter      for (hi = apr_hash_first(pool, pb->renumber_history);
1232251881Speter           hi;
1233251881Speter           hi = apr_hash_next(hi))
1234251881Speter        {
1235289180Speter          const svn_revnum_t *revnum = apr_hash_this_key(hi);
1236251881Speter
1237251881Speter          APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1238251881Speter        }
1239289180Speter      svn_sort__array(keys, svn_sort_compare_revisions);
1240251881Speter      for (i = 0; i < keys->nelts; i++)
1241251881Speter        {
1242251881Speter          svn_revnum_t this_key;
1243251881Speter          struct revmap_t *this_val;
1244251881Speter
1245251881Speter          svn_pool_clear(subpool);
1246251881Speter          this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
1247251881Speter          this_val = apr_hash_get(pb->renumber_history, &this_key,
1248251881Speter                                  sizeof(this_key));
1249251881Speter          if (this_val->was_dropped)
1250251881Speter            SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1251251881Speter                                        _("   %ld => (dropped)\n"),
1252251881Speter                                        this_key));
1253251881Speter          else
1254251881Speter            SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1255251881Speter                                        "   %ld => %ld\n",
1256251881Speter                                        this_key, this_val->rev));
1257251881Speter        }
1258251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1259251881Speter      svn_pool_destroy(subpool);
1260251881Speter    }
1261251881Speter
1262251881Speter  if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1263251881Speter    {
1264251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1265251881Speter      SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1266251881Speter                                  Q_("Dropped %d node:\n",
1267251881Speter                                     "Dropped %d nodes:\n",
1268251881Speter                                     num_keys),
1269251881Speter                                  num_keys));
1270251881Speter
1271251881Speter      /* Get the keys of the hash, sort them, then print the hash keys
1272251881Speter         and values, sorted by keys. */
1273251881Speter      keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
1274251881Speter      for (hi = apr_hash_first(pool, pb->dropped_nodes);
1275251881Speter           hi;
1276251881Speter           hi = apr_hash_next(hi))
1277251881Speter        {
1278289180Speter          const char *path = apr_hash_this_key(hi);
1279251881Speter
1280251881Speter          APR_ARRAY_PUSH(keys, const char *) = path;
1281251881Speter        }
1282289180Speter      svn_sort__array(keys, svn_sort_compare_paths);
1283251881Speter      for (i = 0; i < keys->nelts; i++)
1284251881Speter        {
1285251881Speter          svn_pool_clear(subpool);
1286251881Speter          SVN_ERR(svn_cmdline_fprintf
1287251881Speter                  (stderr, subpool, "   '%s'\n",
1288251881Speter                   (const char *)APR_ARRAY_IDX(keys, i, const char *)));
1289251881Speter        }
1290251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1291251881Speter      svn_pool_destroy(subpool);
1292251881Speter    }
1293251881Speter
1294251881Speter  return SVN_NO_ERROR;
1295251881Speter}
1296251881Speter
1297251881Speter/* This implements `exclude' subcommand. */
1298251881Speterstatic svn_error_t *
1299251881Spetersubcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1300251881Speter{
1301251881Speter  return do_filter(os, baton, TRUE, pool);
1302251881Speter}
1303251881Speter
1304251881Speter
1305251881Speter/* This implements `include` subcommand. */
1306251881Speterstatic svn_error_t *
1307251881Spetersubcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1308251881Speter{
1309251881Speter  return do_filter(os, baton, FALSE, pool);
1310251881Speter}
1311251881Speter
1312251881Speter
1313251881Speter
1314251881Speter/** Main. **/
1315251881Speter
1316289180Speter/*
1317289180Speter * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
1318289180Speter * either return an error to be displayed, or set *EXIT_CODE to non-zero and
1319289180Speter * return SVN_NO_ERROR.
1320289180Speter */
1321289180Speterstatic svn_error_t *
1322289180Spetersub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1323251881Speter{
1324251881Speter  svn_error_t *err;
1325251881Speter  apr_status_t apr_err;
1326251881Speter
1327251881Speter  const svn_opt_subcommand_desc2_t *subcommand = NULL;
1328251881Speter  struct svndumpfilter_opt_state opt_state;
1329251881Speter  apr_getopt_t *os;
1330251881Speter  int opt_id;
1331251881Speter  apr_array_header_t *received_opts;
1332251881Speter  int i;
1333251881Speter
1334251881Speter  /* Check library versions */
1335289180Speter  SVN_ERR(check_lib_versions());
1336251881Speter
1337251881Speter  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1338251881Speter
1339251881Speter  /* Initialize the FS library. */
1340289180Speter  SVN_ERR(svn_fs_initialize(pool));
1341251881Speter
1342251881Speter  if (argc <= 1)
1343251881Speter    {
1344289180Speter      SVN_ERR(subcommand_help(NULL, NULL, pool));
1345289180Speter      *exit_code = EXIT_FAILURE;
1346289180Speter      return SVN_NO_ERROR;
1347251881Speter    }
1348251881Speter
1349251881Speter  /* Initialize opt_state. */
1350251881Speter  memset(&opt_state, 0, sizeof(opt_state));
1351251881Speter  opt_state.start_revision.kind = svn_opt_revision_unspecified;
1352251881Speter  opt_state.end_revision.kind = svn_opt_revision_unspecified;
1353251881Speter
1354251881Speter  /* Parse options. */
1355289180Speter  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
1356251881Speter
1357251881Speter  os->interleave = 1;
1358251881Speter  while (1)
1359251881Speter    {
1360251881Speter      const char *opt_arg;
1361251881Speter
1362251881Speter      /* Parse the next option. */
1363251881Speter      apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1364251881Speter      if (APR_STATUS_IS_EOF(apr_err))
1365251881Speter        break;
1366251881Speter      else if (apr_err)
1367251881Speter        {
1368289180Speter          SVN_ERR(subcommand_help(NULL, NULL, pool));
1369289180Speter          *exit_code = EXIT_FAILURE;
1370289180Speter          return SVN_NO_ERROR;
1371251881Speter        }
1372251881Speter
1373251881Speter      /* Stash the option code in an array before parsing it. */
1374251881Speter      APR_ARRAY_PUSH(received_opts, int) = opt_id;
1375251881Speter
1376251881Speter      switch (opt_id)
1377251881Speter        {
1378251881Speter        case 'h':
1379251881Speter        case '?':
1380251881Speter          opt_state.help = TRUE;
1381251881Speter          break;
1382251881Speter        case svndumpfilter__version:
1383251881Speter          opt_state.version = TRUE;
1384251881Speter          break;
1385251881Speter        case svndumpfilter__quiet:
1386251881Speter          opt_state.quiet = TRUE;
1387251881Speter          break;
1388251881Speter        case svndumpfilter__glob:
1389251881Speter          opt_state.glob = TRUE;
1390251881Speter          break;
1391251881Speter        case svndumpfilter__drop_empty_revs:
1392251881Speter          opt_state.drop_empty_revs = TRUE;
1393251881Speter          break;
1394251881Speter        case svndumpfilter__drop_all_empty_revs:
1395251881Speter          opt_state.drop_all_empty_revs = TRUE;
1396251881Speter          break;
1397251881Speter        case svndumpfilter__renumber_revs:
1398251881Speter          opt_state.renumber_revs = TRUE;
1399251881Speter          break;
1400251881Speter        case svndumpfilter__preserve_revprops:
1401251881Speter          opt_state.preserve_revprops = TRUE;
1402251881Speter          break;
1403251881Speter        case svndumpfilter__skip_missing_merge_sources:
1404251881Speter          opt_state.skip_missing_merge_sources = TRUE;
1405251881Speter          break;
1406251881Speter        case svndumpfilter__targets:
1407251881Speter          opt_state.targets_file = opt_arg;
1408251881Speter          break;
1409251881Speter        default:
1410251881Speter          {
1411289180Speter            SVN_ERR(subcommand_help(NULL, NULL, pool));
1412289180Speter            *exit_code = EXIT_FAILURE;
1413289180Speter            return SVN_NO_ERROR;
1414251881Speter          }
1415251881Speter        }  /* close `switch' */
1416251881Speter    }  /* close `while' */
1417251881Speter
1418251881Speter  /* Disallow simultaneous use of both --drop-empty-revs and
1419251881Speter     --drop-all-empty-revs. */
1420251881Speter  if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
1421251881Speter    {
1422289180Speter      return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS,
1423289180Speter                              NULL,
1424289180Speter                              _("--drop-empty-revs cannot be used with "
1425289180Speter                                "--drop-all-empty-revs"));
1426251881Speter    }
1427251881Speter
1428251881Speter  /* If the user asked for help, then the rest of the arguments are
1429251881Speter     the names of subcommands to get help on (if any), or else they're
1430251881Speter     just typos/mistakes.  Whatever the case, the subcommand to
1431251881Speter     actually run is subcommand_help(). */
1432251881Speter  if (opt_state.help)
1433251881Speter    subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
1434251881Speter
1435251881Speter  /* If we're not running the `help' subcommand, then look for a
1436251881Speter     subcommand in the first argument. */
1437251881Speter  if (subcommand == NULL)
1438251881Speter    {
1439251881Speter      if (os->ind >= os->argc)
1440251881Speter        {
1441251881Speter          if (opt_state.version)
1442251881Speter            {
1443251881Speter              /* Use the "help" subcommand to handle the "--version" option. */
1444251881Speter              static const svn_opt_subcommand_desc2_t pseudo_cmd =
1445251881Speter                { "--version", subcommand_help, {0}, "",
1446251881Speter                  {svndumpfilter__version,  /* must accept its own option */
1447251881Speter                   svndumpfilter__quiet,
1448251881Speter                  } };
1449251881Speter
1450251881Speter              subcommand = &pseudo_cmd;
1451251881Speter            }
1452251881Speter          else
1453251881Speter            {
1454251881Speter              svn_error_clear(svn_cmdline_fprintf
1455251881Speter                              (stderr, pool,
1456251881Speter                               _("Subcommand argument required\n")));
1457289180Speter              SVN_ERR(subcommand_help(NULL, NULL, pool));
1458289180Speter              *exit_code = EXIT_FAILURE;
1459289180Speter              return SVN_NO_ERROR;
1460251881Speter            }
1461251881Speter        }
1462251881Speter      else
1463251881Speter        {
1464251881Speter          const char *first_arg = os->argv[os->ind++];
1465251881Speter          subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
1466251881Speter          if (subcommand == NULL)
1467251881Speter            {
1468251881Speter              const char* first_arg_utf8;
1469289180Speter              SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
1470289180Speter                                              pool));
1471251881Speter
1472251881Speter              svn_error_clear(
1473251881Speter                svn_cmdline_fprintf(stderr, pool,
1474251881Speter                                    _("Unknown subcommand: '%s'\n"),
1475251881Speter                                    first_arg_utf8));
1476289180Speter              SVN_ERR(subcommand_help(NULL, NULL, pool));
1477289180Speter              *exit_code = EXIT_FAILURE;
1478289180Speter              return SVN_NO_ERROR;
1479251881Speter            }
1480251881Speter        }
1481251881Speter    }
1482251881Speter
1483251881Speter  /* If there's a second argument, it's probably [one of] prefixes.
1484251881Speter     Every subcommand except `help' requires at least one, so we parse
1485251881Speter     them out here and store in opt_state. */
1486251881Speter
1487251881Speter  if (subcommand->cmd_func != subcommand_help)
1488251881Speter    {
1489251881Speter
1490251881Speter      opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
1491251881Speter                                          sizeof(const char *));
1492251881Speter      for (i = os->ind ; i< os->argc; i++)
1493251881Speter        {
1494251881Speter          const char *prefix;
1495251881Speter
1496251881Speter          /* Ensure that each prefix is UTF8-encoded, in internal
1497251881Speter             style, and absolute. */
1498289180Speter          SVN_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
1499251881Speter          prefix = svn_relpath__internal_style(prefix, pool);
1500251881Speter          if (prefix[0] != '/')
1501289180Speter            prefix = apr_pstrcat(pool, "/", prefix, SVN_VA_NULL);
1502251881Speter          APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1503251881Speter        }
1504251881Speter
1505251881Speter      if (opt_state.targets_file)
1506251881Speter        {
1507251881Speter          svn_stringbuf_t *buffer, *buffer_utf8;
1508251881Speter          const char *utf8_targets_file;
1509251881Speter          apr_array_header_t *targets = apr_array_make(pool, 0,
1510251881Speter                                                       sizeof(const char *));
1511251881Speter
1512251881Speter          /* We need to convert to UTF-8 now, even before we divide
1513251881Speter             the targets into an array, because otherwise we wouldn't
1514251881Speter             know what delimiter to use for svn_cstring_split().  */
1515251881Speter
1516289180Speter          SVN_ERR(svn_utf_cstring_to_utf8(&utf8_targets_file,
1517289180Speter                                          opt_state.targets_file, pool));
1518251881Speter
1519289180Speter          SVN_ERR(svn_stringbuf_from_file2(&buffer, utf8_targets_file,
1520289180Speter                                           pool));
1521289180Speter          SVN_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1522251881Speter
1523251881Speter          targets = apr_array_append(pool,
1524251881Speter                         svn_cstring_split(buffer_utf8->data, "\n\r",
1525251881Speter                                           TRUE, pool),
1526251881Speter                         targets);
1527251881Speter
1528251881Speter          for (i = 0; i < targets->nelts; i++)
1529251881Speter            {
1530251881Speter              const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
1531251881Speter              if (prefix[0] != '/')
1532289180Speter                prefix = apr_pstrcat(pool, "/", prefix, SVN_VA_NULL);
1533251881Speter              APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1534251881Speter            }
1535251881Speter        }
1536251881Speter
1537251881Speter      if (apr_is_empty_array(opt_state.prefixes))
1538251881Speter        {
1539251881Speter          svn_error_clear(svn_cmdline_fprintf
1540251881Speter                          (stderr, pool,
1541251881Speter                           _("\nError: no prefixes supplied.\n")));
1542289180Speter          *exit_code = EXIT_FAILURE;
1543289180Speter          return SVN_NO_ERROR;
1544251881Speter        }
1545251881Speter    }
1546251881Speter
1547251881Speter
1548251881Speter  /* Check that the subcommand wasn't passed any inappropriate options. */
1549251881Speter  for (i = 0; i < received_opts->nelts; i++)
1550251881Speter    {
1551251881Speter      opt_id = APR_ARRAY_IDX(received_opts, i, int);
1552251881Speter
1553251881Speter      /* All commands implicitly accept --help, so just skip over this
1554251881Speter         when we see it. Note that we don't want to include this option
1555251881Speter         in their "accepted options" list because it would be awfully
1556251881Speter         redundant to display it in every commands' help text. */
1557251881Speter      if (opt_id == 'h' || opt_id == '?')
1558251881Speter        continue;
1559251881Speter
1560251881Speter      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1561251881Speter        {
1562251881Speter          const char *optstr;
1563251881Speter          const apr_getopt_option_t *badopt =
1564251881Speter            svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
1565251881Speter                                          pool);
1566251881Speter          svn_opt_format_option(&optstr, badopt, FALSE, pool);
1567251881Speter          if (subcommand->name[0] == '-')
1568289180Speter            SVN_ERR(subcommand_help(NULL, NULL, pool));
1569251881Speter          else
1570251881Speter            svn_error_clear(svn_cmdline_fprintf
1571251881Speter                            (stderr, pool,
1572251881Speter                             _("Subcommand '%s' doesn't accept option '%s'\n"
1573251881Speter                               "Type 'svndumpfilter help %s' for usage.\n"),
1574251881Speter                             subcommand->name, optstr, subcommand->name));
1575289180Speter          *exit_code = EXIT_FAILURE;
1576289180Speter          return SVN_NO_ERROR;
1577251881Speter        }
1578251881Speter    }
1579251881Speter
1580251881Speter  /* Run the subcommand. */
1581251881Speter  err = (*subcommand->cmd_func)(os, &opt_state, pool);
1582251881Speter  if (err)
1583251881Speter    {
1584251881Speter      /* For argument-related problems, suggest using the 'help'
1585251881Speter         subcommand. */
1586251881Speter      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1587251881Speter          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1588251881Speter        {
1589251881Speter          err = svn_error_quick_wrap(err,
1590251881Speter                                     _("Try 'svndumpfilter help' for more "
1591251881Speter                                       "info"));
1592251881Speter        }
1593289180Speter      return err;
1594251881Speter    }
1595289180Speter
1596289180Speter  return SVN_NO_ERROR;
1597289180Speter}
1598289180Speter
1599289180Speterint
1600289180Spetermain(int argc, const char *argv[])
1601289180Speter{
1602289180Speter  apr_pool_t *pool;
1603289180Speter  int exit_code = EXIT_SUCCESS;
1604289180Speter  svn_error_t *err;
1605289180Speter
1606289180Speter  /* Initialize the app. */
1607289180Speter  if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1608289180Speter    return EXIT_FAILURE;
1609289180Speter
1610289180Speter  /* Create our top-level pool.  Use a separate mutexless allocator,
1611289180Speter   * given this application is single threaded.
1612289180Speter   */
1613289180Speter  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1614289180Speter
1615289180Speter  err = sub_main(&exit_code, argc, argv, pool);
1616289180Speter
1617289180Speter  /* Flush stdout and report if it fails. It would be flushed on exit anyway
1618289180Speter     but this makes sure that output is not silently lost if it fails. */
1619289180Speter  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1620289180Speter
1621289180Speter  if (err)
1622251881Speter    {
1623289180Speter      exit_code = EXIT_FAILURE;
1624289180Speter      svn_cmdline_handle_exit_error(err, NULL, "svndumpfilter: ");
1625289180Speter    }
1626251881Speter
1627289180Speter  svn_pool_destroy(pool);
1628289180Speter  return exit_code;
1629251881Speter}
1630