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