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
46251881Speter#include "private/svn_mergeinfo_private.h"
47251881Speter#include "private/svn_cmdline_private.h"
48262253Speter#include "private/svn_subr_private.h"
49251881Speter
50251881Speter#ifdef _WIN32
51251881Spetertypedef apr_status_t (__stdcall *open_fn_t)(apr_file_t **, apr_pool_t *);
52251881Speter#else
53251881Spetertypedef apr_status_t (*open_fn_t)(apr_file_t **, apr_pool_t *);
54251881Speter#endif
55251881Speter
56251881Speter/*** Code. ***/
57251881Speter
58251881Speter/* Helper to open stdio streams */
59251881Speter
60251881Speter/* NOTE: we used to call svn_stream_from_stdio(), which wraps a stream
61251881Speter   around a standard stdio.h FILE pointer.  The problem is that these
62251881Speter   pointers operate through C Run Time (CRT) on Win32, which does all
63251881Speter   sorts of translation on them: LF's become CRLF's, and ctrl-Z's
64251881Speter   embedded in Word documents are interpreted as premature EOF's.
65251881Speter
66251881Speter   So instead, we use apr_file_open_std*, which bypass the CRT and
67251881Speter   directly wrap the OS's file-handles, which don't know or care about
68251881Speter   translation.  Thus dump/load works correctly on Win32.
69251881Speter*/
70251881Speterstatic svn_error_t *
71251881Spetercreate_stdio_stream(svn_stream_t **stream,
72251881Speter                    open_fn_t open_fn,
73251881Speter                    apr_pool_t *pool)
74251881Speter{
75251881Speter  apr_file_t *stdio_file;
76251881Speter  apr_status_t apr_err = open_fn(&stdio_file, pool);
77251881Speter
78251881Speter  if (apr_err)
79251881Speter    return svn_error_wrap_apr(apr_err, _("Can't open stdio file"));
80251881Speter
81251881Speter  *stream = svn_stream_from_aprfile2(stdio_file, TRUE, pool);
82251881Speter  return SVN_NO_ERROR;
83251881Speter}
84251881Speter
85251881Speter
86251881Speter/* Writes a property in dumpfile format to given stringbuf. */
87251881Speterstatic void
88251881Speterwrite_prop_to_stringbuf(svn_stringbuf_t *strbuf,
89251881Speter                        const char *name,
90251881Speter                        const svn_string_t *value)
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, "K ", 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  /* Output value length, then value. */
108251881Speter  svn_stringbuf_appendbytes(strbuf, "V ", 2);
109251881Speter
110251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len);
111251881Speter  svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
112251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
113251881Speter
114251881Speter  svn_stringbuf_appendbytes(strbuf, value->data, value->len);
115251881Speter  svn_stringbuf_appendbyte(strbuf, '\n');
116251881Speter}
117251881Speter
118251881Speter
119251881Speter/* Writes a property deletion in dumpfile format to given stringbuf. */
120251881Speterstatic void
121251881Speterwrite_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
122251881Speter                           const char *name)
123251881Speter{
124251881Speter  int bytes_used;
125251881Speter  size_t namelen;
126251881Speter  char buf[SVN_KEYLINE_MAXLEN];
127251881Speter
128251881Speter  /* Output name length, then name. */
129251881Speter  namelen = strlen(name);
130251881Speter  svn_stringbuf_appendbytes(*strbuf, "D ", 2);
131251881Speter
132251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
133251881Speter  svn_stringbuf_appendbytes(*strbuf, buf, bytes_used);
134251881Speter  svn_stringbuf_appendbyte(*strbuf, '\n');
135251881Speter
136251881Speter  svn_stringbuf_appendbytes(*strbuf, name, namelen);
137251881Speter  svn_stringbuf_appendbyte(*strbuf, '\n');
138251881Speter}
139251881Speter
140251881Speter
141251881Speter/* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
142251881Speter * Return TRUE if any prefix is a prefix of PATH (matching whole path
143251881Speter * components); FALSE otherwise.
144251881Speter * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
145251881Speterstatic svn_boolean_t
146251881Speterary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
147251881Speter{
148251881Speter  int i;
149251881Speter  size_t path_len = strlen(path);
150251881Speter
151251881Speter  for (i = 0; i < pfxlist->nelts; i++)
152251881Speter    {
153251881Speter      const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
154251881Speter      size_t pfx_len = strlen(pfx);
155251881Speter
156251881Speter      if (path_len < pfx_len)
157251881Speter        continue;
158251881Speter      if (strncmp(path, pfx, pfx_len) == 0
159251881Speter          && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
160251881Speter        return TRUE;
161251881Speter    }
162251881Speter
163251881Speter  return FALSE;
164251881Speter}
165251881Speter
166251881Speter
167251881Speter/* Check whether we need to skip this PATH based on its presence in
168251881Speter   the PREFIXES list, and the DO_EXCLUDE option.
169251881Speter   PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
170251881Speterstatic APR_INLINE svn_boolean_t
171251881Speterskip_path(const char *path, const apr_array_header_t *prefixes,
172251881Speter          svn_boolean_t do_exclude, svn_boolean_t glob)
173251881Speter{
174251881Speter  const svn_boolean_t matches =
175251881Speter    (glob
176251881Speter     ? svn_cstring_match_glob_list(path, prefixes)
177251881Speter     : ary_prefix_match(prefixes, path));
178251881Speter
179251881Speter  /* NXOR */
180251881Speter  return (matches ? do_exclude : !do_exclude);
181251881Speter}
182251881Speter
183251881Speter
184251881Speter
185251881Speter/* Note: the input stream parser calls us with events.
186251881Speter   Output of the filtered dump occurs for the most part streamily with the
187251881Speter   event callbacks, to avoid caching large quantities of data in memory.
188251881Speter   The exceptions this are:
189251881Speter   - All revision data (headers and props) must be cached until a non-skipped
190251881Speter     node within the revision is found, or the revision is closed.
191251881Speter   - Node headers and props must be cached until all props have been received
192251881Speter     (to allow the Prop-content-length to be found). This is signalled either
193251881Speter     by the node text arriving, or the node being closed.
194251881Speter   The writing_begun members of the associated object batons track the state.
195251881Speter   output_revision() and output_node() are called to cause this flushing of
196251881Speter   cached data to occur.
197251881Speter*/
198251881Speter
199251881Speter
200251881Speter/* Filtering batons */
201251881Speter
202251881Speterstruct revmap_t
203251881Speter{
204251881Speter  svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
205251881Speter  svn_boolean_t was_dropped; /* Was this revision dropped? */
206251881Speter};
207251881Speter
208251881Speterstruct parse_baton_t
209251881Speter{
210251881Speter  /* Command-line options values. */
211251881Speter  svn_boolean_t do_exclude;
212251881Speter  svn_boolean_t quiet;
213251881Speter  svn_boolean_t glob;
214251881Speter  svn_boolean_t drop_empty_revs;
215251881Speter  svn_boolean_t drop_all_empty_revs;
216251881Speter  svn_boolean_t do_renumber_revs;
217251881Speter  svn_boolean_t preserve_revprops;
218251881Speter  svn_boolean_t skip_missing_merge_sources;
219251881Speter  svn_boolean_t allow_deltas;
220251881Speter  apr_array_header_t *prefixes;
221251881Speter
222251881Speter  /* Input and output streams. */
223251881Speter  svn_stream_t *in_stream;
224251881Speter  svn_stream_t *out_stream;
225251881Speter
226251881Speter  /* State for the filtering process. */
227251881Speter  apr_int32_t rev_drop_count;
228251881Speter  apr_hash_t *dropped_nodes;
229251881Speter  apr_hash_t *renumber_history;  /* svn_revnum_t -> struct revmap_t */
230251881Speter  svn_revnum_t last_live_revision;
231251881Speter  /* The oldest original revision, greater than r0, in the input
232251881Speter     stream which was not filtered. */
233251881Speter  svn_revnum_t oldest_original_rev;
234251881Speter};
235251881Speter
236251881Speterstruct revision_baton_t
237251881Speter{
238251881Speter  /* Reference to the global parse baton. */
239251881Speter  struct parse_baton_t *pb;
240251881Speter
241251881Speter  /* Does this revision have node or prop changes? */
242251881Speter  svn_boolean_t has_nodes;
243251881Speter  svn_boolean_t has_props;
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. */
256251881Speter  svn_stringbuf_t *header;
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. */
281251881Speter  svn_stringbuf_t *header;
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;
290251881Speter};
291251881Speter
292251881Speter
293251881Speter
294251881Speter/* Filtering vtable members */
295251881Speter
296251881Speter/* File-format stamp. */
297251881Speterstatic svn_error_t *
298251881Spetermagic_header_record(int version, void *parse_baton, apr_pool_t *pool)
299251881Speter{
300251881Speter  struct parse_baton_t *pb = parse_baton;
301251881Speter
302251881Speter  if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
303251881Speter    pb->allow_deltas = TRUE;
304251881Speter
305251881Speter  SVN_ERR(svn_stream_printf(pb->out_stream, pool,
306251881Speter                            SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
307251881Speter                            version));
308251881Speter
309251881Speter  return SVN_NO_ERROR;
310251881Speter}
311251881Speter
312251881Speter
313251881Speter/* New revision: set up revision_baton, decide if we skip it. */
314251881Speterstatic svn_error_t *
315251881Speternew_revision_record(void **revision_baton,
316251881Speter                    apr_hash_t *headers,
317251881Speter                    void *parse_baton,
318251881Speter                    apr_pool_t *pool)
319251881Speter{
320251881Speter  struct revision_baton_t *rb;
321251881Speter  apr_hash_index_t *hi;
322251881Speter  const char *rev_orig;
323251881Speter  svn_stream_t *header_stream;
324251881Speter
325251881Speter  *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t));
326251881Speter  rb = *revision_baton;
327251881Speter  rb->pb = parse_baton;
328251881Speter  rb->has_nodes = FALSE;
329251881Speter  rb->has_props = FALSE;
330251881Speter  rb->had_dropped_nodes = FALSE;
331251881Speter  rb->writing_begun = FALSE;
332251881Speter  rb->header = svn_stringbuf_create_empty(pool);
333251881Speter  rb->props = apr_hash_make(pool);
334251881Speter
335251881Speter  header_stream = svn_stream_from_stringbuf(rb->header, pool);
336251881Speter
337251881Speter  rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
338251881Speter  rb->rev_orig = SVN_STR_TO_REV(rev_orig);
339251881Speter
340251881Speter  if (rb->pb->do_renumber_revs)
341251881Speter    rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
342251881Speter  else
343251881Speter    rb->rev_actual = rb->rev_orig;
344251881Speter
345251881Speter  SVN_ERR(svn_stream_printf(header_stream, pool,
346251881Speter                            SVN_REPOS_DUMPFILE_REVISION_NUMBER ": %ld\n",
347251881Speter                            rb->rev_actual));
348251881Speter
349251881Speter  for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
350251881Speter    {
351251881Speter      const char *key = svn__apr_hash_index_key(hi);
352251881Speter      const char *val = svn__apr_hash_index_val(hi);
353251881Speter
354251881Speter      if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
355251881Speter          || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
356251881Speter          || (!strcmp(key, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
357251881Speter        continue;
358251881Speter
359251881Speter      /* passthru: put header into header stringbuf. */
360251881Speter
361251881Speter      SVN_ERR(svn_stream_printf(header_stream, pool, "%s: %s\n",
362251881Speter                                key, val));
363251881Speter    }
364251881Speter
365251881Speter  SVN_ERR(svn_stream_close(header_stream));
366251881Speter
367251881Speter  return SVN_NO_ERROR;
368251881Speter}
369251881Speter
370251881Speter
371251881Speter/* Output revision to dumpstream
372251881Speter   This may be called by new_node_record(), iff rb->has_nodes has been set
373251881Speter   to TRUE, or by close_revision() otherwise. This must only be called
374251881Speter   if rb->writing_begun is FALSE. */
375251881Speterstatic svn_error_t *
376251881Speteroutput_revision(struct revision_baton_t *rb)
377251881Speter{
378251881Speter  int bytes_used;
379251881Speter  char buf[SVN_KEYLINE_MAXLEN];
380251881Speter  apr_hash_index_t *hi;
381251881Speter  svn_boolean_t write_out_rev = FALSE;
382251881Speter  apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
383251881Speter  svn_stringbuf_t *props = svn_stringbuf_create_empty(hash_pool);
384251881Speter  apr_pool_t *subpool = svn_pool_create(hash_pool);
385251881Speter
386251881Speter  rb->writing_begun = TRUE;
387251881Speter
388251881Speter  /* If this revision has no nodes left because the ones it had were
389251881Speter     dropped, and we are not dropping empty revisions, and we were not
390251881Speter     told to preserve revision props, then we want to fixup the
391251881Speter     revision props to only contain:
392251881Speter       - the date
393251881Speter       - a log message that reports that this revision is just stuffing. */
394251881Speter  if ((! rb->pb->preserve_revprops)
395251881Speter      && (! rb->has_nodes)
396251881Speter      && rb->had_dropped_nodes
397251881Speter      && (! rb->pb->drop_empty_revs)
398251881Speter      && (! rb->pb->drop_all_empty_revs))
399251881Speter    {
400251881Speter      apr_hash_t *old_props = rb->props;
401251881Speter      rb->has_props = TRUE;
402251881Speter      rb->props = apr_hash_make(hash_pool);
403251881Speter      svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE,
404251881Speter                    svn_hash_gets(old_props, SVN_PROP_REVISION_DATE));
405251881Speter      svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG,
406251881Speter                    svn_string_create(_("This is an empty revision for "
407251881Speter                                        "padding."), hash_pool));
408251881Speter    }
409251881Speter
410251881Speter  /* Now, "rasterize" the props to a string, and append the property
411251881Speter     information to the header string.  */
412251881Speter  if (rb->has_props)
413251881Speter    {
414251881Speter      for (hi = apr_hash_first(subpool, rb->props);
415251881Speter           hi;
416251881Speter           hi = apr_hash_next(hi))
417251881Speter        {
418251881Speter          const char *pname = svn__apr_hash_index_key(hi);
419251881Speter          const svn_string_t *pval = svn__apr_hash_index_val(hi);
420251881Speter
421251881Speter          write_prop_to_stringbuf(props, pname, pval);
422251881Speter        }
423251881Speter      svn_stringbuf_appendcstr(props, "PROPS-END\n");
424251881Speter      svn_stringbuf_appendcstr(rb->header,
425251881Speter                               SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
426251881Speter      bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
427251881Speter                                props->len);
428251881Speter      svn_stringbuf_appendbytes(rb->header, buf, bytes_used);
429251881Speter      svn_stringbuf_appendbyte(rb->header, '\n');
430251881Speter    }
431251881Speter
432251881Speter  svn_stringbuf_appendcstr(rb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
433251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT, props->len);
434251881Speter  svn_stringbuf_appendbytes(rb->header, buf, bytes_used);
435251881Speter  svn_stringbuf_appendbyte(rb->header, '\n');
436251881Speter
437251881Speter  /* put an end to headers */
438251881Speter  svn_stringbuf_appendbyte(rb->header, '\n');
439251881Speter
440251881Speter  /* put an end to revision */
441251881Speter  svn_stringbuf_appendbyte(props, '\n');
442251881Speter
443251881Speter  /* write out the revision */
444251881Speter  /* Revision is written out in the following cases:
445251881Speter     1. If the revision has nodes or
446251881Speter     it is revision 0 (Special case: To preserve the props on r0).
447251881Speter     2. --drop-empty-revs has been supplied,
448251881Speter     but revision has not all nodes dropped.
449251881Speter     3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied,
450251881Speter     write out the revision which has no nodes to begin with.
451251881Speter  */
452251881Speter  if (rb->has_nodes || (rb->rev_orig == 0))
453251881Speter    write_out_rev = TRUE;
454251881Speter  else if (rb->pb->drop_empty_revs)
455251881Speter    write_out_rev = ! rb->had_dropped_nodes;
456251881Speter  else if (! rb->pb->drop_all_empty_revs)
457251881Speter    write_out_rev = TRUE;
458251881Speter
459251881Speter  if (write_out_rev)
460251881Speter    {
461251881Speter      /* This revision is a keeper. */
462251881Speter      SVN_ERR(svn_stream_write(rb->pb->out_stream,
463251881Speter                               rb->header->data, &(rb->header->len)));
464251881Speter      SVN_ERR(svn_stream_write(rb->pb->out_stream,
465251881Speter                               props->data, &(props->len)));
466251881Speter
467251881Speter      /* Stash the oldest original rev not dropped. */
468251881Speter      if (rb->rev_orig > 0
469251881Speter          && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
470251881Speter        rb->pb->oldest_original_rev = rb->rev_orig;
471251881Speter
472251881Speter      if (rb->pb->do_renumber_revs)
473251881Speter        {
474251881Speter          svn_revnum_t *rr_key;
475251881Speter          struct revmap_t *rr_val;
476251881Speter          apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
477251881Speter          rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
478251881Speter          rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
479251881Speter          *rr_key = rb->rev_orig;
480251881Speter          rr_val->rev = rb->rev_actual;
481251881Speter          rr_val->was_dropped = FALSE;
482251881Speter          apr_hash_set(rb->pb->renumber_history, rr_key,
483251881Speter                       sizeof(*rr_key), rr_val);
484251881Speter          rb->pb->last_live_revision = rb->rev_actual;
485251881Speter        }
486251881Speter
487251881Speter      if (! rb->pb->quiet)
488251881Speter        SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
489251881Speter                                    _("Revision %ld committed as %ld.\n"),
490251881Speter                                    rb->rev_orig, rb->rev_actual));
491251881Speter    }
492251881Speter  else
493251881Speter    {
494251881Speter      /* We're dropping this revision. */
495251881Speter      rb->pb->rev_drop_count++;
496251881Speter      if (rb->pb->do_renumber_revs)
497251881Speter        {
498251881Speter          svn_revnum_t *rr_key;
499251881Speter          struct revmap_t *rr_val;
500251881Speter          apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
501251881Speter          rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
502251881Speter          rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
503251881Speter          *rr_key = rb->rev_orig;
504251881Speter          rr_val->rev = rb->pb->last_live_revision;
505251881Speter          rr_val->was_dropped = TRUE;
506251881Speter          apr_hash_set(rb->pb->renumber_history, rr_key,
507251881Speter                       sizeof(*rr_key), rr_val);
508251881Speter        }
509251881Speter
510251881Speter      if (! rb->pb->quiet)
511251881Speter        SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
512251881Speter                                    _("Revision %ld skipped.\n"),
513251881Speter                                    rb->rev_orig));
514251881Speter    }
515251881Speter  svn_pool_destroy(subpool);
516251881Speter  return SVN_NO_ERROR;
517251881Speter}
518251881Speter
519251881Speter
520251881Speter/* UUID record here: dump it, as we do not filter them. */
521251881Speterstatic svn_error_t *
522251881Speteruuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
523251881Speter{
524251881Speter  struct parse_baton_t *pb = parse_baton;
525251881Speter  SVN_ERR(svn_stream_printf(pb->out_stream, pool,
526251881Speter                            SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
527251881Speter  return SVN_NO_ERROR;
528251881Speter}
529251881Speter
530251881Speter
531251881Speter/* New node here. Set up node_baton by copying headers. */
532251881Speterstatic svn_error_t *
533251881Speternew_node_record(void **node_baton,
534251881Speter                apr_hash_t *headers,
535251881Speter                void *rev_baton,
536251881Speter                apr_pool_t *pool)
537251881Speter{
538251881Speter  struct parse_baton_t *pb;
539251881Speter  struct node_baton_t *nb;
540251881Speter  char *node_path, *copyfrom_path;
541251881Speter  apr_hash_index_t *hi;
542251881Speter  const char *tcl;
543251881Speter
544251881Speter  *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
545251881Speter  nb          = *node_baton;
546251881Speter  nb->rb      = rev_baton;
547251881Speter  pb          = nb->rb->pb;
548251881Speter
549251881Speter  node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH);
550251881Speter  copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH);
551251881Speter
552251881Speter  /* Ensure that paths start with a leading '/'. */
553251881Speter  if (node_path[0] != '/')
554251881Speter    node_path = apr_pstrcat(pool, "/", node_path, (char *)NULL);
555251881Speter  if (copyfrom_path && copyfrom_path[0] != '/')
556251881Speter    copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, (char *)NULL);
557251881Speter
558251881Speter  nb->do_skip = skip_path(node_path, pb->prefixes,
559251881Speter                          pb->do_exclude, pb->glob);
560251881Speter
561251881Speter  /* If we're skipping the node, take note of path, discarding the
562251881Speter     rest.  */
563251881Speter  if (nb->do_skip)
564251881Speter    {
565251881Speter      svn_hash_sets(pb->dropped_nodes,
566251881Speter                    apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
567251881Speter                                node_path),
568251881Speter                    (void *)1);
569251881Speter      nb->rb->had_dropped_nodes = TRUE;
570251881Speter    }
571251881Speter  else
572251881Speter    {
573269847Speter      const char *kind;
574269847Speter      const char *action;
575269847Speter
576251881Speter      tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
577251881Speter
578251881Speter      /* Test if this node was copied from dropped source. */
579251881Speter      if (copyfrom_path &&
580251881Speter          skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
581251881Speter        {
582251881Speter          /* This node was copied from a dropped source.
583251881Speter             We have a problem, since we did not want to drop this node too.
584251881Speter
585251881Speter             However, there is one special case we'll handle.  If the node is
586251881Speter             a file, and this was a copy-and-modify operation, then the
587251881Speter             dumpfile should contain the new contents of the file.  In this
588251881Speter             scenario, we'll just do an add without history using the new
589251881Speter             contents.  */
590251881Speter          kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
591251881Speter
592251881Speter          /* If there is a Text-content-length header, and the kind is
593251881Speter             "file", we just fallback to an add without history. */
594251881Speter          if (tcl && (strcmp(kind, "file") == 0))
595251881Speter            {
596251881Speter              svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
597251881Speter                            NULL);
598251881Speter              svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
599251881Speter                            NULL);
600251881Speter              copyfrom_path = NULL;
601251881Speter            }
602251881Speter          /* Else, this is either a directory or a file whose contents we
603251881Speter             don't have readily available.  */
604251881Speter          else
605251881Speter            {
606251881Speter              return svn_error_createf
607251881Speter                (SVN_ERR_INCOMPLETE_DATA, 0,
608251881Speter                 _("Invalid copy source path '%s'"), copyfrom_path);
609251881Speter            }
610251881Speter        }
611251881Speter
612251881Speter      nb->has_props = FALSE;
613251881Speter      nb->has_text = FALSE;
614251881Speter      nb->has_prop_delta = FALSE;
615251881Speter      nb->has_text_delta = FALSE;
616251881Speter      nb->writing_begun = FALSE;
617251881Speter      nb->tcl = tcl ? svn__atoui64(tcl) : 0;
618251881Speter      nb->header = svn_stringbuf_create_empty(pool);
619251881Speter      nb->props = svn_stringbuf_create_empty(pool);
620251881Speter      nb->node_path = apr_pstrdup(pool, node_path);
621251881Speter
622251881Speter      /* Now we know for sure that we have a node that will not be
623251881Speter         skipped, flush the revision if it has not already been done. */
624251881Speter      nb->rb->has_nodes = TRUE;
625251881Speter      if (! nb->rb->writing_begun)
626251881Speter        SVN_ERR(output_revision(nb->rb));
627251881Speter
628269847Speter      /* A node record is required to begin with 'Node-path', skip the
629269847Speter         leading '/' to match the form used by 'svnadmin dump'. */
630269847Speter      SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
631269847Speter                                pool, "%s: %s\n",
632269847Speter                                SVN_REPOS_DUMPFILE_NODE_PATH, node_path + 1));
633269847Speter
634269847Speter      /* Node-kind is next and is optional. */
635269847Speter      kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
636269847Speter      if (kind)
637269847Speter        SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
638269847Speter                                  pool, "%s: %s\n",
639269847Speter                                  SVN_REPOS_DUMPFILE_NODE_KIND, kind));
640269847Speter
641269847Speter      /* Node-action is next and required. */
642269847Speter      action = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION);
643269847Speter      if (action)
644269847Speter        SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
645269847Speter                                  pool, "%s: %s\n",
646269847Speter                                  SVN_REPOS_DUMPFILE_NODE_ACTION, action));
647269847Speter      else
648269847Speter        return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
649269847Speter                                 _("Missing Node-action for path '%s'"),
650269847Speter                                 node_path);
651269847Speter
652251881Speter      for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
653251881Speter        {
654251881Speter          const char *key = svn__apr_hash_index_key(hi);
655251881Speter          const char *val = svn__apr_hash_index_val(hi);
656251881Speter
657251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
658251881Speter              && (!strcmp(val, "true")))
659251881Speter            nb->has_prop_delta = TRUE;
660251881Speter
661251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
662251881Speter              && (!strcmp(val, "true")))
663251881Speter            nb->has_text_delta = TRUE;
664251881Speter
665251881Speter          if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
666251881Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
667269847Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH))
668269847Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_PATH))
669269847Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_KIND))
670269847Speter              || (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_ACTION)))
671251881Speter            continue;
672251881Speter
673251881Speter          /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
674251881Speter             The number points to some revision in the past. We keep track
675251881Speter             of revision renumbering in an apr_hash, which maps original
676251881Speter             revisions to new ones. Dropped revision are mapped to -1.
677251881Speter             This should never happen here.
678251881Speter          */
679251881Speter          if (pb->do_renumber_revs
680251881Speter              && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
681251881Speter            {
682251881Speter              svn_revnum_t cf_orig_rev;
683251881Speter              struct revmap_t *cf_renum_val;
684251881Speter
685251881Speter              cf_orig_rev = SVN_STR_TO_REV(val);
686251881Speter              cf_renum_val = apr_hash_get(pb->renumber_history,
687251881Speter                                          &cf_orig_rev,
688251881Speter                                          sizeof(svn_revnum_t));
689251881Speter              if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
690251881Speter                return svn_error_createf
691251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
692251881Speter                   _("No valid copyfrom revision in filtered stream"));
693251881Speter              SVN_ERR(svn_stream_printf
694251881Speter                      (nb->rb->pb->out_stream, pool,
695251881Speter                       SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV ": %ld\n",
696251881Speter                       cf_renum_val->rev));
697251881Speter              continue;
698251881Speter            }
699251881Speter
700251881Speter          /* passthru: put header straight to output */
701251881Speter
702251881Speter          SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
703251881Speter                                    pool, "%s: %s\n",
704251881Speter                                    key, val));
705251881Speter        }
706251881Speter    }
707251881Speter
708251881Speter  return SVN_NO_ERROR;
709251881Speter}
710251881Speter
711251881Speter
712251881Speter/* Output node header and props to dumpstream
713251881Speter   This will be called by set_fulltext() after setting nb->has_text to TRUE,
714251881Speter   if the node has any text, or by close_node() otherwise. This must only
715251881Speter   be called if nb->writing_begun is FALSE. */
716251881Speterstatic svn_error_t *
717251881Speteroutput_node(struct node_baton_t *nb)
718251881Speter{
719251881Speter  int bytes_used;
720251881Speter  char buf[SVN_KEYLINE_MAXLEN];
721251881Speter
722251881Speter  nb->writing_begun = TRUE;
723251881Speter
724251881Speter  /* when there are no props nb->props->len would be zero and won't mess up
725251881Speter     Content-Length. */
726251881Speter  if (nb->has_props)
727251881Speter    svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
728251881Speter
729251881Speter  /* 1. recalculate & check text-md5 if present. Passed through right now. */
730251881Speter
731251881Speter  /* 2. recalculate and add content-lengths */
732251881Speter
733251881Speter  if (nb->has_props)
734251881Speter    {
735251881Speter      svn_stringbuf_appendcstr(nb->header,
736251881Speter                               SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
737251881Speter      bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
738251881Speter                                nb->props->len);
739251881Speter      svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
740251881Speter      svn_stringbuf_appendbyte(nb->header, '\n');
741251881Speter    }
742251881Speter  if (nb->has_text)
743251881Speter    {
744251881Speter      svn_stringbuf_appendcstr(nb->header,
745251881Speter                               SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
746251881Speter      bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
747251881Speter                                nb->tcl);
748251881Speter      svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
749251881Speter      svn_stringbuf_appendbyte(nb->header, '\n');
750251881Speter    }
751251881Speter  svn_stringbuf_appendcstr(nb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
752251881Speter  bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
753251881Speter                            (svn_filesize_t) (nb->props->len + nb->tcl));
754251881Speter  svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
755251881Speter  svn_stringbuf_appendbyte(nb->header, '\n');
756251881Speter
757251881Speter  /* put an end to headers */
758251881Speter  svn_stringbuf_appendbyte(nb->header, '\n');
759251881Speter
760251881Speter  /* 3. output all the stuff */
761251881Speter
762251881Speter  SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
763251881Speter                           nb->header->data , &(nb->header->len)));
764251881Speter  SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
765251881Speter                           nb->props->data , &(nb->props->len)));
766251881Speter
767251881Speter  return SVN_NO_ERROR;
768251881Speter}
769251881Speter
770251881Speter
771251881Speter/* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
772251881Speter   sources or renumbering revisions in rangelists as appropriate, and
773251881Speter   return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
774251881Speter   POOL). */
775251881Speterstatic svn_error_t *
776251881Speteradjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
777251881Speter                 struct revision_baton_t *rb, apr_pool_t *pool)
778251881Speter{
779251881Speter  apr_hash_t *mergeinfo;
780251881Speter  apr_hash_t *final_mergeinfo = apr_hash_make(pool);
781251881Speter  apr_hash_index_t *hi;
782251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
783251881Speter
784251881Speter  SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
785251881Speter
786251881Speter  /* Issue #3020: If we are skipping missing merge sources, then also
787251881Speter     filter mergeinfo ranges as old or older than the oldest revision in the
788251881Speter     dump stream.  Those older than the oldest obviously refer to history
789251881Speter     outside of the dump stream.  The oldest rev itself is present in the
790251881Speter     dump, but cannot be a valid merge source revision since it is the
791251881Speter     start of all history.  E.g. if we dump -r100:400 then dumpfilter the
792251881Speter     result with --skip-missing-merge-sources, any mergeinfo with revision
793251881Speter     100 implies a change of -r99:100, but r99 is part of the history we
794251881Speter     want filtered.  This is analogous to how r1 is always meaningless as
795251881Speter     a merge source revision.
796251881Speter
797251881Speter     If the oldest rev is r0 then there is nothing to filter. */
798251881Speter  if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
799251881Speter    SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
800251881Speter      &mergeinfo, mergeinfo,
801251881Speter      rb->pb->oldest_original_rev, 0,
802251881Speter      FALSE, subpool, subpool));
803251881Speter
804251881Speter  for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
805251881Speter    {
806251881Speter      const char *merge_source = svn__apr_hash_index_key(hi);
807251881Speter      svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
808251881Speter      struct parse_baton_t *pb = rb->pb;
809251881Speter
810251881Speter      /* Determine whether the merge_source is a part of the prefix. */
811251881Speter      if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
812251881Speter        {
813251881Speter          if (pb->skip_missing_merge_sources)
814251881Speter            continue;
815251881Speter          else
816251881Speter            return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
817251881Speter                                     _("Missing merge source path '%s'; try "
818251881Speter                                       "with --skip-missing-merge-sources"),
819251881Speter                                     merge_source);
820251881Speter        }
821251881Speter
822251881Speter      /* Possibly renumber revisions in merge source's rangelist. */
823251881Speter      if (pb->do_renumber_revs)
824251881Speter        {
825251881Speter          int i;
826251881Speter
827251881Speter          for (i = 0; i < rangelist->nelts; i++)
828251881Speter            {
829251881Speter              struct revmap_t *revmap_start;
830251881Speter              struct revmap_t *revmap_end;
831251881Speter              svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
832251881Speter                                                       svn_merge_range_t *);
833251881Speter
834251881Speter              revmap_start = apr_hash_get(pb->renumber_history,
835251881Speter                                          &range->start, sizeof(svn_revnum_t));
836251881Speter              if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
837251881Speter                return svn_error_createf
838251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
839251881Speter                   _("No valid revision range 'start' in filtered stream"));
840251881Speter
841251881Speter              revmap_end = apr_hash_get(pb->renumber_history,
842251881Speter                                        &range->end, sizeof(svn_revnum_t));
843251881Speter              if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
844251881Speter                return svn_error_createf
845251881Speter                  (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
846251881Speter                   _("No valid revision range 'end' in filtered stream"));
847251881Speter
848251881Speter              range->start = revmap_start->rev;
849251881Speter              range->end = revmap_end->rev;
850251881Speter            }
851251881Speter        }
852251881Speter      svn_hash_sets(final_mergeinfo, merge_source, rangelist);
853251881Speter    }
854251881Speter
855251881Speter  SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
856251881Speter  SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
857251881Speter  svn_pool_destroy(subpool);
858251881Speter
859251881Speter  return SVN_NO_ERROR;
860251881Speter}
861251881Speter
862251881Speter
863251881Speterstatic svn_error_t *
864251881Speterset_revision_property(void *revision_baton,
865251881Speter                      const char *name,
866251881Speter                      const svn_string_t *value)
867251881Speter{
868251881Speter  struct revision_baton_t *rb = revision_baton;
869251881Speter  apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
870251881Speter
871251881Speter  rb->has_props = TRUE;
872251881Speter  svn_hash_sets(rb->props,
873251881Speter                apr_pstrdup(hash_pool, name),
874251881Speter                svn_string_dup(value, hash_pool));
875251881Speter  return SVN_NO_ERROR;
876251881Speter}
877251881Speter
878251881Speter
879251881Speterstatic svn_error_t *
880251881Speterset_node_property(void *node_baton,
881251881Speter                  const char *name,
882251881Speter                  const svn_string_t *value)
883251881Speter{
884251881Speter  struct node_baton_t *nb = node_baton;
885251881Speter  struct revision_baton_t *rb = nb->rb;
886251881Speter
887251881Speter  if (nb->do_skip)
888251881Speter    return SVN_NO_ERROR;
889251881Speter
890251881Speter  if (! (nb->has_props || nb->has_prop_delta))
891251881Speter    return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
892251881Speter                             _("Delta property block detected, but deltas "
893251881Speter                               "are not enabled for node '%s' in original "
894251881Speter                               "revision %ld"),
895251881Speter                             nb->node_path, rb->rev_orig);
896251881Speter
897251881Speter  if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
898251881Speter    {
899251881Speter      svn_string_t *filtered_mergeinfo;  /* Avoid compiler warning. */
900251881Speter      apr_pool_t *pool = apr_hash_pool_get(rb->props);
901251881Speter      SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
902251881Speter      value = filtered_mergeinfo;
903251881Speter    }
904251881Speter
905251881Speter  nb->has_props = TRUE;
906251881Speter  write_prop_to_stringbuf(nb->props, name, value);
907251881Speter
908251881Speter  return SVN_NO_ERROR;
909251881Speter}
910251881Speter
911251881Speter
912251881Speterstatic svn_error_t *
913251881Speterdelete_node_property(void *node_baton, const char *name)
914251881Speter{
915251881Speter  struct node_baton_t *nb = node_baton;
916251881Speter  struct revision_baton_t *rb = nb->rb;
917251881Speter
918251881Speter  if (nb->do_skip)
919251881Speter    return SVN_NO_ERROR;
920251881Speter
921251881Speter  if (!nb->has_prop_delta)
922251881Speter    return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
923251881Speter                             _("Delta property block detected, but deltas "
924251881Speter                               "are not enabled for node '%s' in original "
925251881Speter                               "revision %ld"),
926251881Speter                             nb->node_path, rb->rev_orig);
927251881Speter
928251881Speter  nb->has_props = TRUE;
929251881Speter  write_propdel_to_stringbuf(&(nb->props), name);
930251881Speter
931251881Speter  return SVN_NO_ERROR;
932251881Speter}
933251881Speter
934251881Speter
935251881Speterstatic svn_error_t *
936251881Speterremove_node_props(void *node_baton)
937251881Speter{
938251881Speter  struct node_baton_t *nb = node_baton;
939251881Speter
940251881Speter  /* In this case, not actually indicating that the node *has* props,
941251881Speter     rather that we know about all the props that it has, since it now
942251881Speter     has none. */
943251881Speter  nb->has_props = TRUE;
944251881Speter
945251881Speter  return SVN_NO_ERROR;
946251881Speter}
947251881Speter
948251881Speter
949251881Speterstatic svn_error_t *
950251881Speterset_fulltext(svn_stream_t **stream, void *node_baton)
951251881Speter{
952251881Speter  struct node_baton_t *nb = node_baton;
953251881Speter
954251881Speter  if (!nb->do_skip)
955251881Speter    {
956251881Speter      nb->has_text = TRUE;
957251881Speter      if (! nb->writing_begun)
958251881Speter        SVN_ERR(output_node(nb));
959251881Speter      *stream = nb->rb->pb->out_stream;
960251881Speter    }
961251881Speter
962251881Speter  return SVN_NO_ERROR;
963251881Speter}
964251881Speter
965251881Speter
966251881Speter/* Finalize node */
967251881Speterstatic svn_error_t *
968251881Speterclose_node(void *node_baton)
969251881Speter{
970251881Speter  struct node_baton_t *nb = node_baton;
971251881Speter  apr_size_t len = 2;
972251881Speter
973251881Speter  /* Get out of here if we can. */
974251881Speter  if (nb->do_skip)
975251881Speter    return SVN_NO_ERROR;
976251881Speter
977251881Speter  /* If the node was not flushed already to output its text, do it now. */
978251881Speter  if (! nb->writing_begun)
979251881Speter    SVN_ERR(output_node(nb));
980251881Speter
981251881Speter  /* put an end to node. */
982251881Speter  SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
983251881Speter
984251881Speter  return SVN_NO_ERROR;
985251881Speter}
986251881Speter
987251881Speter
988251881Speter/* Finalize revision */
989251881Speterstatic svn_error_t *
990251881Speterclose_revision(void *revision_baton)
991251881Speter{
992251881Speter  struct revision_baton_t *rb = revision_baton;
993251881Speter
994251881Speter  /* If no node has yet flushed the revision, do it now. */
995251881Speter  if (! rb->writing_begun)
996251881Speter    return output_revision(rb);
997251881Speter  else
998251881Speter    return SVN_NO_ERROR;
999251881Speter}
1000251881Speter
1001251881Speter
1002251881Speter/* Filtering vtable */
1003251881Spetersvn_repos_parse_fns3_t filtering_vtable =
1004251881Speter  {
1005251881Speter    magic_header_record,
1006251881Speter    uuid_record,
1007251881Speter    new_revision_record,
1008251881Speter    new_node_record,
1009251881Speter    set_revision_property,
1010251881Speter    set_node_property,
1011251881Speter    delete_node_property,
1012251881Speter    remove_node_props,
1013251881Speter    set_fulltext,
1014251881Speter    NULL,
1015251881Speter    close_node,
1016251881Speter    close_revision
1017251881Speter  };
1018251881Speter
1019251881Speter
1020251881Speter
1021251881Speter/** Subcommands. **/
1022251881Speter
1023251881Speterstatic svn_opt_subcommand_t
1024251881Speter  subcommand_help,
1025251881Speter  subcommand_exclude,
1026251881Speter  subcommand_include;
1027251881Speter
1028251881Speterenum
1029251881Speter  {
1030251881Speter    svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
1031251881Speter    svndumpfilter__drop_all_empty_revs,
1032251881Speter    svndumpfilter__renumber_revs,
1033251881Speter    svndumpfilter__preserve_revprops,
1034251881Speter    svndumpfilter__skip_missing_merge_sources,
1035251881Speter    svndumpfilter__targets,
1036251881Speter    svndumpfilter__quiet,
1037251881Speter    svndumpfilter__glob,
1038251881Speter    svndumpfilter__version
1039251881Speter  };
1040251881Speter
1041251881Speter/* Option codes and descriptions.
1042251881Speter *
1043251881Speter * The entire list must be terminated with an entry of nulls.
1044251881Speter */
1045251881Speterstatic const apr_getopt_option_t options_table[] =
1046251881Speter  {
1047251881Speter    {"help",          'h', 0,
1048251881Speter     N_("show help on a subcommand")},
1049251881Speter
1050251881Speter    {NULL,            '?', 0,
1051251881Speter     N_("show help on a subcommand")},
1052251881Speter
1053251881Speter    {"version",            svndumpfilter__version, 0,
1054251881Speter     N_("show program version information") },
1055251881Speter    {"quiet",              svndumpfilter__quiet, 0,
1056251881Speter     N_("Do not display filtering statistics.") },
1057251881Speter    {"pattern",            svndumpfilter__glob, 0,
1058251881Speter     N_("Treat the path prefixes as file glob patterns.") },
1059251881Speter    {"drop-empty-revs",    svndumpfilter__drop_empty_revs, 0,
1060251881Speter     N_("Remove revisions emptied by filtering.")},
1061251881Speter    {"drop-all-empty-revs",    svndumpfilter__drop_all_empty_revs, 0,
1062251881Speter     N_("Remove all empty revisions found in dumpstream\n"
1063251881Speter        "                             except revision 0.")},
1064251881Speter    {"renumber-revs",      svndumpfilter__renumber_revs, 0,
1065251881Speter     N_("Renumber revisions left after filtering.") },
1066251881Speter    {"skip-missing-merge-sources",
1067251881Speter     svndumpfilter__skip_missing_merge_sources, 0,
1068251881Speter     N_("Skip missing merge sources.") },
1069251881Speter    {"preserve-revprops",  svndumpfilter__preserve_revprops, 0,
1070251881Speter     N_("Don't filter revision properties.") },
1071251881Speter    {"targets", svndumpfilter__targets, 1,
1072251881Speter     N_("Read additional prefixes, one per line, from\n"
1073251881Speter        "                             file ARG.")},
1074251881Speter    {NULL}
1075251881Speter  };
1076251881Speter
1077251881Speter
1078251881Speter/* Array of available subcommands.
1079251881Speter * The entire list must be terminated with an entry of nulls.
1080251881Speter */
1081251881Speterstatic const svn_opt_subcommand_desc2_t cmd_table[] =
1082251881Speter  {
1083251881Speter    {"exclude", subcommand_exclude, {0},
1084251881Speter     N_("Filter out nodes with given prefixes from dumpstream.\n"
1085251881Speter        "usage: svndumpfilter exclude PATH_PREFIX...\n"),
1086251881Speter     {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1087251881Speter      svndumpfilter__renumber_revs,
1088251881Speter      svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1089251881Speter      svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1090251881Speter      svndumpfilter__glob} },
1091251881Speter
1092251881Speter    {"include", subcommand_include, {0},
1093251881Speter     N_("Filter out nodes without given prefixes from dumpstream.\n"
1094251881Speter        "usage: svndumpfilter include PATH_PREFIX...\n"),
1095251881Speter     {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
1096251881Speter      svndumpfilter__renumber_revs,
1097251881Speter      svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
1098251881Speter      svndumpfilter__preserve_revprops, svndumpfilter__quiet,
1099251881Speter      svndumpfilter__glob} },
1100251881Speter
1101251881Speter    {"help", subcommand_help, {"?", "h"},
1102251881Speter     N_("Describe the usage of this program or its subcommands.\n"
1103251881Speter        "usage: svndumpfilter help [SUBCOMMAND...]\n"),
1104251881Speter     {0} },
1105251881Speter
1106251881Speter    { NULL, NULL, {0}, NULL, {0} }
1107251881Speter  };
1108251881Speter
1109251881Speter
1110251881Speter/* Baton for passing option/argument state to a subcommand function. */
1111251881Speterstruct svndumpfilter_opt_state
1112251881Speter{
1113251881Speter  svn_opt_revision_t start_revision;     /* -r X[:Y] is         */
1114251881Speter  svn_opt_revision_t end_revision;       /* not implemented.    */
1115251881Speter  svn_boolean_t quiet;                   /* --quiet             */
1116251881Speter  svn_boolean_t glob;                    /* --pattern           */
1117251881Speter  svn_boolean_t version;                 /* --version           */
1118251881Speter  svn_boolean_t drop_empty_revs;         /* --drop-empty-revs   */
1119251881Speter  svn_boolean_t drop_all_empty_revs;     /* --drop-all-empty-revs */
1120251881Speter  svn_boolean_t help;                    /* --help or -?        */
1121251881Speter  svn_boolean_t renumber_revs;           /* --renumber-revs     */
1122251881Speter  svn_boolean_t preserve_revprops;       /* --preserve-revprops */
1123251881Speter  svn_boolean_t skip_missing_merge_sources;
1124251881Speter                                         /* --skip-missing-merge-sources */
1125251881Speter  const char *targets_file;              /* --targets-file       */
1126251881Speter  apr_array_header_t *prefixes;          /* mainargs.           */
1127251881Speter};
1128251881Speter
1129251881Speter
1130251881Speterstatic svn_error_t *
1131251881Speterparse_baton_initialize(struct parse_baton_t **pb,
1132251881Speter                       struct svndumpfilter_opt_state *opt_state,
1133251881Speter                       svn_boolean_t do_exclude,
1134251881Speter                       apr_pool_t *pool)
1135251881Speter{
1136251881Speter  struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
1137251881Speter
1138251881Speter  /* Read the stream from STDIN.  Users can redirect a file. */
1139251881Speter  SVN_ERR(create_stdio_stream(&(baton->in_stream),
1140251881Speter                              apr_file_open_stdin, pool));
1141251881Speter
1142251881Speter  /* Have the parser dump results to STDOUT. Users can redirect a file. */
1143251881Speter  SVN_ERR(create_stdio_stream(&(baton->out_stream),
1144251881Speter                              apr_file_open_stdout, pool));
1145251881Speter
1146251881Speter  baton->do_exclude = do_exclude;
1147251881Speter
1148251881Speter  /* Ignore --renumber-revs if there can't possibly be
1149251881Speter     anything to renumber. */
1150251881Speter  baton->do_renumber_revs =
1151251881Speter    (opt_state->renumber_revs && (opt_state->drop_empty_revs
1152251881Speter                                  || opt_state->drop_all_empty_revs));
1153251881Speter
1154251881Speter  baton->drop_empty_revs = opt_state->drop_empty_revs;
1155251881Speter  baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
1156251881Speter  baton->preserve_revprops = opt_state->preserve_revprops;
1157251881Speter  baton->quiet = opt_state->quiet;
1158251881Speter  baton->glob = opt_state->glob;
1159251881Speter  baton->prefixes = opt_state->prefixes;
1160251881Speter  baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
1161251881Speter  baton->rev_drop_count = 0; /* used to shift revnums while filtering */
1162251881Speter  baton->dropped_nodes = apr_hash_make(pool);
1163251881Speter  baton->renumber_history = apr_hash_make(pool);
1164251881Speter  baton->last_live_revision = SVN_INVALID_REVNUM;
1165251881Speter  baton->oldest_original_rev = SVN_INVALID_REVNUM;
1166251881Speter  baton->allow_deltas = FALSE;
1167251881Speter
1168251881Speter  *pb = baton;
1169251881Speter  return SVN_NO_ERROR;
1170251881Speter}
1171251881Speter
1172251881Speter/* This implements `help` subcommand. */
1173251881Speterstatic svn_error_t *
1174251881Spetersubcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1175251881Speter{
1176251881Speter  struct svndumpfilter_opt_state *opt_state = baton;
1177251881Speter  const char *header =
1178251881Speter    _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
1179251881Speter      "Type 'svndumpfilter help <subcommand>' for help on a "
1180251881Speter      "specific subcommand.\n"
1181251881Speter      "Type 'svndumpfilter --version' to see the program version.\n"
1182251881Speter      "\n"
1183251881Speter      "Available subcommands:\n");
1184251881Speter
1185251881Speter  SVN_ERR(svn_opt_print_help4(os, "svndumpfilter",
1186251881Speter                              opt_state ? opt_state->version : FALSE,
1187251881Speter                              opt_state ? opt_state->quiet : FALSE,
1188251881Speter                              /*###opt_state ? opt_state->verbose :*/ FALSE,
1189251881Speter                              NULL, header, cmd_table, options_table,
1190251881Speter                              NULL, NULL, pool));
1191251881Speter
1192251881Speter  return SVN_NO_ERROR;
1193251881Speter}
1194251881Speter
1195251881Speter
1196251881Speter/* Version compatibility check */
1197251881Speterstatic svn_error_t *
1198251881Spetercheck_lib_versions(void)
1199251881Speter{
1200251881Speter  static const svn_version_checklist_t checklist[] =
1201251881Speter    {
1202251881Speter      { "svn_subr",  svn_subr_version },
1203251881Speter      { "svn_repos", svn_repos_version },
1204251881Speter      { "svn_delta", svn_delta_version },
1205251881Speter      { NULL, NULL }
1206251881Speter    };
1207251881Speter  SVN_VERSION_DEFINE(my_version);
1208251881Speter
1209262253Speter  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
1210251881Speter}
1211251881Speter
1212251881Speter
1213251881Speter/* Do the real work of filtering. */
1214251881Speterstatic svn_error_t *
1215251881Speterdo_filter(apr_getopt_t *os,
1216251881Speter          void *baton,
1217251881Speter          svn_boolean_t do_exclude,
1218251881Speter          apr_pool_t *pool)
1219251881Speter{
1220251881Speter  struct svndumpfilter_opt_state *opt_state = baton;
1221251881Speter  struct parse_baton_t *pb;
1222251881Speter  apr_hash_index_t *hi;
1223251881Speter  apr_array_header_t *keys;
1224251881Speter  int i, num_keys;
1225251881Speter
1226251881Speter  if (! opt_state->quiet)
1227251881Speter    {
1228251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1229251881Speter
1230251881Speter      if (opt_state->glob)
1231251881Speter        {
1232251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1233251881Speter                                      do_exclude
1234251881Speter                                      ? (opt_state->drop_empty_revs
1235251881Speter                                         || opt_state->drop_all_empty_revs)
1236251881Speter                                        ? _("Excluding (and dropping empty "
1237251881Speter                                            "revisions for) prefix patterns:\n")
1238251881Speter                                        : _("Excluding prefix patterns:\n")
1239251881Speter                                      : (opt_state->drop_empty_revs
1240251881Speter                                         || opt_state->drop_all_empty_revs)
1241251881Speter                                        ? _("Including (and dropping empty "
1242251881Speter                                            "revisions for) prefix patterns:\n")
1243251881Speter                                        : _("Including prefix patterns:\n")));
1244251881Speter        }
1245251881Speter      else
1246251881Speter        {
1247251881Speter          SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1248251881Speter                                      do_exclude
1249251881Speter                                      ? (opt_state->drop_empty_revs
1250251881Speter                                         || opt_state->drop_all_empty_revs)
1251251881Speter                                        ? _("Excluding (and dropping empty "
1252251881Speter                                            "revisions for) prefixes:\n")
1253251881Speter                                        : _("Excluding prefixes:\n")
1254251881Speter                                      : (opt_state->drop_empty_revs
1255251881Speter                                         || opt_state->drop_all_empty_revs)
1256251881Speter                                        ? _("Including (and dropping empty "
1257251881Speter                                            "revisions for) prefixes:\n")
1258251881Speter                                        : _("Including prefixes:\n")));
1259251881Speter        }
1260251881Speter
1261251881Speter      for (i = 0; i < opt_state->prefixes->nelts; i++)
1262251881Speter        {
1263251881Speter          svn_pool_clear(subpool);
1264251881Speter          SVN_ERR(svn_cmdline_fprintf
1265251881Speter                  (stderr, subpool, "   '%s'\n",
1266251881Speter                   APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
1267251881Speter        }
1268251881Speter
1269251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1270251881Speter      svn_pool_destroy(subpool);
1271251881Speter    }
1272251881Speter
1273251881Speter  SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
1274251881Speter  SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
1275251881Speter                                      TRUE, NULL, NULL, pool));
1276251881Speter
1277251881Speter  /* The rest of this is just reporting.  If we aren't reporting, get
1278251881Speter     outta here. */
1279251881Speter  if (opt_state->quiet)
1280251881Speter    return SVN_NO_ERROR;
1281251881Speter
1282251881Speter  SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
1283251881Speter
1284251881Speter  if (pb->rev_drop_count)
1285251881Speter    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1286251881Speter                                Q_("Dropped %d revision.\n\n",
1287251881Speter                                   "Dropped %d revisions.\n\n",
1288251881Speter                                   pb->rev_drop_count),
1289251881Speter                                pb->rev_drop_count));
1290251881Speter
1291251881Speter  if (pb->do_renumber_revs)
1292251881Speter    {
1293251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1294251881Speter      SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
1295251881Speter                                stderr, subpool));
1296251881Speter
1297251881Speter      /* Get the keys of the hash, sort them, then print the hash keys
1298251881Speter         and values, sorted by keys. */
1299251881Speter      num_keys = apr_hash_count(pb->renumber_history);
1300251881Speter      keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
1301251881Speter      for (hi = apr_hash_first(pool, pb->renumber_history);
1302251881Speter           hi;
1303251881Speter           hi = apr_hash_next(hi))
1304251881Speter        {
1305251881Speter          const svn_revnum_t *revnum = svn__apr_hash_index_key(hi);
1306251881Speter
1307251881Speter          APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
1308251881Speter        }
1309251881Speter      qsort(keys->elts, keys->nelts,
1310251881Speter            keys->elt_size, svn_sort_compare_revisions);
1311251881Speter      for (i = 0; i < keys->nelts; i++)
1312251881Speter        {
1313251881Speter          svn_revnum_t this_key;
1314251881Speter          struct revmap_t *this_val;
1315251881Speter
1316251881Speter          svn_pool_clear(subpool);
1317251881Speter          this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
1318251881Speter          this_val = apr_hash_get(pb->renumber_history, &this_key,
1319251881Speter                                  sizeof(this_key));
1320251881Speter          if (this_val->was_dropped)
1321251881Speter            SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1322251881Speter                                        _("   %ld => (dropped)\n"),
1323251881Speter                                        this_key));
1324251881Speter          else
1325251881Speter            SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1326251881Speter                                        "   %ld => %ld\n",
1327251881Speter                                        this_key, this_val->rev));
1328251881Speter        }
1329251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1330251881Speter      svn_pool_destroy(subpool);
1331251881Speter    }
1332251881Speter
1333251881Speter  if ((num_keys = apr_hash_count(pb->dropped_nodes)))
1334251881Speter    {
1335251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1336251881Speter      SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
1337251881Speter                                  Q_("Dropped %d node:\n",
1338251881Speter                                     "Dropped %d nodes:\n",
1339251881Speter                                     num_keys),
1340251881Speter                                  num_keys));
1341251881Speter
1342251881Speter      /* Get the keys of the hash, sort them, then print the hash keys
1343251881Speter         and values, sorted by keys. */
1344251881Speter      keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
1345251881Speter      for (hi = apr_hash_first(pool, pb->dropped_nodes);
1346251881Speter           hi;
1347251881Speter           hi = apr_hash_next(hi))
1348251881Speter        {
1349251881Speter          const char *path = svn__apr_hash_index_key(hi);
1350251881Speter
1351251881Speter          APR_ARRAY_PUSH(keys, const char *) = path;
1352251881Speter        }
1353251881Speter      qsort(keys->elts, keys->nelts, keys->elt_size, svn_sort_compare_paths);
1354251881Speter      for (i = 0; i < keys->nelts; i++)
1355251881Speter        {
1356251881Speter          svn_pool_clear(subpool);
1357251881Speter          SVN_ERR(svn_cmdline_fprintf
1358251881Speter                  (stderr, subpool, "   '%s'\n",
1359251881Speter                   (const char *)APR_ARRAY_IDX(keys, i, const char *)));
1360251881Speter        }
1361251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
1362251881Speter      svn_pool_destroy(subpool);
1363251881Speter    }
1364251881Speter
1365251881Speter  return SVN_NO_ERROR;
1366251881Speter}
1367251881Speter
1368251881Speter/* This implements `exclude' subcommand. */
1369251881Speterstatic svn_error_t *
1370251881Spetersubcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1371251881Speter{
1372251881Speter  return do_filter(os, baton, TRUE, pool);
1373251881Speter}
1374251881Speter
1375251881Speter
1376251881Speter/* This implements `include` subcommand. */
1377251881Speterstatic svn_error_t *
1378251881Spetersubcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1379251881Speter{
1380251881Speter  return do_filter(os, baton, FALSE, pool);
1381251881Speter}
1382251881Speter
1383251881Speter
1384251881Speter
1385251881Speter/** Main. **/
1386251881Speter
1387251881Speterint
1388251881Spetermain(int argc, const char *argv[])
1389251881Speter{
1390251881Speter  svn_error_t *err;
1391251881Speter  apr_status_t apr_err;
1392251881Speter  apr_pool_t *pool;
1393251881Speter
1394251881Speter  const svn_opt_subcommand_desc2_t *subcommand = NULL;
1395251881Speter  struct svndumpfilter_opt_state opt_state;
1396251881Speter  apr_getopt_t *os;
1397251881Speter  int opt_id;
1398251881Speter  apr_array_header_t *received_opts;
1399251881Speter  int i;
1400251881Speter
1401251881Speter
1402251881Speter  /* Initialize the app. */
1403251881Speter  if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
1404251881Speter    return EXIT_FAILURE;
1405251881Speter
1406251881Speter  /* Create our top-level pool.  Use a separate mutexless allocator,
1407251881Speter   * given this application is single threaded.
1408251881Speter   */
1409251881Speter  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1410251881Speter
1411251881Speter  /* Check library versions */
1412251881Speter  err = check_lib_versions();
1413251881Speter  if (err)
1414251881Speter    return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1415251881Speter
1416251881Speter  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1417251881Speter
1418251881Speter  /* Initialize the FS library. */
1419251881Speter  err = svn_fs_initialize(pool);
1420251881Speter  if (err)
1421251881Speter    return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1422251881Speter
1423251881Speter  if (argc <= 1)
1424251881Speter    {
1425251881Speter      SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1426251881Speter      svn_pool_destroy(pool);
1427251881Speter      return EXIT_FAILURE;
1428251881Speter    }
1429251881Speter
1430251881Speter  /* Initialize opt_state. */
1431251881Speter  memset(&opt_state, 0, sizeof(opt_state));
1432251881Speter  opt_state.start_revision.kind = svn_opt_revision_unspecified;
1433251881Speter  opt_state.end_revision.kind = svn_opt_revision_unspecified;
1434251881Speter
1435251881Speter  /* Parse options. */
1436251881Speter  err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1437251881Speter  if (err)
1438251881Speter    return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1439251881Speter
1440251881Speter  os->interleave = 1;
1441251881Speter  while (1)
1442251881Speter    {
1443251881Speter      const char *opt_arg;
1444251881Speter
1445251881Speter      /* Parse the next option. */
1446251881Speter      apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1447251881Speter      if (APR_STATUS_IS_EOF(apr_err))
1448251881Speter        break;
1449251881Speter      else if (apr_err)
1450251881Speter        {
1451251881Speter          SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1452251881Speter          svn_pool_destroy(pool);
1453251881Speter          return EXIT_FAILURE;
1454251881Speter        }
1455251881Speter
1456251881Speter      /* Stash the option code in an array before parsing it. */
1457251881Speter      APR_ARRAY_PUSH(received_opts, int) = opt_id;
1458251881Speter
1459251881Speter      switch (opt_id)
1460251881Speter        {
1461251881Speter        case 'h':
1462251881Speter        case '?':
1463251881Speter          opt_state.help = TRUE;
1464251881Speter          break;
1465251881Speter        case svndumpfilter__version:
1466251881Speter          opt_state.version = TRUE;
1467251881Speter          break;
1468251881Speter        case svndumpfilter__quiet:
1469251881Speter          opt_state.quiet = TRUE;
1470251881Speter          break;
1471251881Speter        case svndumpfilter__glob:
1472251881Speter          opt_state.glob = TRUE;
1473251881Speter          break;
1474251881Speter        case svndumpfilter__drop_empty_revs:
1475251881Speter          opt_state.drop_empty_revs = TRUE;
1476251881Speter          break;
1477251881Speter        case svndumpfilter__drop_all_empty_revs:
1478251881Speter          opt_state.drop_all_empty_revs = TRUE;
1479251881Speter          break;
1480251881Speter        case svndumpfilter__renumber_revs:
1481251881Speter          opt_state.renumber_revs = TRUE;
1482251881Speter          break;
1483251881Speter        case svndumpfilter__preserve_revprops:
1484251881Speter          opt_state.preserve_revprops = TRUE;
1485251881Speter          break;
1486251881Speter        case svndumpfilter__skip_missing_merge_sources:
1487251881Speter          opt_state.skip_missing_merge_sources = TRUE;
1488251881Speter          break;
1489251881Speter        case svndumpfilter__targets:
1490251881Speter          opt_state.targets_file = opt_arg;
1491251881Speter          break;
1492251881Speter        default:
1493251881Speter          {
1494251881Speter            SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1495251881Speter            svn_pool_destroy(pool);
1496251881Speter            return EXIT_FAILURE;
1497251881Speter          }
1498251881Speter        }  /* close `switch' */
1499251881Speter    }  /* close `while' */
1500251881Speter
1501251881Speter  /* Disallow simultaneous use of both --drop-empty-revs and
1502251881Speter     --drop-all-empty-revs. */
1503251881Speter  if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
1504251881Speter    {
1505251881Speter      err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
1506251881Speter                             _("--drop-empty-revs cannot be used with "
1507251881Speter                               "--drop-all-empty-revs"));
1508251881Speter      return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1509251881Speter    }
1510251881Speter
1511251881Speter  /* If the user asked for help, then the rest of the arguments are
1512251881Speter     the names of subcommands to get help on (if any), or else they're
1513251881Speter     just typos/mistakes.  Whatever the case, the subcommand to
1514251881Speter     actually run is subcommand_help(). */
1515251881Speter  if (opt_state.help)
1516251881Speter    subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
1517251881Speter
1518251881Speter  /* If we're not running the `help' subcommand, then look for a
1519251881Speter     subcommand in the first argument. */
1520251881Speter  if (subcommand == NULL)
1521251881Speter    {
1522251881Speter      if (os->ind >= os->argc)
1523251881Speter        {
1524251881Speter          if (opt_state.version)
1525251881Speter            {
1526251881Speter              /* Use the "help" subcommand to handle the "--version" option. */
1527251881Speter              static const svn_opt_subcommand_desc2_t pseudo_cmd =
1528251881Speter                { "--version", subcommand_help, {0}, "",
1529251881Speter                  {svndumpfilter__version,  /* must accept its own option */
1530251881Speter                   svndumpfilter__quiet,
1531251881Speter                  } };
1532251881Speter
1533251881Speter              subcommand = &pseudo_cmd;
1534251881Speter            }
1535251881Speter          else
1536251881Speter            {
1537251881Speter              svn_error_clear(svn_cmdline_fprintf
1538251881Speter                              (stderr, pool,
1539251881Speter                               _("Subcommand argument required\n")));
1540251881Speter              SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1541251881Speter              svn_pool_destroy(pool);
1542251881Speter              return EXIT_FAILURE;
1543251881Speter            }
1544251881Speter        }
1545251881Speter      else
1546251881Speter        {
1547251881Speter          const char *first_arg = os->argv[os->ind++];
1548251881Speter          subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
1549251881Speter          if (subcommand == NULL)
1550251881Speter            {
1551251881Speter              const char* first_arg_utf8;
1552251881Speter              if ((err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
1553251881Speter                                                 pool)))
1554251881Speter                return svn_cmdline_handle_exit_error(err, pool,
1555251881Speter                                                     "svndumpfilter: ");
1556251881Speter
1557251881Speter              svn_error_clear(
1558251881Speter                svn_cmdline_fprintf(stderr, pool,
1559251881Speter                                    _("Unknown subcommand: '%s'\n"),
1560251881Speter                                    first_arg_utf8));
1561251881Speter              SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1562251881Speter              svn_pool_destroy(pool);
1563251881Speter              return EXIT_FAILURE;
1564251881Speter            }
1565251881Speter        }
1566251881Speter    }
1567251881Speter
1568251881Speter  /* If there's a second argument, it's probably [one of] prefixes.
1569251881Speter     Every subcommand except `help' requires at least one, so we parse
1570251881Speter     them out here and store in opt_state. */
1571251881Speter
1572251881Speter  if (subcommand->cmd_func != subcommand_help)
1573251881Speter    {
1574251881Speter
1575251881Speter      opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
1576251881Speter                                          sizeof(const char *));
1577251881Speter      for (i = os->ind ; i< os->argc; i++)
1578251881Speter        {
1579251881Speter          const char *prefix;
1580251881Speter
1581251881Speter          /* Ensure that each prefix is UTF8-encoded, in internal
1582251881Speter             style, and absolute. */
1583251881Speter          SVN_INT_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
1584251881Speter          prefix = svn_relpath__internal_style(prefix, pool);
1585251881Speter          if (prefix[0] != '/')
1586251881Speter            prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
1587251881Speter          APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1588251881Speter        }
1589251881Speter
1590251881Speter      if (opt_state.targets_file)
1591251881Speter        {
1592251881Speter          svn_stringbuf_t *buffer, *buffer_utf8;
1593251881Speter          const char *utf8_targets_file;
1594251881Speter          apr_array_header_t *targets = apr_array_make(pool, 0,
1595251881Speter                                                       sizeof(const char *));
1596251881Speter
1597251881Speter          /* We need to convert to UTF-8 now, even before we divide
1598251881Speter             the targets into an array, because otherwise we wouldn't
1599251881Speter             know what delimiter to use for svn_cstring_split().  */
1600251881Speter
1601251881Speter          SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_targets_file,
1602251881Speter                                              opt_state.targets_file, pool));
1603251881Speter
1604251881Speter          SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_targets_file,
1605251881Speter                                               pool));
1606251881Speter          SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
1607251881Speter
1608251881Speter          targets = apr_array_append(pool,
1609251881Speter                         svn_cstring_split(buffer_utf8->data, "\n\r",
1610251881Speter                                           TRUE, pool),
1611251881Speter                         targets);
1612251881Speter
1613251881Speter          for (i = 0; i < targets->nelts; i++)
1614251881Speter            {
1615251881Speter              const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
1616251881Speter              if (prefix[0] != '/')
1617251881Speter                prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
1618251881Speter              APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
1619251881Speter            }
1620251881Speter        }
1621251881Speter
1622251881Speter      if (apr_is_empty_array(opt_state.prefixes))
1623251881Speter        {
1624251881Speter          svn_error_clear(svn_cmdline_fprintf
1625251881Speter                          (stderr, pool,
1626251881Speter                           _("\nError: no prefixes supplied.\n")));
1627251881Speter          svn_pool_destroy(pool);
1628251881Speter          return EXIT_FAILURE;
1629251881Speter        }
1630251881Speter    }
1631251881Speter
1632251881Speter
1633251881Speter  /* Check that the subcommand wasn't passed any inappropriate options. */
1634251881Speter  for (i = 0; i < received_opts->nelts; i++)
1635251881Speter    {
1636251881Speter      opt_id = APR_ARRAY_IDX(received_opts, i, int);
1637251881Speter
1638251881Speter      /* All commands implicitly accept --help, so just skip over this
1639251881Speter         when we see it. Note that we don't want to include this option
1640251881Speter         in their "accepted options" list because it would be awfully
1641251881Speter         redundant to display it in every commands' help text. */
1642251881Speter      if (opt_id == 'h' || opt_id == '?')
1643251881Speter        continue;
1644251881Speter
1645251881Speter      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1646251881Speter        {
1647251881Speter          const char *optstr;
1648251881Speter          const apr_getopt_option_t *badopt =
1649251881Speter            svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
1650251881Speter                                          pool);
1651251881Speter          svn_opt_format_option(&optstr, badopt, FALSE, pool);
1652251881Speter          if (subcommand->name[0] == '-')
1653251881Speter            SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1654251881Speter          else
1655251881Speter            svn_error_clear(svn_cmdline_fprintf
1656251881Speter                            (stderr, pool,
1657251881Speter                             _("Subcommand '%s' doesn't accept option '%s'\n"
1658251881Speter                               "Type 'svndumpfilter help %s' for usage.\n"),
1659251881Speter                             subcommand->name, optstr, subcommand->name));
1660251881Speter          svn_pool_destroy(pool);
1661251881Speter          return EXIT_FAILURE;
1662251881Speter        }
1663251881Speter    }
1664251881Speter
1665251881Speter  /* Run the subcommand. */
1666251881Speter  err = (*subcommand->cmd_func)(os, &opt_state, pool);
1667251881Speter  if (err)
1668251881Speter    {
1669251881Speter      /* For argument-related problems, suggest using the 'help'
1670251881Speter         subcommand. */
1671251881Speter      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1672251881Speter          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1673251881Speter        {
1674251881Speter          err = svn_error_quick_wrap(err,
1675251881Speter                                     _("Try 'svndumpfilter help' for more "
1676251881Speter                                       "info"));
1677251881Speter        }
1678251881Speter      return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
1679251881Speter    }
1680251881Speter  else
1681251881Speter    {
1682251881Speter      svn_pool_destroy(pool);
1683251881Speter
1684251881Speter      /* Flush stdout, making sure the user will see any print errors. */
1685251881Speter      SVN_INT_ERR(svn_cmdline_fflush(stdout));
1686251881Speter      return EXIT_SUCCESS;
1687251881Speter    }
1688251881Speter}
1689