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
46362181Sdim#include "private/svn_dirent_uri_private.h"
47289180Speter#include "private/svn_repos_private.h"
48251881Speter#include "private/svn_mergeinfo_private.h"
49251881Speter#include "private/svn_cmdline_private.h"
50289180Speter#include "private/svn_sorts_private.h"
51251881Speter
52251881Speter/*** Code. ***/
53251881Speter
54251881Speter/* Writes a property in dumpfile format to given stringbuf. */
55251881Speterstatic void
56251881Speterwrite_prop_to_stringbuf(svn_stringbuf_t *strbuf,
57251881Speter                        const char *name,
58251881Speter                        const svn_string_t *value)
59251881Speter{
60251881Speter  int bytes_used;
61251881Speter  size_t namelen;
62251881Speter  char buf[SVN_KEYLINE_MAXLEN];
63251881Speter
64251881Speter  /* Output name length, then name. */
65251881Speter  namelen = strlen(name);
66251881Speter  svn_stringbuf_appendbytes(strbuf, "K ", 2);
67251881Speter
68251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
69251881Speter  svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
70251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
71251881Speter
72251881Speter  svn_stringbuf_appendbytes(strbuf, name, namelen);
73251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
74251881Speter
75251881Speter  /* Output value length, then value. */
76251881Speter  svn_stringbuf_appendbytes(strbuf, "V ", 2);
77251881Speter
78251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len);
79251881Speter  svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
80251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
81251881Speter
82251881Speter  svn_stringbuf_appendbytes(strbuf, value->data, value->len);
83251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
84251881Speter}
85251881Speter
86251881Speter
87251881Speter/* Writes a property deletion in dumpfile format to given stringbuf. */
88251881Speterstatic void
89251881Speterwrite_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
90251881Speter                           const char *name)
91251881Speter{
92251881Speter  int bytes_used;
93251881Speter  size_t namelen;
94251881Speter  char buf[SVN_KEYLINE_MAXLEN];
95251881Speter
96251881Speter  /* Output name length, then name. */
97251881Speter  namelen = strlen(name);
98251881Speter  svn_stringbuf_appendbytes(*strbuf, "D ", 2);
99251881Speter
100251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
101251881Speter  svn_stringbuf_appendbytes(*strbuf, buf, bytes_used);
102251881Speter  svn_stringbuf_appendbyte(*strbuf, '\n');
103251881Speter
104251881Speter  svn_stringbuf_appendbytes(*strbuf, name, namelen);
105251881Speter  svn_stringbuf_appendbyte(*strbuf, '\n');
106251881Speter}
107251881Speter
108251881Speter
109251881Speter/* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
110251881Speter * Return TRUE if any prefix is a prefix of PATH (matching whole path
111251881Speter * components); FALSE otherwise.
112251881Speter * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
113362181Sdim/* This function is a duplicate of svnadmin.c:ary_prefix_match(). */
114251881Speterstatic svn_boolean_t
115251881Speterary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
116251881Speter{
117251881Speter  int i;
118251881Speter  size_t path_len = strlen(path);
119251881Speter
120251881Speter  for (i = 0; i < pfxlist->nelts; i++)
121251881Speter    {
122251881Speter      const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
123251881Speter      size_t pfx_len = strlen(pfx);
124251881Speter
125251881Speter      if (path_len < pfx_len)
126251881Speter        continue;
127251881Speter      if (strncmp(path, pfx, pfx_len) == 0
128251881Speter          && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
129251881Speter        return TRUE;
130251881Speter    }
131251881Speter
132251881Speter  return FALSE;
133251881Speter}
134251881Speter
135251881Speter
136251881Speter/* Check whether we need to skip this PATH based on its presence in
137251881Speter   the PREFIXES list, and the DO_EXCLUDE option.
138251881Speter   PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
139251881Speterstatic APR_INLINE svn_boolean_t
140251881Speterskip_path(const char *path, const apr_array_header_t *prefixes,
141251881Speter          svn_boolean_t do_exclude, svn_boolean_t glob)
142251881Speter{
143251881Speter  const svn_boolean_t matches =
144251881Speter    (glob
145251881Speter     ? svn_cstring_match_glob_list(path, prefixes)
146251881Speter     : ary_prefix_match(prefixes, path));
147251881Speter
148251881Speter  /* NXOR */
149251881Speter  return (matches ? do_exclude : !do_exclude);
150251881Speter}
151251881Speter
152251881Speter
153251881Speter
154251881Speter/* Note: the input stream parser calls us with events.
155251881Speter   Output of the filtered dump occurs for the most part streamily with the
156251881Speter   event callbacks, to avoid caching large quantities of data in memory.
157251881Speter   The exceptions this are:
158251881Speter   - All revision data (headers and props) must be cached until a non-skipped
159251881Speter     node within the revision is found, or the revision is closed.
160251881Speter   - Node headers and props must be cached until all props have been received
161251881Speter     (to allow the Prop-content-length to be found). This is signalled either
162251881Speter     by the node text arriving, or the node being closed.
163251881Speter   The writing_begun members of the associated object batons track the state.
164251881Speter   output_revision() and output_node() are called to cause this flushing of
165251881Speter   cached data to occur.
166251881Speter*/
167251881Speter
168251881Speter
169251881Speter/* Filtering batons */
170251881Speter
171251881Speterstruct revmap_t
172251881Speter{
173251881Speter  svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
174251881Speter  svn_boolean_t was_dropped; /* Was this revision dropped? */
175251881Speter};
176251881Speter
177251881Speterstruct parse_baton_t
178251881Speter{
179251881Speter  /* Command-line options values. */
180251881Speter  svn_boolean_t do_exclude;
181251881Speter  svn_boolean_t quiet;
182251881Speter  svn_boolean_t glob;
183251881Speter  svn_boolean_t drop_empty_revs;
184251881Speter  svn_boolean_t drop_all_empty_revs;
185251881Speter  svn_boolean_t do_renumber_revs;
186251881Speter  svn_boolean_t preserve_revprops;
187251881Speter  svn_boolean_t skip_missing_merge_sources;
188251881Speter  svn_boolean_t allow_deltas;
189251881Speter  apr_array_header_t *prefixes;
190251881Speter
191251881Speter  /* Input and output streams. */
192251881Speter  svn_stream_t *in_stream;
193251881Speter  svn_stream_t *out_stream;
194251881Speter
195251881Speter  /* State for the filtering process. */
196251881Speter  apr_int32_t rev_drop_count;
197251881Speter  apr_hash_t *dropped_nodes;
198251881Speter  apr_hash_t *renumber_history;  /* svn_revnum_t -> struct revmap_t */
199251881Speter  svn_revnum_t last_live_revision;
200251881Speter  /* The oldest original revision, greater than r0, in the input
201251881Speter     stream which was not filtered. */
202251881Speter  svn_revnum_t oldest_original_rev;
203251881Speter};
204251881Speter
205251881Speterstruct revision_baton_t
206251881Speter{
207251881Speter  /* Reference to the global parse baton. */
208251881Speter  struct parse_baton_t *pb;
209251881Speter
210251881Speter  /* Does this revision have node or prop changes? */
211251881Speter  svn_boolean_t has_nodes;
212251881Speter
213251881Speter  /* Did we drop any nodes? */
214251881Speter  svn_boolean_t had_dropped_nodes;
215251881Speter
216251881Speter  /* Written to output stream? */
217251881Speter  svn_boolean_t writing_begun;
218251881Speter
219251881Speter  /* The original and new (re-mapped) revision numbers. */
220251881Speter  svn_revnum_t rev_orig;
221251881Speter  svn_revnum_t rev_actual;
222251881Speter
223251881Speter  /* Pointers to dumpfile data. */
224289180Speter  apr_hash_t *original_headers;
225251881Speter  apr_hash_t *props;
226251881Speter};
227251881Speter
228251881Speterstruct node_baton_t
229251881Speter{
230251881Speter  /* Reference to the current revision baton. */
231251881Speter  struct revision_baton_t *rb;
232251881Speter
233251881Speter  /* Are we skipping this node? */
234251881Speter  svn_boolean_t do_skip;
235251881Speter
236251881Speter  /* Have we been instructed to change or remove props on, or change
237251881Speter     the text of, this node? */
238251881Speter  svn_boolean_t has_props;
239251881Speter  svn_boolean_t has_text;
240251881Speter
241251881Speter  /* Written to output stream? */
242251881Speter  svn_boolean_t writing_begun;
243251881Speter
244251881Speter  /* The text content length according to the dumpfile headers, because we
245251881Speter     need the length before we have the actual text. */
246251881Speter  svn_filesize_t tcl;
247251881Speter
248251881Speter  /* Pointers to dumpfile data. */
249289180Speter  svn_repos__dumpfile_headers_t *headers;
250251881Speter  svn_stringbuf_t *props;
251251881Speter
252251881Speter  /* Expect deltas? */
253251881Speter  svn_boolean_t has_prop_delta;
254251881Speter  svn_boolean_t has_text_delta;
255251881Speter
256251881Speter  /* We might need the node path in a parse error message. */
257251881Speter  char *node_path;
258289180Speter
259289180Speter  apr_pool_t *node_pool;
260251881Speter};
261251881Speter
262251881Speter
263251881Speter
264251881Speter/* Filtering vtable members */
265251881Speter
266251881Speter/* File-format stamp. */
267251881Speterstatic svn_error_t *
268251881Spetermagic_header_record(int version, void *parse_baton, apr_pool_t *pool)
269251881Speter{
270251881Speter  struct parse_baton_t *pb = parse_baton;
271251881Speter
272251881Speter  if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
273251881Speter    pb->allow_deltas = TRUE;
274251881Speter
275362181Sdim  SVN_ERR(svn_repos__dump_magic_header_record(pb->out_stream, version, pool));
276251881Speter
277251881Speter  return SVN_NO_ERROR;
278251881Speter}
279251881Speter
280251881Speter
281289180Speter/* Return a deep copy of a (char * -> char *) hash. */
282289180Speterstatic apr_hash_t *
283289180Speterheaders_dup(apr_hash_t *headers,
284289180Speter            apr_pool_t *pool)
285289180Speter{
286289180Speter  apr_hash_t *new_hash = apr_hash_make(pool);
287289180Speter  apr_hash_index_t *hi;
288289180Speter
289289180Speter  for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
290289180Speter    {
291289180Speter      const char *key = apr_hash_this_key(hi);
292289180Speter      const char *val = apr_hash_this_val(hi);
293289180Speter
294289180Speter      svn_hash_sets(new_hash, apr_pstrdup(pool, key), apr_pstrdup(pool, val));
295289180Speter    }
296289180Speter  return new_hash;
297289180Speter}
298289180Speter
299251881Speter/* New revision: set up revision_baton, decide if we skip it. */
300251881Speterstatic svn_error_t *
301251881Speternew_revision_record(void **revision_baton,
302251881Speter                    apr_hash_t *headers,
303251881Speter                    void *parse_baton,
304251881Speter                    apr_pool_t *pool)
305251881Speter{
306251881Speter  struct revision_baton_t *rb;
307251881Speter  const char *rev_orig;
308251881Speter
309251881Speter  *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t));
310251881Speter  rb = *revision_baton;
311251881Speter  rb->pb = parse_baton;
312251881Speter  rb->has_nodes = FALSE;
313251881Speter  rb->had_dropped_nodes = FALSE;
314251881Speter  rb->writing_begun = FALSE;
315251881Speter  rb->props = apr_hash_make(pool);
316289180Speter  rb->original_headers = headers_dup(headers, pool);
317251881Speter
318251881Speter  rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
319251881Speter  rb->rev_orig = SVN_STR_TO_REV(rev_orig);
320251881Speter
321251881Speter  if (rb->pb->do_renumber_revs)
322251881Speter    rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
323251881Speter  else
324251881Speter    rb->rev_actual = rb->rev_orig;
325251881Speter
326251881Speter  return SVN_NO_ERROR;
327251881Speter}
328251881Speter
329251881Speter
330251881Speter/* Output revision to dumpstream
331251881Speter   This may be called by new_node_record(), iff rb->has_nodes has been set
332251881Speter   to TRUE, or by close_revision() otherwise. This must only be called
333251881Speter   if rb->writing_begun is FALSE. */
334251881Speterstatic svn_error_t *
335251881Speteroutput_revision(struct revision_baton_t *rb)
336251881Speter{
337251881Speter  svn_boolean_t write_out_rev = FALSE;
338251881Speter  apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
339251881Speter  apr_pool_t *subpool = svn_pool_create(hash_pool);
340251881Speter
341251881Speter  rb->writing_begun = TRUE;
342251881Speter
343251881Speter  /* If this revision has no nodes left because the ones it had were
344251881Speter     dropped, and we are not dropping empty revisions, and we were not
345251881Speter     told to preserve revision props, then we want to fixup the
346251881Speter     revision props to only contain:
347251881Speter       - the date
348251881Speter       - a log message that reports that this revision is just stuffing. */
349251881Speter  if ((! rb->pb->preserve_revprops)
350251881Speter      && (! rb->has_nodes)
351251881Speter      && rb->had_dropped_nodes
352251881Speter      && (! rb->pb->drop_empty_revs)
353251881Speter      && (! rb->pb->drop_all_empty_revs))
354251881Speter    {
355251881Speter      apr_hash_t *old_props = rb->props;
356251881Speter      rb->props = apr_hash_make(hash_pool);
357251881Speter      svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE,
358251881Speter                    svn_hash_gets(old_props, SVN_PROP_REVISION_DATE));
359251881Speter      svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG,
360251881Speter                    svn_string_create(_("This is an empty revision for "
361251881Speter                                        "padding."), hash_pool));
362251881Speter    }
363251881Speter
364251881Speter  /* write out the revision */
365251881Speter  /* Revision is written out in the following cases:
366251881Speter     1. If the revision has nodes or
367251881Speter     it is revision 0 (Special case: To preserve the props on r0).
368251881Speter     2. --drop-empty-revs has been supplied,
369251881Speter     but revision has not all nodes dropped.
370251881Speter     3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied,
371251881Speter     write out the revision which has no nodes to begin with.
372251881Speter  */
373251881Speter  if (rb->has_nodes || (rb->rev_orig == 0))
374251881Speter    write_out_rev = TRUE;
375251881Speter  else if (rb->pb->drop_empty_revs)
376251881Speter    write_out_rev = ! rb->had_dropped_nodes;
377251881Speter  else if (! rb->pb->drop_all_empty_revs)
378251881Speter    write_out_rev = TRUE;
379251881Speter
380251881Speter  if (write_out_rev)
381251881Speter    {
382251881Speter      /* This revision is a keeper. */
383289180Speter      SVN_ERR(svn_repos__dump_revision_record(rb->pb->out_stream,
384289180Speter                                              rb->rev_actual,
385289180Speter                                              rb->original_headers,
386289180Speter                                              rb->props,
387289180Speter                                              FALSE /*props_section_always*/,
388289180Speter                                              subpool));
389251881Speter
390251881Speter      /* Stash the oldest original rev not dropped. */
391251881Speter      if (rb->rev_orig > 0
392251881Speter          && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
393251881Speter        rb->pb->oldest_original_rev = rb->rev_orig;
394251881Speter
395251881Speter      if (rb->pb->do_renumber_revs)
396251881Speter        {
397251881Speter          svn_revnum_t *rr_key;
398251881Speter          struct revmap_t *rr_val;
399251881Speter          apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
400251881Speter          rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
401251881Speter          rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
402251881Speter          *rr_key = rb->rev_orig;
403251881Speter          rr_val->rev = rb->rev_actual;
404251881Speter          rr_val->was_dropped = FALSE;
405251881Speter          apr_hash_set(rb->pb->renumber_history, rr_key,
406251881Speter                       sizeof(*rr_key), rr_val);
407251881Speter          rb->pb->last_live_revision = rb->rev_actual;
408251881Speter        }
409251881Speter
410251881Speter      if (! rb->pb->quiet)
411251881Speter        SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
412251881Speter                                    _("Revision %ld committed as %ld.\n"),
413251881Speter                                    rb->rev_orig, rb->rev_actual));
414251881Speter    }
415251881Speter  else
416251881Speter    {
417251881Speter      /* We're dropping this revision. */
418251881Speter      rb->pb->rev_drop_count++;
419251881Speter      if (rb->pb->do_renumber_revs)
420251881Speter        {
421251881Speter          svn_revnum_t *rr_key;
422251881Speter          struct revmap_t *rr_val;
423251881Speter          apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
424251881Speter          rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
425251881Speter          rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
426251881Speter          *rr_key = rb->rev_orig;
427251881Speter          rr_val->rev = rb->pb->last_live_revision;
428251881Speter          rr_val->was_dropped = TRUE;
429251881Speter          apr_hash_set(rb->pb->renumber_history, rr_key,
430251881Speter                       sizeof(*rr_key), rr_val);
431251881Speter        }
432251881Speter
433251881Speter      if (! rb->pb->quiet)
434251881Speter        SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
435251881Speter                                    _("Revision %ld skipped.\n"),
436251881Speter                                    rb->rev_orig));
437251881Speter    }
438251881Speter  svn_pool_destroy(subpool);
439251881Speter  return SVN_NO_ERROR;
440251881Speter}
441251881Speter
442251881Speter
443251881Speter/* UUID record here: dump it, as we do not filter them. */
444251881Speterstatic svn_error_t *
445251881Speteruuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
446251881Speter{
447251881Speter  struct parse_baton_t *pb = parse_baton;
448362181Sdim
449362181Sdim  SVN_ERR(svn_repos__dump_uuid_header_record(pb->out_stream, uuid, pool));
450251881Speter  return SVN_NO_ERROR;
451251881Speter}
452251881Speter
453251881Speter
454251881Speter/* New node here. Set up node_baton by copying headers. */
455251881Speterstatic svn_error_t *
456251881Speternew_node_record(void **node_baton,
457251881Speter                apr_hash_t *headers,
458251881Speter                void *rev_baton,
459251881Speter                apr_pool_t *pool)
460251881Speter{
461251881Speter  struct parse_baton_t *pb;
462251881Speter  struct node_baton_t *nb;
463251881Speter  char *node_path, *copyfrom_path;
464251881Speter  apr_hash_index_t *hi;
465251881Speter  const char *tcl;
466251881Speter
467251881Speter  *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
468251881Speter  nb          = *node_baton;
469251881Speter  nb->rb      = rev_baton;
470289180Speter  nb->node_pool = pool;
471251881Speter  pb          = nb->rb->pb;
472251881Speter
473251881Speter  node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH);
474251881Speter  copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH);
475251881Speter
476251881Speter  /* Ensure that paths start with a leading '/'. */
477251881Speter  if (node_path[0] != '/')
478289180Speter    node_path = apr_pstrcat(pool, "/", node_path, SVN_VA_NULL);
479251881Speter  if (copyfrom_path && copyfrom_path[0] != '/')
480289180Speter    copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, SVN_VA_NULL);
481251881Speter
482251881Speter  nb->do_skip = skip_path(node_path, pb->prefixes,
483251881Speter                          pb->do_exclude, pb->glob);
484251881Speter
485251881Speter  /* If we're skipping the node, take note of path, discarding the
486251881Speter     rest.  */
487251881Speter  if (nb->do_skip)
488251881Speter    {
489251881Speter      svn_hash_sets(pb->dropped_nodes,
490251881Speter                    apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
491251881Speter                                node_path),
492251881Speter                    (void *)1);
493251881Speter      nb->rb->had_dropped_nodes = TRUE;
494251881Speter    }
495251881Speter  else
496251881Speter    {
497266731Speter      const char *kind;
498266731Speter      const char *action;
499266731Speter
500251881Speter      tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
501251881Speter
502251881Speter      /* Test if this node was copied from dropped source. */
503251881Speter      if (copyfrom_path &&
504251881Speter          skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
505251881Speter        {
506251881Speter          /* This node was copied from a dropped source.
507251881Speter             We have a problem, since we did not want to drop this node too.
508251881Speter
509251881Speter             However, there is one special case we'll handle.  If the node is
510251881Speter             a file, and this was a copy-and-modify operation, then the
511251881Speter             dumpfile should contain the new contents of the file.  In this
512251881Speter             scenario, we'll just do an add without history using the new
513251881Speter             contents.  */
514251881Speter          kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
515251881Speter
516251881Speter          /* If there is a Text-content-length header, and the kind is
517251881Speter             "file", we just fallback to an add without history. */
518251881Speter          if (tcl && (strcmp(kind, "file") == 0))
519251881Speter            {
520251881Speter              svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
521251881Speter                            NULL);
522251881Speter              svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
523251881Speter                            NULL);
524251881Speter              copyfrom_path = NULL;
525251881Speter            }
526251881Speter          /* Else, this is either a directory or a file whose contents we
527251881Speter             don't have readily available.  */
528251881Speter          else
529251881Speter            {
530251881Speter              return svn_error_createf
531251881Speter                (SVN_ERR_INCOMPLETE_DATA, 0,
532362181Sdim                 _("Invalid copy source path '%s' for '%s'"),
533362181Sdim                 copyfrom_path, node_path);
534251881Speter            }
535251881Speter        }
536251881Speter
537251881Speter      nb->has_props = FALSE;
538251881Speter      nb->has_text = FALSE;
539251881Speter      nb->has_prop_delta = FALSE;
540251881Speter      nb->has_text_delta = FALSE;
541251881Speter      nb->writing_begun = FALSE;
542251881Speter      nb->tcl = tcl ? svn__atoui64(tcl) : 0;
543289180Speter      nb->headers = svn_repos__dumpfile_headers_create(pool);
544251881Speter      nb->props = svn_stringbuf_create_empty(pool);
545251881Speter      nb->node_path = apr_pstrdup(pool, node_path);
546251881Speter
547251881Speter      /* Now we know for sure that we have a node that will not be
548251881Speter         skipped, flush the revision if it has not already been done. */
549251881Speter      nb->rb->has_nodes = TRUE;
550251881Speter      if (! nb->rb->writing_begun)
551251881Speter        SVN_ERR(output_revision(nb->rb));
552251881Speter
553266731Speter      /* A node record is required to begin with 'Node-path', skip the
554266731Speter         leading '/' to match the form used by 'svnadmin dump'. */
555289180Speter      svn_repos__dumpfile_header_push(
556289180Speter        nb->headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_path + 1);
557266731Speter
558266731Speter      /* Node-kind is next and is optional. */
559266731Speter      kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
560266731Speter      if (kind)
561289180Speter        svn_repos__dumpfile_header_push(
562289180Speter          nb->headers, SVN_REPOS_DUMPFILE_NODE_KIND, kind);
563266731Speter
564266731Speter      /* Node-action is next and required. */
565266731Speter      action = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION);
566266731Speter      if (action)
567289180Speter        svn_repos__dumpfile_header_push(
568289180Speter          nb->headers, SVN_REPOS_DUMPFILE_NODE_ACTION, action);
569266731Speter      else
570266731Speter        return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
571266731Speter                                 _("Missing Node-action for path '%s'"),
572266731Speter                                 node_path);
573266731Speter
574251881Speter      for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
575251881Speter        {
576289180Speter          const char *key = apr_hash_this_key(hi);
577289180Speter          const char *val = apr_hash_this_val(hi);
578251881Speter
579251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
580251881Speter              && (!strcmp(val, "true")))
581251881Speter            nb->has_prop_delta = TRUE;
582251881Speter
583251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
584251881Speter              && (!strcmp(val, "true")))
585251881Speter            nb->has_text_delta = TRUE;
586251881Speter
587251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
588251881Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
589266731Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH))
590266731Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_PATH))
591266731Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_KIND))
592266731Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_ACTION)))
593251881Speter            continue;
594251881Speter
595251881Speter          /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
596251881Speter             The number points to some revision in the past. We keep track
597251881Speter             of revision renumbering in an apr_hash, which maps original
598251881Speter             revisions to new ones. Dropped revision are mapped to -1.
599251881Speter             This should never happen here.
600251881Speter          */
601251881Speter          if (pb->do_renumber_revs
602251881Speter              && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
603251881Speter            {
604251881Speter              svn_revnum_t cf_orig_rev;
605251881Speter              struct revmap_t *cf_renum_val;
606251881Speter
607251881Speter              cf_orig_rev = SVN_STR_TO_REV(val);
608251881Speter              cf_renum_val = apr_hash_get(pb->renumber_history,
609251881Speter                                          &cf_orig_rev,
610362181Sdim                                          sizeof(cf_orig_rev));
611251881Speter              if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
612251881Speter                return svn_error_createf
613251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
614362181Sdim                   _("No valid copyfrom revision in filtered stream for '%s'"),
615362181Sdim                   node_path);
616289180Speter              svn_repos__dumpfile_header_pushf(
617289180Speter                nb->headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
618289180Speter                "%ld", cf_renum_val->rev);
619251881Speter              continue;
620251881Speter            }
621251881Speter
622251881Speter          /* passthru: put header straight to output */
623289180Speter          svn_repos__dumpfile_header_push(nb->headers, key, val);
624251881Speter        }
625251881Speter    }
626251881Speter
627251881Speter  return SVN_NO_ERROR;
628251881Speter}
629251881Speter
630251881Speter
631251881Speter/* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
632251881Speter   sources or renumbering revisions in rangelists as appropriate, and
633251881Speter   return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
634251881Speter   POOL). */
635251881Speterstatic svn_error_t *
636251881Speteradjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
637251881Speter                 struct revision_baton_t *rb, apr_pool_t *pool)
638251881Speter{
639251881Speter  apr_hash_t *mergeinfo;
640251881Speter  apr_hash_t *final_mergeinfo = apr_hash_make(pool);
641251881Speter  apr_hash_index_t *hi;
642251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
643251881Speter
644251881Speter  SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
645251881Speter
646251881Speter  /* Issue #3020: If we are skipping missing merge sources, then also
647251881Speter     filter mergeinfo ranges as old or older than the oldest revision in the
648251881Speter     dump stream.  Those older than the oldest obviously refer to history
649251881Speter     outside of the dump stream.  The oldest rev itself is present in the
650251881Speter     dump, but cannot be a valid merge source revision since it is the
651251881Speter     start of all history.  E.g. if we dump -r100:400 then dumpfilter the
652251881Speter     result with --skip-missing-merge-sources, any mergeinfo with revision
653251881Speter     100 implies a change of -r99:100, but r99 is part of the history we
654286506Speter     want filtered.
655251881Speter
656251881Speter     If the oldest rev is r0 then there is nothing to filter. */
657289180Speter
658289180Speter  /* ### This seems to cater only for use cases where the revisions being
659289180Speter         processed are not following on from revisions that will already
660289180Speter         exist in the destination repository. If the revisions being
661289180Speter         processed do follow on, then we might want to keep the mergeinfo
662289180Speter         that refers to those older revisions. */
663289180Speter
664251881Speter  if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
665251881Speter    SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
666251881Speter      &mergeinfo, mergeinfo,
667251881Speter      rb->pb->oldest_original_rev, 0,
668251881Speter      FALSE, subpool, subpool));
669251881Speter
670251881Speter  for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
671251881Speter    {
672289180Speter      const char *merge_source = apr_hash_this_key(hi);
673289180Speter      svn_rangelist_t *rangelist = apr_hash_this_val(hi);
674251881Speter      struct parse_baton_t *pb = rb->pb;
675251881Speter
676251881Speter      /* Determine whether the merge_source is a part of the prefix. */
677251881Speter      if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
678251881Speter        {
679251881Speter          if (pb->skip_missing_merge_sources)
680251881Speter            continue;
681251881Speter          else
682251881Speter            return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
683251881Speter                                     _("Missing merge source path '%s'; try "
684251881Speter                                       "with --skip-missing-merge-sources"),
685251881Speter                                     merge_source);
686251881Speter        }
687251881Speter
688251881Speter      /* Possibly renumber revisions in merge source's rangelist. */
689251881Speter      if (pb->do_renumber_revs)
690251881Speter        {
691251881Speter          int i;
692251881Speter
693251881Speter          for (i = 0; i < rangelist->nelts; i++)
694251881Speter            {
695251881Speter              struct revmap_t *revmap_start;
696251881Speter              struct revmap_t *revmap_end;
697251881Speter              svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
698251881Speter                                                       svn_merge_range_t *);
699251881Speter
700251881Speter              revmap_start = apr_hash_get(pb->renumber_history,
701362181Sdim                                          &range->start, sizeof(range->start));
702251881Speter              if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
703251881Speter                return svn_error_createf
704251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
705251881Speter                   _("No valid revision range 'start' in filtered stream"));
706251881Speter
707251881Speter              revmap_end = apr_hash_get(pb->renumber_history,
708362181Sdim                                        &range->end, sizeof(range->end));
709251881Speter              if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
710251881Speter                return svn_error_createf
711251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
712251881Speter                   _("No valid revision range 'end' in filtered stream"));
713251881Speter
714251881Speter              range->start = revmap_start->rev;
715251881Speter              range->end = revmap_end->rev;
716251881Speter            }
717251881Speter        }
718251881Speter      svn_hash_sets(final_mergeinfo, merge_source, rangelist);
719251881Speter    }
720251881Speter
721286506Speter  SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
722251881Speter  SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
723251881Speter  svn_pool_destroy(subpool);
724251881Speter
725251881Speter  return SVN_NO_ERROR;
726251881Speter}
727251881Speter
728251881Speter
729251881Speterstatic svn_error_t *
730251881Speterset_revision_property(void *revision_baton,
731251881Speter                      const char *name,
732251881Speter                      const svn_string_t *value)
733251881Speter{
734251881Speter  struct revision_baton_t *rb = revision_baton;
735251881Speter  apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
736251881Speter
737251881Speter  svn_hash_sets(rb->props,
738251881Speter                apr_pstrdup(hash_pool, name),
739251881Speter                svn_string_dup(value, hash_pool));
740251881Speter  return SVN_NO_ERROR;
741251881Speter}
742251881Speter
743251881Speter
744251881Speterstatic svn_error_t *
745251881Speterset_node_property(void *node_baton,
746251881Speter                  const char *name,
747251881Speter                  const svn_string_t *value)
748251881Speter{
749251881Speter  struct node_baton_t *nb = node_baton;
750251881Speter  struct revision_baton_t *rb = nb->rb;
751251881Speter
752251881Speter  if (nb->do_skip)
753251881Speter    return SVN_NO_ERROR;
754251881Speter
755289180Speter  /* Try to detect if a delta-mode property occurs unexpectedly. HAS_PROPS
756289180Speter     can be false here only if the parser didn't call remove_node_props(),
757289180Speter     so this may indicate a bug rather than bad data. */
758251881Speter  if (! (nb->has_props || nb->has_prop_delta))
759251881Speter    return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
760251881Speter                             _("Delta property block detected, but deltas "
761251881Speter                               "are not enabled for node '%s' in original "
762251881Speter                               "revision %ld"),
763251881Speter                             nb->node_path, rb->rev_orig);
764251881Speter
765251881Speter  if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
766251881Speter    {
767251881Speter      svn_string_t *filtered_mergeinfo;  /* Avoid compiler warning. */
768251881Speter      apr_pool_t *pool = apr_hash_pool_get(rb->props);
769251881Speter      SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
770251881Speter      value = filtered_mergeinfo;
771251881Speter    }
772251881Speter
773251881Speter  nb->has_props = TRUE;
774251881Speter  write_prop_to_stringbuf(nb->props, name, value);
775251881Speter
776251881Speter  return SVN_NO_ERROR;
777251881Speter}
778251881Speter
779251881Speter
780251881Speterstatic svn_error_t *
781251881Speterdelete_node_property(void *node_baton, const char *name)
782251881Speter{
783251881Speter  struct node_baton_t *nb = node_baton;
784251881Speter  struct revision_baton_t *rb = nb->rb;
785251881Speter
786251881Speter  if (nb->do_skip)
787251881Speter    return SVN_NO_ERROR;
788251881Speter
789251881Speter  if (!nb->has_prop_delta)
790251881Speter    return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
791251881Speter                             _("Delta property block detected, but deltas "
792251881Speter                               "are not enabled for node '%s' in original "
793251881Speter                               "revision %ld"),
794251881Speter                             nb->node_path, rb->rev_orig);
795251881Speter
796251881Speter  nb->has_props = TRUE;
797251881Speter  write_propdel_to_stringbuf(&(nb->props), name);
798251881Speter
799251881Speter  return SVN_NO_ERROR;
800251881Speter}
801251881Speter
802251881Speter
803289180Speter/* The parser calls this method if the node record has a non-delta
804289180Speter * property content section, before any calls to set_node_property().
805289180Speter * If the node record uses property deltas, this is not called.
806289180Speter */
807251881Speterstatic svn_error_t *
808251881Speterremove_node_props(void *node_baton)
809251881Speter{
810251881Speter  struct node_baton_t *nb = node_baton;
811251881Speter
812251881Speter  /* In this case, not actually indicating that the node *has* props,
813289180Speter     rather that it has a property content section. */
814251881Speter  nb->has_props = TRUE;
815251881Speter
816251881Speter  return SVN_NO_ERROR;
817251881Speter}
818251881Speter
819251881Speter
820251881Speterstatic svn_error_t *
821251881Speterset_fulltext(svn_stream_t **stream, void *node_baton)
822251881Speter{
823251881Speter  struct node_baton_t *nb = node_baton;
824251881Speter
825251881Speter  if (!nb->do_skip)
826251881Speter    {
827251881Speter      nb->has_text = TRUE;
828251881Speter      if (! nb->writing_begun)
829289180Speter        {
830289180Speter          nb->writing_begun = TRUE;
831289180Speter          if (nb->has_props)
832289180Speter            {
833289180Speter              svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
834289180Speter            }
835289180Speter          SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
836289180Speter                                              nb->headers,
837289180Speter                                              nb->has_props ? nb->props : NULL,
838289180Speter                                              nb->has_text,
839289180Speter                                              nb->tcl,
840289180Speter                                              TRUE /*content_length_always*/,
841289180Speter                                              nb->node_pool));
842289180Speter        }
843251881Speter      *stream = nb->rb->pb->out_stream;
844251881Speter    }
845251881Speter
846251881Speter  return SVN_NO_ERROR;
847251881Speter}
848251881Speter
849251881Speter
850251881Speter/* Finalize node */
851251881Speterstatic svn_error_t *
852251881Speterclose_node(void *node_baton)
853251881Speter{
854251881Speter  struct node_baton_t *nb = node_baton;
855251881Speter  apr_size_t len = 2;
856251881Speter
857251881Speter  /* Get out of here if we can. */
858251881Speter  if (nb->do_skip)
859251881Speter    return SVN_NO_ERROR;
860251881Speter
861251881Speter  /* If the node was not flushed already to output its text, do it now. */
862251881Speter  if (! nb->writing_begun)
863289180Speter    {
864289180Speter      nb->writing_begun = TRUE;
865289180Speter      if (nb->has_props)
866289180Speter        {
867289180Speter          svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
868289180Speter        }
869289180Speter      SVN_ERR(svn_repos__dump_node_record(nb->rb->pb->out_stream,
870289180Speter                                          nb->headers,
871289180Speter                                          nb->has_props ? nb->props : NULL,
872289180Speter                                          nb->has_text,
873289180Speter                                          nb->tcl,
874289180Speter                                          TRUE /*content_length_always*/,
875289180Speter                                          nb->node_pool));
876289180Speter    }
877251881Speter
878251881Speter  /* put an end to node. */
879251881Speter  SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
880251881Speter
881251881Speter  return SVN_NO_ERROR;
882251881Speter}
883251881Speter
884251881Speter
885251881Speter/* Finalize revision */
886251881Speterstatic svn_error_t *
887251881Speterclose_revision(void *revision_baton)
888251881Speter{
889251881Speter  struct revision_baton_t *rb = revision_baton;
890251881Speter
891251881Speter  /* If no node has yet flushed the revision, do it now. */
892251881Speter  if (! rb->writing_begun)
893251881Speter    return output_revision(rb);
894251881Speter  else
895251881Speter    return SVN_NO_ERROR;
896251881Speter}
897251881Speter
898251881Speter
899251881Speter/* Filtering vtable */
900289180Speterstatic svn_repos_parse_fns3_t filtering_vtable =
901251881Speter  {
902251881Speter    magic_header_record,
903251881Speter    uuid_record,
904251881Speter    new_revision_record,
905251881Speter    new_node_record,
906251881Speter    set_revision_property,
907251881Speter    set_node_property,
908251881Speter    delete_node_property,
909251881Speter    remove_node_props,
910251881Speter    set_fulltext,
911251881Speter    NULL,
912251881Speter    close_node,
913251881Speter    close_revision
914251881Speter  };
915251881Speter
916251881Speter
917251881Speter
918251881Speter/** Subcommands. **/
919251881Speter
920251881Speterstatic svn_opt_subcommand_t
921251881Speter  subcommand_help,
922251881Speter  subcommand_exclude,
923251881Speter  subcommand_include;
924251881Speter
925251881Speterenum
926251881Speter  {
927251881Speter    svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
928251881Speter    svndumpfilter__drop_all_empty_revs,
929251881Speter    svndumpfilter__renumber_revs,
930251881Speter    svndumpfilter__preserve_revprops,
931251881Speter    svndumpfilter__skip_missing_merge_sources,
932251881Speter    svndumpfilter__targets,
933251881Speter    svndumpfilter__quiet,
934251881Speter    svndumpfilter__glob,
935251881Speter    svndumpfilter__version
936251881Speter  };
937251881Speter
938251881Speter/* Option codes and descriptions.
939251881Speter *
940251881Speter * The entire list must be terminated with an entry of nulls.
941251881Speter */
942251881Speterstatic const apr_getopt_option_t options_table[] =
943251881Speter  {
944251881Speter    {"help",          'h', 0,
945251881Speter     N_("show help on a subcommand")},
946251881Speter
947251881Speter    {NULL,            '?', 0,
948251881Speter     N_("show help on a subcommand")},
949251881Speter
950251881Speter    {"version",            svndumpfilter__version, 0,
951251881Speter     N_("show program version information") },
952251881Speter    {"quiet",              svndumpfilter__quiet, 0,
953251881Speter     N_("Do not display filtering statistics.") },
954251881Speter    {"pattern",            svndumpfilter__glob, 0,
955362181Sdim     N_("Treat the path prefixes as file glob patterns.\n"
956362181Sdim        "                             Glob special characters are '*' '?' '[]' and '\\'.\n"
957362181Sdim        "                             Character '/' is not treated specially, so\n"
958362181Sdim        "                             pattern /*/foo matches paths /a/foo and /a/b/foo.") },
959251881Speter    {"drop-empty-revs",    svndumpfilter__drop_empty_revs, 0,
960251881Speter     N_("Remove revisions emptied by filtering.")},
961251881Speter    {"drop-all-empty-revs",    svndumpfilter__drop_all_empty_revs, 0,
962251881Speter     N_("Remove all empty revisions found in dumpstream\n"
963251881Speter        "                             except revision 0.")},
964251881Speter    {"renumber-revs",      svndumpfilter__renumber_revs, 0,
965251881Speter     N_("Renumber revisions left after filtering.") },
966251881Speter    {"skip-missing-merge-sources",
967251881Speter     svndumpfilter__skip_missing_merge_sources, 0,
968251881Speter     N_("Skip missing merge sources.") },
969251881Speter    {"preserve-revprops",  svndumpfilter__preserve_revprops, 0,
970251881Speter     N_("Don't filter revision properties.") },
971251881Speter    {"targets", svndumpfilter__targets, 1,
972251881Speter     N_("Read additional prefixes, one per line, from\n"
973251881Speter        "                             file ARG.")},
974251881Speter    {NULL}
975251881Speter  };
976251881Speter
977251881Speter
978251881Speter/* Array of available subcommands.
979251881Speter * The entire list must be terminated with an entry of nulls.
980251881Speter */
981362181Sdimstatic const svn_opt_subcommand_desc3_t cmd_table[] =
982251881Speter  {
983362181Sdim    {"exclude", subcommand_exclude, {0}, {N_(
984362181Sdim        "Filter out nodes with given prefixes from dumpstream.\n"
985362181Sdim        "usage: svndumpfilter exclude PATH_PREFIX...\n"
986362181Sdim     )},
987251881Speter     {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
988251881Speter      svndumpfilter__renumber_revs,
989251881Speter      svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
990251881Speter      svndumpfilter__preserve_revprops, svndumpfilter__quiet,
991251881Speter      svndumpfilter__glob} },
992251881Speter
993362181Sdim    {"include", subcommand_include, {0}, {N_(
994362181Sdim        "Filter out nodes without given prefixes from dumpstream.\n"
995362181Sdim        "usage: svndumpfilter include PATH_PREFIX...\n"
996362181Sdim     )},
997251881Speter     {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
998251881Speter      svndumpfilter__renumber_revs,
999251881Speter      svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1000251881Speter      svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1001251881Speter      svndumpfilter__glob} },
1002251881Speter
1003362181Sdim    {"help", subcommand_help, {"?", "h"}, {N_(
1004362181Sdim        "Describe the usage of this program or its subcommands.\n"
1005362181Sdim        "usage: svndumpfilter help [SUBCOMMAND...]\n"
1006362181Sdim     )},
1007251881Speter     {0} },
1008251881Speter
1009362181Sdim    { NULL, NULL, {0}, {NULL}, {0} }
1010251881Speter  };
1011251881Speter
1012251881Speter
1013251881Speter/* Baton for passing option/argument state to a subcommand function. */
1014251881Speterstruct svndumpfilter_opt_state
1015251881Speter{
1016251881Speter  svn_opt_revision_t start_revision;     /* -r X[:Y] is         */
1017251881Speter  svn_opt_revision_t end_revision;       /* not implemented.    */
1018251881Speter  svn_boolean_t quiet;                   /* --quiet             */
1019251881Speter  svn_boolean_t glob;                    /* --pattern           */
1020251881Speter  svn_boolean_t version;                 /* --version           */
1021251881Speter  svn_boolean_t drop_empty_revs;         /* --drop-empty-revs   */
1022251881Speter  svn_boolean_t drop_all_empty_revs;     /* --drop-all-empty-revs */
1023251881Speter  svn_boolean_t help;                    /* --help or -?        */
1024251881Speter  svn_boolean_t renumber_revs;           /* --renumber-revs     */
1025251881Speter  svn_boolean_t preserve_revprops;       /* --preserve-revprops */
1026251881Speter  svn_boolean_t skip_missing_merge_sources;
1027251881Speter                                         /* --skip-missing-merge-sources */
1028251881Speter  const char *targets_file;              /* --targets-file       */
1029251881Speter  apr_array_header_t *prefixes;          /* mainargs.           */
1030251881Speter};
1031251881Speter
1032251881Speter
1033251881Speterstatic svn_error_t *
1034251881Speterparse_baton_initialize(struct parse_baton_t **pb,
1035251881Speter                       struct svndumpfilter_opt_state *opt_state,
1036251881Speter                       svn_boolean_t do_exclude,
1037251881Speter                       apr_pool_t *pool)
1038251881Speter{
1039251881Speter  struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1040251881Speter
1041251881Speter  /* Read the stream from STDIN.  Users can redirect a file. */
1042362181Sdim  SVN_ERR(svn_stream_for_stdin2(&baton->in_stream, TRUE, pool));
1043251881Speter
1044251881Speter  /* Have the parser dump results to STDOUT. Users can redirect a file. */
1045362181Sdim  SVN_ERR(svn_stream_for_stdout(&baton->out_stream, pool));
1046251881Speter
1047251881Speter  baton->do_exclude = do_exclude;
1048251881Speter
1049251881Speter  /* Ignore --renumber-revs if there can't possibly be
1050251881Speter     anything to renumber. */
1051251881Speter  baton->do_renumber_revs =
1052251881Speter    (opt_state->renumber_revs && (opt_state->drop_empty_revs
1053251881Speter                                  || opt_state->drop_all_empty_revs));
1054251881Speter
1055251881Speter  baton->drop_empty_revs = opt_state->drop_empty_revs;
1056251881Speter  baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
1057251881Speter  baton->preserve_revprops = opt_state->preserve_revprops;
1058251881Speter  baton->quiet = opt_state->quiet;
1059251881Speter  baton->glob = opt_state->glob;
1060251881Speter  baton->prefixes = opt_state->prefixes;
1061251881Speter  baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
1062251881Speter  baton->rev_drop_count = 0; /* used to shift revnums while filtering */
1063251881Speter  baton->dropped_nodes = apr_hash_make(pool);
1064251881Speter  baton->renumber_history = apr_hash_make(pool);
1065251881Speter  baton->last_live_revision = SVN_INVALID_REVNUM;
1066251881Speter  baton->oldest_original_rev = SVN_INVALID_REVNUM;
1067251881Speter  baton->allow_deltas = FALSE;
1068251881Speter
1069251881Speter  *pb = baton;
1070251881Speter  return SVN_NO_ERROR;
1071251881Speter}
1072251881Speter
1073251881Speter/* This implements `help` subcommand. */
1074251881Speterstatic svn_error_t *
1075251881Spetersubcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1076251881Speter{
1077251881Speter  struct svndumpfilter_opt_state *opt_state = baton;
1078251881Speter  const char *header =
1079251881Speter    _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
1080289180Speter      "Subversion repository dump filtering tool.\n"
1081251881Speter      "Type 'svndumpfilter help <subcommand>' for help on a "
1082251881Speter      "specific subcommand.\n"
1083251881Speter      "Type 'svndumpfilter --version' to see the program version.\n"
1084251881Speter      "\n"
1085251881Speter      "Available subcommands:\n");
1086251881Speter
1087362181Sdim  SVN_ERR(svn_opt_print_help5(os, "svndumpfilter",
1088251881Speter                              opt_state ? opt_state->version : FALSE,
1089251881Speter                              opt_state ? opt_state->quiet : FALSE,
1090251881Speter                              /*###opt_state ? opt_state->verbose :*/ FALSE,
1091251881Speter                              NULL, header, cmd_table, options_table,
1092251881Speter                              NULL, NULL, pool));
1093251881Speter
1094251881Speter  return SVN_NO_ERROR;
1095251881Speter}
1096251881Speter
1097251881Speter
1098251881Speter/* Version compatibility check */
1099251881Speterstatic svn_error_t *
1100251881Spetercheck_lib_versions(void)
1101251881Speter{
1102251881Speter  static const svn_version_checklist_t checklist[] =
1103251881Speter    {
1104251881Speter      { "svn_subr",  svn_subr_version },
1105251881Speter      { "svn_repos", svn_repos_version },
1106251881Speter      { "svn_delta", svn_delta_version },
1107251881Speter      { NULL, NULL }
1108251881Speter    };
1109251881Speter  SVN_VERSION_DEFINE(my_version);
1110251881Speter
1111257936Speter  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1112251881Speter}
1113251881Speter
1114251881Speter
1115251881Speter/* Do the real work of filtering. */
1116251881Speterstatic svn_error_t *
1117251881Speterdo_filter(apr_getopt_t *os,
1118251881Speter          void *baton,
1119251881Speter          svn_boolean_t do_exclude,
1120251881Speter          apr_pool_t *pool)
1121251881Speter{
1122251881Speter  struct svndumpfilter_opt_state *opt_state = baton;
1123251881Speter  struct parse_baton_t *pb;
1124251881Speter  apr_hash_index_t *hi;
1125251881Speter  apr_array_header_t *keys;
1126251881Speter  int i, num_keys;
1127251881Speter
1128251881Speter  if (! opt_state->quiet)
1129251881Speter    {
1130251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1131251881Speter
1132251881Speter      if (opt_state->glob)
1133251881Speter        {
1134251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1135251881Speter                                      do_exclude
1136251881Speter                                      ? (opt_state->drop_empty_revs
1137251881Speter                                         || opt_state->drop_all_empty_revs)
1138251881Speter                                        ? _("Excluding (and dropping empty "
1139251881Speter                                            "revisions for) prefix patterns:\n")
1140251881Speter                                        : _("Excluding prefix patterns:\n")
1141251881Speter                                      : (opt_state->drop_empty_revs
1142251881Speter                                         || opt_state->drop_all_empty_revs)
1143251881Speter                                        ? _("Including (and dropping empty "
1144251881Speter                                            "revisions for) prefix patterns:\n")
1145251881Speter                                        : _("Including prefix patterns:\n")));
1146251881Speter        }
1147251881Speter      else
1148251881Speter        {
1149251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1150251881Speter                                      do_exclude
1151251881Speter                                      ? (opt_state->drop_empty_revs
1152251881Speter                                         || opt_state->drop_all_empty_revs)
1153251881Speter                                        ? _("Excluding (and dropping empty "
1154251881Speter                                            "revisions for) prefixes:\n")
1155251881Speter                                        : _("Excluding prefixes:\n")
1156251881Speter                                      : (opt_state->drop_empty_revs
1157251881Speter                                         || opt_state->drop_all_empty_revs)
1158251881Speter                                        ? _("Including (and dropping empty "
1159251881Speter                                            "revisions for) prefixes:\n")
1160251881Speter                                        : _("Including prefixes:\n")));
1161251881Speter        }
1162251881Speter
1163251881Speter      for (i = 0; i < opt_state->prefixes->nelts; i++)
1164251881Speter        {
1165251881Speter          svn_pool_clear(subpool);
1166251881Speter          SVN_ERR(svn_cmdline_fprintf
1167251881Speter                  (stderr, subpool, "   '%s'\n",
1168251881Speter                   APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
1169251881Speter        }
1170251881Speter
1171251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1172251881Speter      svn_pool_destroy(subpool);
1173251881Speter    }
1174251881Speter
1175251881Speter  SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
1176251881Speter  SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
1177251881Speter                                      TRUE, NULL, NULL, pool));
1178251881Speter
1179251881Speter  /* The rest of this is just reporting.  If we aren't reporting, get
1180251881Speter     outta here. */
1181251881Speter  if (opt_state->quiet)
1182251881Speter    return SVN_NO_ERROR;
1183251881Speter
1184251881Speter  SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1185251881Speter
1186251881Speter  if (pb->rev_drop_count)
1187251881Speter    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1188251881Speter                                Q_("Dropped %d revision.\n\n",
1189251881Speter                                   "Dropped %d revisions.\n\n",
1190251881Speter                                   pb->rev_drop_count),
1191251881Speter                                pb->rev_drop_count));
1192251881Speter
1193251881Speter  if (pb->do_renumber_revs)
1194251881Speter    {
1195251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1196251881Speter      SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1197251881Speter                                stderr, subpool));
1198251881Speter
1199251881Speter      /* Get the keys of the hash, sort them, then print the hash keys
1200251881Speter         and values, sorted by keys. */
1201251881Speter      num_keys = apr_hash_count(pb->renumber_history);
1202251881Speter      keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
1203251881Speter      for (hi = apr_hash_first(pool, pb->renumber_history);
1204251881Speter           hi;
1205251881Speter           hi = apr_hash_next(hi))
1206251881Speter        {
1207289180Speter          const svn_revnum_t *revnum = apr_hash_this_key(hi);
1208251881Speter
1209251881Speter          APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1210251881Speter        }
1211289180Speter      svn_sort__array(keys, svn_sort_compare_revisions);
1212251881Speter      for (i = 0; i < keys->nelts; i++)
1213251881Speter        {
1214251881Speter          svn_revnum_t this_key;
1215251881Speter          struct revmap_t *this_val;
1216251881Speter
1217251881Speter          svn_pool_clear(subpool);
1218251881Speter          this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
1219251881Speter          this_val = apr_hash_get(pb->renumber_history, &this_key,
1220251881Speter                                  sizeof(this_key));
1221251881Speter          if (this_val->was_dropped)
1222251881Speter            SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1223251881Speter                                        _("   %ld => (dropped)\n"),
1224251881Speter                                        this_key));
1225251881Speter          else
1226251881Speter            SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1227251881Speter                                        "   %ld => %ld\n",
1228251881Speter                                        this_key, this_val->rev));
1229251881Speter        }
1230251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1231251881Speter      svn_pool_destroy(subpool);
1232251881Speter    }
1233251881Speter
1234251881Speter  if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1235251881Speter    {
1236251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1237251881Speter      SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1238251881Speter                                  Q_("Dropped %d node:\n",
1239251881Speter                                     "Dropped %d nodes:\n",
1240251881Speter                                     num_keys),
1241251881Speter                                  num_keys));
1242251881Speter
1243251881Speter      /* Get the keys of the hash, sort them, then print the hash keys
1244251881Speter         and values, sorted by keys. */
1245251881Speter      keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
1246251881Speter      for (hi = apr_hash_first(pool, pb->dropped_nodes);
1247251881Speter           hi;
1248251881Speter           hi = apr_hash_next(hi))
1249251881Speter        {
1250289180Speter          const char *path = apr_hash_this_key(hi);
1251251881Speter
1252251881Speter          APR_ARRAY_PUSH(keys, const char *) = path;
1253251881Speter        }
1254289180Speter      svn_sort__array(keys, svn_sort_compare_paths);
1255251881Speter      for (i = 0; i < keys->nelts; i++)
1256251881Speter        {
1257251881Speter          svn_pool_clear(subpool);
1258251881Speter          SVN_ERR(svn_cmdline_fprintf
1259251881Speter                  (stderr, subpool, "   '%s'\n",
1260251881Speter                   (const char *)APR_ARRAY_IDX(keys, i, const char *)));
1261251881Speter        }
1262251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1263251881Speter      svn_pool_destroy(subpool);
1264251881Speter    }
1265251881Speter
1266251881Speter  return SVN_NO_ERROR;
1267251881Speter}
1268251881Speter
1269251881Speter/* This implements `exclude' subcommand. */
1270251881Speterstatic svn_error_t *
1271251881Spetersubcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1272251881Speter{
1273251881Speter  return do_filter(os, baton, TRUE, pool);
1274251881Speter}
1275251881Speter
1276251881Speter
1277251881Speter/* This implements `include` subcommand. */
1278251881Speterstatic svn_error_t *
1279251881Spetersubcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1280251881Speter{
1281251881Speter  return do_filter(os, baton, FALSE, pool);
1282251881Speter}
1283251881Speter
1284251881Speter
1285251881Speter
1286251881Speter/** Main. **/
1287251881Speter
1288289180Speter/*
1289289180Speter * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
1290289180Speter * either return an error to be displayed, or set *EXIT_CODE to non-zero and
1291289180Speter * return SVN_NO_ERROR.
1292289180Speter */
1293289180Speterstatic svn_error_t *
1294289180Spetersub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1295251881Speter{
1296251881Speter  svn_error_t *err;
1297251881Speter  apr_status_t apr_err;
1298251881Speter
1299362181Sdim  const svn_opt_subcommand_desc3_t *subcommand = NULL;
1300251881Speter  struct svndumpfilter_opt_state opt_state;
1301251881Speter  apr_getopt_t *os;
1302251881Speter  int opt_id;
1303251881Speter  apr_array_header_t *received_opts;
1304251881Speter  int i;
1305251881Speter
1306251881Speter  /* Check library versions */
1307289180Speter  SVN_ERR(check_lib_versions());
1308251881Speter
1309251881Speter  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1310251881Speter
1311251881Speter  /* Initialize the FS library. */
1312289180Speter  SVN_ERR(svn_fs_initialize(pool));
1313251881Speter
1314251881Speter  if (argc <= 1)
1315251881Speter    {
1316289180Speter      SVN_ERR(subcommand_help(NULL, NULL, pool));
1317289180Speter      *exit_code = EXIT_FAILURE;
1318289180Speter      return SVN_NO_ERROR;
1319251881Speter    }
1320251881Speter
1321251881Speter  /* Initialize opt_state. */
1322251881Speter  memset(&opt_state, 0, sizeof(opt_state));
1323251881Speter  opt_state.start_revision.kind = svn_opt_revision_unspecified;
1324251881Speter  opt_state.end_revision.kind = svn_opt_revision_unspecified;
1325251881Speter
1326251881Speter  /* Parse options. */
1327289180Speter  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
1328251881Speter
1329251881Speter  os->interleave = 1;
1330251881Speter  while (1)
1331251881Speter    {
1332251881Speter      const char *opt_arg;
1333251881Speter
1334251881Speter      /* Parse the next option. */
1335251881Speter      apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1336251881Speter      if (APR_STATUS_IS_EOF(apr_err))
1337251881Speter        break;
1338251881Speter      else if (apr_err)
1339251881Speter        {
1340289180Speter          SVN_ERR(subcommand_help(NULL, NULL, pool));
1341289180Speter          *exit_code = EXIT_FAILURE;
1342289180Speter          return SVN_NO_ERROR;
1343251881Speter        }
1344251881Speter
1345251881Speter      /* Stash the option code in an array before parsing it. */
1346251881Speter      APR_ARRAY_PUSH(received_opts, int) = opt_id;
1347251881Speter
1348251881Speter      switch (opt_id)
1349251881Speter        {
1350251881Speter        case 'h':
1351251881Speter        case '?':
1352251881Speter          opt_state.help = TRUE;
1353251881Speter          break;
1354251881Speter        case svndumpfilter__version:
1355251881Speter          opt_state.version = TRUE;
1356251881Speter          break;
1357251881Speter        case svndumpfilter__quiet:
1358251881Speter          opt_state.quiet = TRUE;
1359251881Speter          break;
1360251881Speter        case svndumpfilter__glob:
1361251881Speter          opt_state.glob = TRUE;
1362251881Speter          break;
1363251881Speter        case svndumpfilter__drop_empty_revs:
1364251881Speter          opt_state.drop_empty_revs = TRUE;
1365251881Speter          break;
1366251881Speter        case svndumpfilter__drop_all_empty_revs:
1367251881Speter          opt_state.drop_all_empty_revs = TRUE;
1368251881Speter          break;
1369251881Speter        case svndumpfilter__renumber_revs:
1370251881Speter          opt_state.renumber_revs = TRUE;
1371251881Speter          break;
1372251881Speter        case svndumpfilter__preserve_revprops:
1373251881Speter          opt_state.preserve_revprops = TRUE;
1374251881Speter          break;
1375251881Speter        case svndumpfilter__skip_missing_merge_sources:
1376251881Speter          opt_state.skip_missing_merge_sources = TRUE;
1377251881Speter          break;
1378251881Speter        case svndumpfilter__targets:
1379362181Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.targets_file,
1380362181Sdim                                          opt_arg, pool));
1381251881Speter          break;
1382251881Speter        default:
1383251881Speter          {
1384289180Speter            SVN_ERR(subcommand_help(NULL, NULL, pool));
1385289180Speter            *exit_code = EXIT_FAILURE;
1386289180Speter            return SVN_NO_ERROR;
1387251881Speter          }
1388251881Speter        }  /* close `switch' */
1389251881Speter    }  /* close `while' */
1390251881Speter
1391251881Speter  /* Disallow simultaneous use of both --drop-empty-revs and
1392251881Speter     --drop-all-empty-revs. */
1393251881Speter  if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
1394251881Speter    {
1395289180Speter      return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS,
1396289180Speter                              NULL,
1397289180Speter                              _("--drop-empty-revs cannot be used with "
1398289180Speter                                "--drop-all-empty-revs"));
1399251881Speter    }
1400251881Speter
1401251881Speter  /* If the user asked for help, then the rest of the arguments are
1402251881Speter     the names of subcommands to get help on (if any), or else they're
1403251881Speter     just typos/mistakes.  Whatever the case, the subcommand to
1404251881Speter     actually run is subcommand_help(). */
1405251881Speter  if (opt_state.help)
1406362181Sdim    subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "help");
1407251881Speter
1408251881Speter  /* If we're not running the `help' subcommand, then look for a
1409251881Speter     subcommand in the first argument. */
1410251881Speter  if (subcommand == NULL)
1411251881Speter    {
1412251881Speter      if (os->ind >= os->argc)
1413251881Speter        {
1414251881Speter          if (opt_state.version)
1415251881Speter            {
1416251881Speter              /* Use the "help" subcommand to handle the "--version" option. */
1417362181Sdim              static const svn_opt_subcommand_desc3_t pseudo_cmd =
1418362181Sdim                { "--version", subcommand_help, {0}, {""},
1419251881Speter                  {svndumpfilter__version,  /* must accept its own option */
1420251881Speter                   svndumpfilter__quiet,
1421251881Speter                  } };
1422251881Speter
1423251881Speter              subcommand = &pseudo_cmd;
1424251881Speter            }
1425251881Speter          else
1426251881Speter            {
1427251881Speter              svn_error_clear(svn_cmdline_fprintf
1428251881Speter                              (stderr, pool,
1429251881Speter                               _("Subcommand argument required\n")));
1430289180Speter              SVN_ERR(subcommand_help(NULL, NULL, pool));
1431289180Speter              *exit_code = EXIT_FAILURE;
1432289180Speter              return SVN_NO_ERROR;
1433251881Speter            }
1434251881Speter        }
1435251881Speter      else
1436251881Speter        {
1437362181Sdim          const char *first_arg;
1438362181Sdim
1439362181Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
1440362181Sdim                                          pool));
1441362181Sdim          subcommand = svn_opt_get_canonical_subcommand3(cmd_table, first_arg);
1442251881Speter          if (subcommand == NULL)
1443251881Speter            {
1444251881Speter              svn_error_clear(
1445251881Speter                svn_cmdline_fprintf(stderr, pool,
1446251881Speter                                    _("Unknown subcommand: '%s'\n"),
1447362181Sdim                                    first_arg));
1448289180Speter              SVN_ERR(subcommand_help(NULL, NULL, pool));
1449289180Speter              *exit_code = EXIT_FAILURE;
1450289180Speter              return SVN_NO_ERROR;
1451251881Speter            }
1452251881Speter        }
1453251881Speter    }
1454251881Speter
1455251881Speter  /* If there's a second argument, it's probably [one of] prefixes.
1456251881Speter     Every subcommand except `help' requires at least one, so we parse
1457251881Speter     them out here and store in opt_state. */
1458251881Speter
1459251881Speter  if (subcommand->cmd_func != subcommand_help)
1460251881Speter    {
1461251881Speter
1462251881Speter      opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
1463251881Speter                                          sizeof(const char *));
1464251881Speter      for (i = os->ind ; i< os->argc; i++)
1465251881Speter        {
1466251881Speter          const char *prefix;
1467251881Speter
1468251881Speter          /* Ensure that each prefix is UTF8-encoded, in internal
1469251881Speter             style, and absolute. */
1470289180Speter          SVN_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
1471362181Sdim          SVN_ERR(svn_relpath__make_internal(&prefix, prefix, pool, pool));
1472251881Speter          if (prefix[0] != '/')
1473289180Speter            prefix = apr_pstrcat(pool, "/", prefix, SVN_VA_NULL);
1474251881Speter          APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1475251881Speter        }
1476251881Speter
1477251881Speter      if (opt_state.targets_file)
1478251881Speter        {
1479251881Speter          svn_stringbuf_t *buffer, *buffer_utf8;
1480251881Speter          apr_array_header_t *targets = apr_array_make(pool, 0,
1481251881Speter                                                       sizeof(const char *));
1482251881Speter
1483251881Speter          /* We need to convert to UTF-8 now, even before we divide
1484251881Speter             the targets into an array, because otherwise we wouldn't
1485251881Speter             know what delimiter to use for svn_cstring_split().  */
1486362181Sdim          SVN_ERR(svn_stringbuf_from_file2(&buffer, opt_state.targets_file,
1487289180Speter                                           pool));
1488289180Speter          SVN_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1489251881Speter
1490251881Speter          targets = apr_array_append(pool,
1491251881Speter                         svn_cstring_split(buffer_utf8->data, "\n\r",
1492251881Speter                                           TRUE, pool),
1493251881Speter                         targets);
1494251881Speter
1495251881Speter          for (i = 0; i < targets->nelts; i++)
1496251881Speter            {
1497251881Speter              const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
1498251881Speter              if (prefix[0] != '/')
1499289180Speter                prefix = apr_pstrcat(pool, "/", prefix, SVN_VA_NULL);
1500251881Speter              APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1501251881Speter            }
1502251881Speter        }
1503251881Speter
1504251881Speter      if (apr_is_empty_array(opt_state.prefixes))
1505251881Speter        {
1506251881Speter          svn_error_clear(svn_cmdline_fprintf
1507251881Speter                          (stderr, pool,
1508251881Speter                           _("\nError: no prefixes supplied.\n")));
1509289180Speter          *exit_code = EXIT_FAILURE;
1510289180Speter          return SVN_NO_ERROR;
1511251881Speter        }
1512251881Speter    }
1513251881Speter
1514251881Speter
1515251881Speter  /* Check that the subcommand wasn't passed any inappropriate options. */
1516251881Speter  for (i = 0; i < received_opts->nelts; i++)
1517251881Speter    {
1518251881Speter      opt_id = APR_ARRAY_IDX(received_opts, i, int);
1519251881Speter
1520251881Speter      /* All commands implicitly accept --help, so just skip over this
1521251881Speter         when we see it. Note that we don't want to include this option
1522251881Speter         in their "accepted options" list because it would be awfully
1523251881Speter         redundant to display it in every commands' help text. */
1524251881Speter      if (opt_id == 'h' || opt_id == '?')
1525251881Speter        continue;
1526251881Speter
1527362181Sdim      if (! svn_opt_subcommand_takes_option4(subcommand, opt_id, NULL))
1528251881Speter        {
1529251881Speter          const char *optstr;
1530251881Speter          const apr_getopt_option_t *badopt =
1531362181Sdim            svn_opt_get_option_from_code3(opt_id, options_table, subcommand,
1532251881Speter                                          pool);
1533251881Speter          svn_opt_format_option(&optstr, badopt, FALSE, pool);
1534251881Speter          if (subcommand->name[0] == '-')
1535289180Speter            SVN_ERR(subcommand_help(NULL, NULL, pool));
1536251881Speter          else
1537251881Speter            svn_error_clear(svn_cmdline_fprintf
1538251881Speter                            (stderr, pool,
1539251881Speter                             _("Subcommand '%s' doesn't accept option '%s'\n"
1540251881Speter                               "Type 'svndumpfilter help %s' for usage.\n"),
1541251881Speter                             subcommand->name, optstr, subcommand->name));
1542289180Speter          *exit_code = EXIT_FAILURE;
1543289180Speter          return SVN_NO_ERROR;
1544251881Speter        }
1545251881Speter    }
1546251881Speter
1547251881Speter  /* Run the subcommand. */
1548251881Speter  err = (*subcommand->cmd_func)(os, &opt_state, pool);
1549251881Speter  if (err)
1550251881Speter    {
1551251881Speter      /* For argument-related problems, suggest using the 'help'
1552251881Speter         subcommand. */
1553251881Speter      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1554251881Speter          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1555251881Speter        {
1556251881Speter          err = svn_error_quick_wrap(err,
1557251881Speter                                     _("Try 'svndumpfilter help' for more "
1558251881Speter                                       "info"));
1559251881Speter        }
1560289180Speter      return err;
1561251881Speter    }
1562289180Speter
1563289180Speter  return SVN_NO_ERROR;
1564289180Speter}
1565289180Speter
1566289180Speterint
1567289180Spetermain(int argc, const char *argv[])
1568289180Speter{
1569289180Speter  apr_pool_t *pool;
1570289180Speter  int exit_code = EXIT_SUCCESS;
1571289180Speter  svn_error_t *err;
1572289180Speter
1573289180Speter  /* Initialize the app. */
1574289180Speter  if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1575289180Speter    return EXIT_FAILURE;
1576289180Speter
1577289180Speter  /* Create our top-level pool.  Use a separate mutexless allocator,
1578289180Speter   * given this application is single threaded.
1579289180Speter   */
1580289180Speter  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1581289180Speter
1582289180Speter  err = sub_main(&exit_code, argc, argv, pool);
1583289180Speter
1584289180Speter  /* Flush stdout and report if it fails. It would be flushed on exit anyway
1585289180Speter     but this makes sure that output is not silently lost if it fails. */
1586289180Speter  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1587289180Speter
1588289180Speter  if (err)
1589251881Speter    {
1590289180Speter      exit_code = EXIT_FAILURE;
1591289180Speter      svn_cmdline_handle_exit_error(err, NULL, "svndumpfilter: ");
1592289180Speter    }
1593251881Speter
1594289180Speter  svn_pool_destroy(pool);
1595289180Speter  return exit_code;
1596251881Speter}
1597