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