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