svndumpfilter.c revision 251881
1/* 2 * svndumpfilter.c: Subversion dump stream filtering tool main file. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25#include <stdlib.h> 26 27#include <apr_file_io.h> 28 29#include "svn_private_config.h" 30#include "svn_cmdline.h" 31#include "svn_error.h" 32#include "svn_string.h" 33#include "svn_opt.h" 34#include "svn_utf.h" 35#include "svn_dirent_uri.h" 36#include "svn_path.h" 37#include "svn_hash.h" 38#include "svn_repos.h" 39#include "svn_fs.h" 40#include "svn_pools.h" 41#include "svn_sorts.h" 42#include "svn_props.h" 43#include "svn_mergeinfo.h" 44#include "svn_version.h" 45 46#include "private/svn_mergeinfo_private.h" 47#include "private/svn_cmdline_private.h" 48 49#ifdef _WIN32 50typedef apr_status_t (__stdcall *open_fn_t)(apr_file_t **, apr_pool_t *); 51#else 52typedef apr_status_t (*open_fn_t)(apr_file_t **, apr_pool_t *); 53#endif 54 55/*** Code. ***/ 56 57/* Helper to open stdio streams */ 58 59/* NOTE: we used to call svn_stream_from_stdio(), which wraps a stream 60 around a standard stdio.h FILE pointer. The problem is that these 61 pointers operate through C Run Time (CRT) on Win32, which does all 62 sorts of translation on them: LF's become CRLF's, and ctrl-Z's 63 embedded in Word documents are interpreted as premature EOF's. 64 65 So instead, we use apr_file_open_std*, which bypass the CRT and 66 directly wrap the OS's file-handles, which don't know or care about 67 translation. Thus dump/load works correctly on Win32. 68*/ 69static svn_error_t * 70create_stdio_stream(svn_stream_t **stream, 71 open_fn_t open_fn, 72 apr_pool_t *pool) 73{ 74 apr_file_t *stdio_file; 75 apr_status_t apr_err = open_fn(&stdio_file, pool); 76 77 if (apr_err) 78 return svn_error_wrap_apr(apr_err, _("Can't open stdio file")); 79 80 *stream = svn_stream_from_aprfile2(stdio_file, TRUE, pool); 81 return SVN_NO_ERROR; 82} 83 84 85/* Writes a property in dumpfile format to given stringbuf. */ 86static void 87write_prop_to_stringbuf(svn_stringbuf_t *strbuf, 88 const char *name, 89 const svn_string_t *value) 90{ 91 int bytes_used; 92 size_t namelen; 93 char buf[SVN_KEYLINE_MAXLEN]; 94 95 /* Output name length, then name. */ 96 namelen = strlen(name); 97 svn_stringbuf_appendbytes(strbuf, "K ", 2); 98 99 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen); 100 svn_stringbuf_appendbytes(strbuf, buf, bytes_used); 101 svn_stringbuf_appendbyte(strbuf, '\n'); 102 103 svn_stringbuf_appendbytes(strbuf, name, namelen); 104 svn_stringbuf_appendbyte(strbuf, '\n'); 105 106 /* Output value length, then value. */ 107 svn_stringbuf_appendbytes(strbuf, "V ", 2); 108 109 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len); 110 svn_stringbuf_appendbytes(strbuf, buf, bytes_used); 111 svn_stringbuf_appendbyte(strbuf, '\n'); 112 113 svn_stringbuf_appendbytes(strbuf, value->data, value->len); 114 svn_stringbuf_appendbyte(strbuf, '\n'); 115} 116 117 118/* Writes a property deletion in dumpfile format to given stringbuf. */ 119static void 120write_propdel_to_stringbuf(svn_stringbuf_t **strbuf, 121 const char *name) 122{ 123 int bytes_used; 124 size_t namelen; 125 char buf[SVN_KEYLINE_MAXLEN]; 126 127 /* Output name length, then name. */ 128 namelen = strlen(name); 129 svn_stringbuf_appendbytes(*strbuf, "D ", 2); 130 131 bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen); 132 svn_stringbuf_appendbytes(*strbuf, buf, bytes_used); 133 svn_stringbuf_appendbyte(*strbuf, '\n'); 134 135 svn_stringbuf_appendbytes(*strbuf, name, namelen); 136 svn_stringbuf_appendbyte(*strbuf, '\n'); 137} 138 139 140/* Compare the node-path PATH with the (const char *) prefixes in PFXLIST. 141 * Return TRUE if any prefix is a prefix of PATH (matching whole path 142 * components); FALSE otherwise. 143 * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */ 144static svn_boolean_t 145ary_prefix_match(const apr_array_header_t *pfxlist, const char *path) 146{ 147 int i; 148 size_t path_len = strlen(path); 149 150 for (i = 0; i < pfxlist->nelts; i++) 151 { 152 const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *); 153 size_t pfx_len = strlen(pfx); 154 155 if (path_len < pfx_len) 156 continue; 157 if (strncmp(path, pfx, pfx_len) == 0 158 && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/')) 159 return TRUE; 160 } 161 162 return FALSE; 163} 164 165 166/* Check whether we need to skip this PATH based on its presence in 167 the PREFIXES list, and the DO_EXCLUDE option. 168 PATH starts with a '/', as do the (const char *) paths in PREFIXES. */ 169static APR_INLINE svn_boolean_t 170skip_path(const char *path, const apr_array_header_t *prefixes, 171 svn_boolean_t do_exclude, svn_boolean_t glob) 172{ 173 const svn_boolean_t matches = 174 (glob 175 ? svn_cstring_match_glob_list(path, prefixes) 176 : ary_prefix_match(prefixes, path)); 177 178 /* NXOR */ 179 return (matches ? do_exclude : !do_exclude); 180} 181 182 183 184/* Note: the input stream parser calls us with events. 185 Output of the filtered dump occurs for the most part streamily with the 186 event callbacks, to avoid caching large quantities of data in memory. 187 The exceptions this are: 188 - All revision data (headers and props) must be cached until a non-skipped 189 node within the revision is found, or the revision is closed. 190 - Node headers and props must be cached until all props have been received 191 (to allow the Prop-content-length to be found). This is signalled either 192 by the node text arriving, or the node being closed. 193 The writing_begun members of the associated object batons track the state. 194 output_revision() and output_node() are called to cause this flushing of 195 cached data to occur. 196*/ 197 198 199/* Filtering batons */ 200 201struct revmap_t 202{ 203 svn_revnum_t rev; /* Last non-dropped revision to which this maps. */ 204 svn_boolean_t was_dropped; /* Was this revision dropped? */ 205}; 206 207struct parse_baton_t 208{ 209 /* Command-line options values. */ 210 svn_boolean_t do_exclude; 211 svn_boolean_t quiet; 212 svn_boolean_t glob; 213 svn_boolean_t drop_empty_revs; 214 svn_boolean_t drop_all_empty_revs; 215 svn_boolean_t do_renumber_revs; 216 svn_boolean_t preserve_revprops; 217 svn_boolean_t skip_missing_merge_sources; 218 svn_boolean_t allow_deltas; 219 apr_array_header_t *prefixes; 220 221 /* Input and output streams. */ 222 svn_stream_t *in_stream; 223 svn_stream_t *out_stream; 224 225 /* State for the filtering process. */ 226 apr_int32_t rev_drop_count; 227 apr_hash_t *dropped_nodes; 228 apr_hash_t *renumber_history; /* svn_revnum_t -> struct revmap_t */ 229 svn_revnum_t last_live_revision; 230 /* The oldest original revision, greater than r0, in the input 231 stream which was not filtered. */ 232 svn_revnum_t oldest_original_rev; 233}; 234 235struct revision_baton_t 236{ 237 /* Reference to the global parse baton. */ 238 struct parse_baton_t *pb; 239 240 /* Does this revision have node or prop changes? */ 241 svn_boolean_t has_nodes; 242 svn_boolean_t has_props; 243 244 /* Did we drop any nodes? */ 245 svn_boolean_t had_dropped_nodes; 246 247 /* Written to output stream? */ 248 svn_boolean_t writing_begun; 249 250 /* The original and new (re-mapped) revision numbers. */ 251 svn_revnum_t rev_orig; 252 svn_revnum_t rev_actual; 253 254 /* Pointers to dumpfile data. */ 255 svn_stringbuf_t *header; 256 apr_hash_t *props; 257}; 258 259struct node_baton_t 260{ 261 /* Reference to the current revision baton. */ 262 struct revision_baton_t *rb; 263 264 /* Are we skipping this node? */ 265 svn_boolean_t do_skip; 266 267 /* Have we been instructed to change or remove props on, or change 268 the text of, this node? */ 269 svn_boolean_t has_props; 270 svn_boolean_t has_text; 271 272 /* Written to output stream? */ 273 svn_boolean_t writing_begun; 274 275 /* The text content length according to the dumpfile headers, because we 276 need the length before we have the actual text. */ 277 svn_filesize_t tcl; 278 279 /* Pointers to dumpfile data. */ 280 svn_stringbuf_t *header; 281 svn_stringbuf_t *props; 282 283 /* Expect deltas? */ 284 svn_boolean_t has_prop_delta; 285 svn_boolean_t has_text_delta; 286 287 /* We might need the node path in a parse error message. */ 288 char *node_path; 289}; 290 291 292 293/* Filtering vtable members */ 294 295/* File-format stamp. */ 296static svn_error_t * 297magic_header_record(int version, void *parse_baton, apr_pool_t *pool) 298{ 299 struct parse_baton_t *pb = parse_baton; 300 301 if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS) 302 pb->allow_deltas = TRUE; 303 304 SVN_ERR(svn_stream_printf(pb->out_stream, pool, 305 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", 306 version)); 307 308 return SVN_NO_ERROR; 309} 310 311 312/* New revision: set up revision_baton, decide if we skip it. */ 313static svn_error_t * 314new_revision_record(void **revision_baton, 315 apr_hash_t *headers, 316 void *parse_baton, 317 apr_pool_t *pool) 318{ 319 struct revision_baton_t *rb; 320 apr_hash_index_t *hi; 321 const char *rev_orig; 322 svn_stream_t *header_stream; 323 324 *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t)); 325 rb = *revision_baton; 326 rb->pb = parse_baton; 327 rb->has_nodes = FALSE; 328 rb->has_props = FALSE; 329 rb->had_dropped_nodes = FALSE; 330 rb->writing_begun = FALSE; 331 rb->header = svn_stringbuf_create_empty(pool); 332 rb->props = apr_hash_make(pool); 333 334 header_stream = svn_stream_from_stringbuf(rb->header, pool); 335 336 rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER); 337 rb->rev_orig = SVN_STR_TO_REV(rev_orig); 338 339 if (rb->pb->do_renumber_revs) 340 rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count; 341 else 342 rb->rev_actual = rb->rev_orig; 343 344 SVN_ERR(svn_stream_printf(header_stream, pool, 345 SVN_REPOS_DUMPFILE_REVISION_NUMBER ": %ld\n", 346 rb->rev_actual)); 347 348 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi)) 349 { 350 const char *key = svn__apr_hash_index_key(hi); 351 const char *val = svn__apr_hash_index_val(hi); 352 353 if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH)) 354 || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH)) 355 || (!strcmp(key, SVN_REPOS_DUMPFILE_REVISION_NUMBER))) 356 continue; 357 358 /* passthru: put header into header stringbuf. */ 359 360 SVN_ERR(svn_stream_printf(header_stream, pool, "%s: %s\n", 361 key, val)); 362 } 363 364 SVN_ERR(svn_stream_close(header_stream)); 365 366 return SVN_NO_ERROR; 367} 368 369 370/* Output revision to dumpstream 371 This may be called by new_node_record(), iff rb->has_nodes has been set 372 to TRUE, or by close_revision() otherwise. This must only be called 373 if rb->writing_begun is FALSE. */ 374static svn_error_t * 375output_revision(struct revision_baton_t *rb) 376{ 377 int bytes_used; 378 char buf[SVN_KEYLINE_MAXLEN]; 379 apr_hash_index_t *hi; 380 svn_boolean_t write_out_rev = FALSE; 381 apr_pool_t *hash_pool = apr_hash_pool_get(rb->props); 382 svn_stringbuf_t *props = svn_stringbuf_create_empty(hash_pool); 383 apr_pool_t *subpool = svn_pool_create(hash_pool); 384 385 rb->writing_begun = TRUE; 386 387 /* If this revision has no nodes left because the ones it had were 388 dropped, and we are not dropping empty revisions, and we were not 389 told to preserve revision props, then we want to fixup the 390 revision props to only contain: 391 - the date 392 - a log message that reports that this revision is just stuffing. */ 393 if ((! rb->pb->preserve_revprops) 394 && (! rb->has_nodes) 395 && rb->had_dropped_nodes 396 && (! rb->pb->drop_empty_revs) 397 && (! rb->pb->drop_all_empty_revs)) 398 { 399 apr_hash_t *old_props = rb->props; 400 rb->has_props = TRUE; 401 rb->props = apr_hash_make(hash_pool); 402 svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE, 403 svn_hash_gets(old_props, SVN_PROP_REVISION_DATE)); 404 svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG, 405 svn_string_create(_("This is an empty revision for " 406 "padding."), hash_pool)); 407 } 408 409 /* Now, "rasterize" the props to a string, and append the property 410 information to the header string. */ 411 if (rb->has_props) 412 { 413 for (hi = apr_hash_first(subpool, rb->props); 414 hi; 415 hi = apr_hash_next(hi)) 416 { 417 const char *pname = svn__apr_hash_index_key(hi); 418 const svn_string_t *pval = svn__apr_hash_index_val(hi); 419 420 write_prop_to_stringbuf(props, pname, pval); 421 } 422 svn_stringbuf_appendcstr(props, "PROPS-END\n"); 423 svn_stringbuf_appendcstr(rb->header, 424 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH); 425 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT, 426 props->len); 427 svn_stringbuf_appendbytes(rb->header, buf, bytes_used); 428 svn_stringbuf_appendbyte(rb->header, '\n'); 429 } 430 431 svn_stringbuf_appendcstr(rb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH); 432 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT, props->len); 433 svn_stringbuf_appendbytes(rb->header, buf, bytes_used); 434 svn_stringbuf_appendbyte(rb->header, '\n'); 435 436 /* put an end to headers */ 437 svn_stringbuf_appendbyte(rb->header, '\n'); 438 439 /* put an end to revision */ 440 svn_stringbuf_appendbyte(props, '\n'); 441 442 /* write out the revision */ 443 /* Revision is written out in the following cases: 444 1. If the revision has nodes or 445 it is revision 0 (Special case: To preserve the props on r0). 446 2. --drop-empty-revs has been supplied, 447 but revision has not all nodes dropped. 448 3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied, 449 write out the revision which has no nodes to begin with. 450 */ 451 if (rb->has_nodes || (rb->rev_orig == 0)) 452 write_out_rev = TRUE; 453 else if (rb->pb->drop_empty_revs) 454 write_out_rev = ! rb->had_dropped_nodes; 455 else if (! rb->pb->drop_all_empty_revs) 456 write_out_rev = TRUE; 457 458 if (write_out_rev) 459 { 460 /* This revision is a keeper. */ 461 SVN_ERR(svn_stream_write(rb->pb->out_stream, 462 rb->header->data, &(rb->header->len))); 463 SVN_ERR(svn_stream_write(rb->pb->out_stream, 464 props->data, &(props->len))); 465 466 /* Stash the oldest original rev not dropped. */ 467 if (rb->rev_orig > 0 468 && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev)) 469 rb->pb->oldest_original_rev = rb->rev_orig; 470 471 if (rb->pb->do_renumber_revs) 472 { 473 svn_revnum_t *rr_key; 474 struct revmap_t *rr_val; 475 apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history); 476 rr_key = apr_palloc(rr_pool, sizeof(*rr_key)); 477 rr_val = apr_palloc(rr_pool, sizeof(*rr_val)); 478 *rr_key = rb->rev_orig; 479 rr_val->rev = rb->rev_actual; 480 rr_val->was_dropped = FALSE; 481 apr_hash_set(rb->pb->renumber_history, rr_key, 482 sizeof(*rr_key), rr_val); 483 rb->pb->last_live_revision = rb->rev_actual; 484 } 485 486 if (! rb->pb->quiet) 487 SVN_ERR(svn_cmdline_fprintf(stderr, subpool, 488 _("Revision %ld committed as %ld.\n"), 489 rb->rev_orig, rb->rev_actual)); 490 } 491 else 492 { 493 /* We're dropping this revision. */ 494 rb->pb->rev_drop_count++; 495 if (rb->pb->do_renumber_revs) 496 { 497 svn_revnum_t *rr_key; 498 struct revmap_t *rr_val; 499 apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history); 500 rr_key = apr_palloc(rr_pool, sizeof(*rr_key)); 501 rr_val = apr_palloc(rr_pool, sizeof(*rr_val)); 502 *rr_key = rb->rev_orig; 503 rr_val->rev = rb->pb->last_live_revision; 504 rr_val->was_dropped = TRUE; 505 apr_hash_set(rb->pb->renumber_history, rr_key, 506 sizeof(*rr_key), rr_val); 507 } 508 509 if (! rb->pb->quiet) 510 SVN_ERR(svn_cmdline_fprintf(stderr, subpool, 511 _("Revision %ld skipped.\n"), 512 rb->rev_orig)); 513 } 514 svn_pool_destroy(subpool); 515 return SVN_NO_ERROR; 516} 517 518 519/* UUID record here: dump it, as we do not filter them. */ 520static svn_error_t * 521uuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool) 522{ 523 struct parse_baton_t *pb = parse_baton; 524 SVN_ERR(svn_stream_printf(pb->out_stream, pool, 525 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid)); 526 return SVN_NO_ERROR; 527} 528 529 530/* New node here. Set up node_baton by copying headers. */ 531static svn_error_t * 532new_node_record(void **node_baton, 533 apr_hash_t *headers, 534 void *rev_baton, 535 apr_pool_t *pool) 536{ 537 struct parse_baton_t *pb; 538 struct node_baton_t *nb; 539 char *node_path, *copyfrom_path; 540 apr_hash_index_t *hi; 541 const char *tcl; 542 543 *node_baton = apr_palloc(pool, sizeof(struct node_baton_t)); 544 nb = *node_baton; 545 nb->rb = rev_baton; 546 pb = nb->rb->pb; 547 548 node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH); 549 copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH); 550 551 /* Ensure that paths start with a leading '/'. */ 552 if (node_path[0] != '/') 553 node_path = apr_pstrcat(pool, "/", node_path, (char *)NULL); 554 if (copyfrom_path && copyfrom_path[0] != '/') 555 copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, (char *)NULL); 556 557 nb->do_skip = skip_path(node_path, pb->prefixes, 558 pb->do_exclude, pb->glob); 559 560 /* If we're skipping the node, take note of path, discarding the 561 rest. */ 562 if (nb->do_skip) 563 { 564 svn_hash_sets(pb->dropped_nodes, 565 apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes), 566 node_path), 567 (void *)1); 568 nb->rb->had_dropped_nodes = TRUE; 569 } 570 else 571 { 572 tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH); 573 574 /* Test if this node was copied from dropped source. */ 575 if (copyfrom_path && 576 skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob)) 577 { 578 /* This node was copied from a dropped source. 579 We have a problem, since we did not want to drop this node too. 580 581 However, there is one special case we'll handle. If the node is 582 a file, and this was a copy-and-modify operation, then the 583 dumpfile should contain the new contents of the file. In this 584 scenario, we'll just do an add without history using the new 585 contents. */ 586 const char *kind; 587 kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND); 588 589 /* If there is a Text-content-length header, and the kind is 590 "file", we just fallback to an add without history. */ 591 if (tcl && (strcmp(kind, "file") == 0)) 592 { 593 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, 594 NULL); 595 svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, 596 NULL); 597 copyfrom_path = NULL; 598 } 599 /* Else, this is either a directory or a file whose contents we 600 don't have readily available. */ 601 else 602 { 603 return svn_error_createf 604 (SVN_ERR_INCOMPLETE_DATA, 0, 605 _("Invalid copy source path '%s'"), copyfrom_path); 606 } 607 } 608 609 nb->has_props = FALSE; 610 nb->has_text = FALSE; 611 nb->has_prop_delta = FALSE; 612 nb->has_text_delta = FALSE; 613 nb->writing_begun = FALSE; 614 nb->tcl = tcl ? svn__atoui64(tcl) : 0; 615 nb->header = svn_stringbuf_create_empty(pool); 616 nb->props = svn_stringbuf_create_empty(pool); 617 nb->node_path = apr_pstrdup(pool, node_path); 618 619 /* Now we know for sure that we have a node that will not be 620 skipped, flush the revision if it has not already been done. */ 621 nb->rb->has_nodes = TRUE; 622 if (! nb->rb->writing_begun) 623 SVN_ERR(output_revision(nb->rb)); 624 625 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi)) 626 { 627 const char *key = svn__apr_hash_index_key(hi); 628 const char *val = svn__apr_hash_index_val(hi); 629 630 if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA)) 631 && (!strcmp(val, "true"))) 632 nb->has_prop_delta = TRUE; 633 634 if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA)) 635 && (!strcmp(val, "true"))) 636 nb->has_text_delta = TRUE; 637 638 if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH)) 639 || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH)) 640 || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH))) 641 continue; 642 643 /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions. 644 The number points to some revision in the past. We keep track 645 of revision renumbering in an apr_hash, which maps original 646 revisions to new ones. Dropped revision are mapped to -1. 647 This should never happen here. 648 */ 649 if (pb->do_renumber_revs 650 && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV))) 651 { 652 svn_revnum_t cf_orig_rev; 653 struct revmap_t *cf_renum_val; 654 655 cf_orig_rev = SVN_STR_TO_REV(val); 656 cf_renum_val = apr_hash_get(pb->renumber_history, 657 &cf_orig_rev, 658 sizeof(svn_revnum_t)); 659 if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev))) 660 return svn_error_createf 661 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 662 _("No valid copyfrom revision in filtered stream")); 663 SVN_ERR(svn_stream_printf 664 (nb->rb->pb->out_stream, pool, 665 SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV ": %ld\n", 666 cf_renum_val->rev)); 667 continue; 668 } 669 670 /* passthru: put header straight to output */ 671 672 SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream, 673 pool, "%s: %s\n", 674 key, val)); 675 } 676 } 677 678 return SVN_NO_ERROR; 679} 680 681 682/* Output node header and props to dumpstream 683 This will be called by set_fulltext() after setting nb->has_text to TRUE, 684 if the node has any text, or by close_node() otherwise. This must only 685 be called if nb->writing_begun is FALSE. */ 686static svn_error_t * 687output_node(struct node_baton_t *nb) 688{ 689 int bytes_used; 690 char buf[SVN_KEYLINE_MAXLEN]; 691 692 nb->writing_begun = TRUE; 693 694 /* when there are no props nb->props->len would be zero and won't mess up 695 Content-Length. */ 696 if (nb->has_props) 697 svn_stringbuf_appendcstr(nb->props, "PROPS-END\n"); 698 699 /* 1. recalculate & check text-md5 if present. Passed through right now. */ 700 701 /* 2. recalculate and add content-lengths */ 702 703 if (nb->has_props) 704 { 705 svn_stringbuf_appendcstr(nb->header, 706 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH); 707 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT, 708 nb->props->len); 709 svn_stringbuf_appendbytes(nb->header, buf, bytes_used); 710 svn_stringbuf_appendbyte(nb->header, '\n'); 711 } 712 if (nb->has_text) 713 { 714 svn_stringbuf_appendcstr(nb->header, 715 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH); 716 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT, 717 nb->tcl); 718 svn_stringbuf_appendbytes(nb->header, buf, bytes_used); 719 svn_stringbuf_appendbyte(nb->header, '\n'); 720 } 721 svn_stringbuf_appendcstr(nb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH); 722 bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT, 723 (svn_filesize_t) (nb->props->len + nb->tcl)); 724 svn_stringbuf_appendbytes(nb->header, buf, bytes_used); 725 svn_stringbuf_appendbyte(nb->header, '\n'); 726 727 /* put an end to headers */ 728 svn_stringbuf_appendbyte(nb->header, '\n'); 729 730 /* 3. output all the stuff */ 731 732 SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, 733 nb->header->data , &(nb->header->len))); 734 SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, 735 nb->props->data , &(nb->props->len))); 736 737 return SVN_NO_ERROR; 738} 739 740 741/* Examine the mergeinfo in INITIAL_VAL, omitting missing merge 742 sources or renumbering revisions in rangelists as appropriate, and 743 return the (possibly new) mergeinfo in *FINAL_VAL (allocated from 744 POOL). */ 745static svn_error_t * 746adjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val, 747 struct revision_baton_t *rb, apr_pool_t *pool) 748{ 749 apr_hash_t *mergeinfo; 750 apr_hash_t *final_mergeinfo = apr_hash_make(pool); 751 apr_hash_index_t *hi; 752 apr_pool_t *subpool = svn_pool_create(pool); 753 754 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool)); 755 756 /* Issue #3020: If we are skipping missing merge sources, then also 757 filter mergeinfo ranges as old or older than the oldest revision in the 758 dump stream. Those older than the oldest obviously refer to history 759 outside of the dump stream. The oldest rev itself is present in the 760 dump, but cannot be a valid merge source revision since it is the 761 start of all history. E.g. if we dump -r100:400 then dumpfilter the 762 result with --skip-missing-merge-sources, any mergeinfo with revision 763 100 implies a change of -r99:100, but r99 is part of the history we 764 want filtered. This is analogous to how r1 is always meaningless as 765 a merge source revision. 766 767 If the oldest rev is r0 then there is nothing to filter. */ 768 if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0) 769 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 770 &mergeinfo, mergeinfo, 771 rb->pb->oldest_original_rev, 0, 772 FALSE, subpool, subpool)); 773 774 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi)) 775 { 776 const char *merge_source = svn__apr_hash_index_key(hi); 777 svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); 778 struct parse_baton_t *pb = rb->pb; 779 780 /* Determine whether the merge_source is a part of the prefix. */ 781 if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob)) 782 { 783 if (pb->skip_missing_merge_sources) 784 continue; 785 else 786 return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0, 787 _("Missing merge source path '%s'; try " 788 "with --skip-missing-merge-sources"), 789 merge_source); 790 } 791 792 /* Possibly renumber revisions in merge source's rangelist. */ 793 if (pb->do_renumber_revs) 794 { 795 int i; 796 797 for (i = 0; i < rangelist->nelts; i++) 798 { 799 struct revmap_t *revmap_start; 800 struct revmap_t *revmap_end; 801 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i, 802 svn_merge_range_t *); 803 804 revmap_start = apr_hash_get(pb->renumber_history, 805 &range->start, sizeof(svn_revnum_t)); 806 if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev))) 807 return svn_error_createf 808 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 809 _("No valid revision range 'start' in filtered stream")); 810 811 revmap_end = apr_hash_get(pb->renumber_history, 812 &range->end, sizeof(svn_revnum_t)); 813 if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev))) 814 return svn_error_createf 815 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 816 _("No valid revision range 'end' in filtered stream")); 817 818 range->start = revmap_start->rev; 819 range->end = revmap_end->rev; 820 } 821 } 822 svn_hash_sets(final_mergeinfo, merge_source, rangelist); 823 } 824 825 SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool)); 826 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool)); 827 svn_pool_destroy(subpool); 828 829 return SVN_NO_ERROR; 830} 831 832 833static svn_error_t * 834set_revision_property(void *revision_baton, 835 const char *name, 836 const svn_string_t *value) 837{ 838 struct revision_baton_t *rb = revision_baton; 839 apr_pool_t *hash_pool = apr_hash_pool_get(rb->props); 840 841 rb->has_props = TRUE; 842 svn_hash_sets(rb->props, 843 apr_pstrdup(hash_pool, name), 844 svn_string_dup(value, hash_pool)); 845 return SVN_NO_ERROR; 846} 847 848 849static svn_error_t * 850set_node_property(void *node_baton, 851 const char *name, 852 const svn_string_t *value) 853{ 854 struct node_baton_t *nb = node_baton; 855 struct revision_baton_t *rb = nb->rb; 856 857 if (nb->do_skip) 858 return SVN_NO_ERROR; 859 860 if (! (nb->has_props || nb->has_prop_delta)) 861 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL, 862 _("Delta property block detected, but deltas " 863 "are not enabled for node '%s' in original " 864 "revision %ld"), 865 nb->node_path, rb->rev_orig); 866 867 if (strcmp(name, SVN_PROP_MERGEINFO) == 0) 868 { 869 svn_string_t *filtered_mergeinfo; /* Avoid compiler warning. */ 870 apr_pool_t *pool = apr_hash_pool_get(rb->props); 871 SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool)); 872 value = filtered_mergeinfo; 873 } 874 875 nb->has_props = TRUE; 876 write_prop_to_stringbuf(nb->props, name, value); 877 878 return SVN_NO_ERROR; 879} 880 881 882static svn_error_t * 883delete_node_property(void *node_baton, const char *name) 884{ 885 struct node_baton_t *nb = node_baton; 886 struct revision_baton_t *rb = nb->rb; 887 888 if (nb->do_skip) 889 return SVN_NO_ERROR; 890 891 if (!nb->has_prop_delta) 892 return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL, 893 _("Delta property block detected, but deltas " 894 "are not enabled for node '%s' in original " 895 "revision %ld"), 896 nb->node_path, rb->rev_orig); 897 898 nb->has_props = TRUE; 899 write_propdel_to_stringbuf(&(nb->props), name); 900 901 return SVN_NO_ERROR; 902} 903 904 905static svn_error_t * 906remove_node_props(void *node_baton) 907{ 908 struct node_baton_t *nb = node_baton; 909 910 /* In this case, not actually indicating that the node *has* props, 911 rather that we know about all the props that it has, since it now 912 has none. */ 913 nb->has_props = TRUE; 914 915 return SVN_NO_ERROR; 916} 917 918 919static svn_error_t * 920set_fulltext(svn_stream_t **stream, void *node_baton) 921{ 922 struct node_baton_t *nb = node_baton; 923 924 if (!nb->do_skip) 925 { 926 nb->has_text = TRUE; 927 if (! nb->writing_begun) 928 SVN_ERR(output_node(nb)); 929 *stream = nb->rb->pb->out_stream; 930 } 931 932 return SVN_NO_ERROR; 933} 934 935 936/* Finalize node */ 937static svn_error_t * 938close_node(void *node_baton) 939{ 940 struct node_baton_t *nb = node_baton; 941 apr_size_t len = 2; 942 943 /* Get out of here if we can. */ 944 if (nb->do_skip) 945 return SVN_NO_ERROR; 946 947 /* If the node was not flushed already to output its text, do it now. */ 948 if (! nb->writing_begun) 949 SVN_ERR(output_node(nb)); 950 951 /* put an end to node. */ 952 SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len)); 953 954 return SVN_NO_ERROR; 955} 956 957 958/* Finalize revision */ 959static svn_error_t * 960close_revision(void *revision_baton) 961{ 962 struct revision_baton_t *rb = revision_baton; 963 964 /* If no node has yet flushed the revision, do it now. */ 965 if (! rb->writing_begun) 966 return output_revision(rb); 967 else 968 return SVN_NO_ERROR; 969} 970 971 972/* Filtering vtable */ 973svn_repos_parse_fns3_t filtering_vtable = 974 { 975 magic_header_record, 976 uuid_record, 977 new_revision_record, 978 new_node_record, 979 set_revision_property, 980 set_node_property, 981 delete_node_property, 982 remove_node_props, 983 set_fulltext, 984 NULL, 985 close_node, 986 close_revision 987 }; 988 989 990 991/** Subcommands. **/ 992 993static svn_opt_subcommand_t 994 subcommand_help, 995 subcommand_exclude, 996 subcommand_include; 997 998enum 999 { 1000 svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID, 1001 svndumpfilter__drop_all_empty_revs, 1002 svndumpfilter__renumber_revs, 1003 svndumpfilter__preserve_revprops, 1004 svndumpfilter__skip_missing_merge_sources, 1005 svndumpfilter__targets, 1006 svndumpfilter__quiet, 1007 svndumpfilter__glob, 1008 svndumpfilter__version 1009 }; 1010 1011/* Option codes and descriptions. 1012 * 1013 * The entire list must be terminated with an entry of nulls. 1014 */ 1015static const apr_getopt_option_t options_table[] = 1016 { 1017 {"help", 'h', 0, 1018 N_("show help on a subcommand")}, 1019 1020 {NULL, '?', 0, 1021 N_("show help on a subcommand")}, 1022 1023 {"version", svndumpfilter__version, 0, 1024 N_("show program version information") }, 1025 {"quiet", svndumpfilter__quiet, 0, 1026 N_("Do not display filtering statistics.") }, 1027 {"pattern", svndumpfilter__glob, 0, 1028 N_("Treat the path prefixes as file glob patterns.") }, 1029 {"drop-empty-revs", svndumpfilter__drop_empty_revs, 0, 1030 N_("Remove revisions emptied by filtering.")}, 1031 {"drop-all-empty-revs", svndumpfilter__drop_all_empty_revs, 0, 1032 N_("Remove all empty revisions found in dumpstream\n" 1033 " except revision 0.")}, 1034 {"renumber-revs", svndumpfilter__renumber_revs, 0, 1035 N_("Renumber revisions left after filtering.") }, 1036 {"skip-missing-merge-sources", 1037 svndumpfilter__skip_missing_merge_sources, 0, 1038 N_("Skip missing merge sources.") }, 1039 {"preserve-revprops", svndumpfilter__preserve_revprops, 0, 1040 N_("Don't filter revision properties.") }, 1041 {"targets", svndumpfilter__targets, 1, 1042 N_("Read additional prefixes, one per line, from\n" 1043 " file ARG.")}, 1044 {NULL} 1045 }; 1046 1047 1048/* Array of available subcommands. 1049 * The entire list must be terminated with an entry of nulls. 1050 */ 1051static const svn_opt_subcommand_desc2_t cmd_table[] = 1052 { 1053 {"exclude", subcommand_exclude, {0}, 1054 N_("Filter out nodes with given prefixes from dumpstream.\n" 1055 "usage: svndumpfilter exclude PATH_PREFIX...\n"), 1056 {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs, 1057 svndumpfilter__renumber_revs, 1058 svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets, 1059 svndumpfilter__preserve_revprops, svndumpfilter__quiet, 1060 svndumpfilter__glob} }, 1061 1062 {"include", subcommand_include, {0}, 1063 N_("Filter out nodes without given prefixes from dumpstream.\n" 1064 "usage: svndumpfilter include PATH_PREFIX...\n"), 1065 {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs, 1066 svndumpfilter__renumber_revs, 1067 svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets, 1068 svndumpfilter__preserve_revprops, svndumpfilter__quiet, 1069 svndumpfilter__glob} }, 1070 1071 {"help", subcommand_help, {"?", "h"}, 1072 N_("Describe the usage of this program or its subcommands.\n" 1073 "usage: svndumpfilter help [SUBCOMMAND...]\n"), 1074 {0} }, 1075 1076 { NULL, NULL, {0}, NULL, {0} } 1077 }; 1078 1079 1080/* Baton for passing option/argument state to a subcommand function. */ 1081struct svndumpfilter_opt_state 1082{ 1083 svn_opt_revision_t start_revision; /* -r X[:Y] is */ 1084 svn_opt_revision_t end_revision; /* not implemented. */ 1085 svn_boolean_t quiet; /* --quiet */ 1086 svn_boolean_t glob; /* --pattern */ 1087 svn_boolean_t version; /* --version */ 1088 svn_boolean_t drop_empty_revs; /* --drop-empty-revs */ 1089 svn_boolean_t drop_all_empty_revs; /* --drop-all-empty-revs */ 1090 svn_boolean_t help; /* --help or -? */ 1091 svn_boolean_t renumber_revs; /* --renumber-revs */ 1092 svn_boolean_t preserve_revprops; /* --preserve-revprops */ 1093 svn_boolean_t skip_missing_merge_sources; 1094 /* --skip-missing-merge-sources */ 1095 const char *targets_file; /* --targets-file */ 1096 apr_array_header_t *prefixes; /* mainargs. */ 1097}; 1098 1099 1100static svn_error_t * 1101parse_baton_initialize(struct parse_baton_t **pb, 1102 struct svndumpfilter_opt_state *opt_state, 1103 svn_boolean_t do_exclude, 1104 apr_pool_t *pool) 1105{ 1106 struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton)); 1107 1108 /* Read the stream from STDIN. Users can redirect a file. */ 1109 SVN_ERR(create_stdio_stream(&(baton->in_stream), 1110 apr_file_open_stdin, pool)); 1111 1112 /* Have the parser dump results to STDOUT. Users can redirect a file. */ 1113 SVN_ERR(create_stdio_stream(&(baton->out_stream), 1114 apr_file_open_stdout, pool)); 1115 1116 baton->do_exclude = do_exclude; 1117 1118 /* Ignore --renumber-revs if there can't possibly be 1119 anything to renumber. */ 1120 baton->do_renumber_revs = 1121 (opt_state->renumber_revs && (opt_state->drop_empty_revs 1122 || opt_state->drop_all_empty_revs)); 1123 1124 baton->drop_empty_revs = opt_state->drop_empty_revs; 1125 baton->drop_all_empty_revs = opt_state->drop_all_empty_revs; 1126 baton->preserve_revprops = opt_state->preserve_revprops; 1127 baton->quiet = opt_state->quiet; 1128 baton->glob = opt_state->glob; 1129 baton->prefixes = opt_state->prefixes; 1130 baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources; 1131 baton->rev_drop_count = 0; /* used to shift revnums while filtering */ 1132 baton->dropped_nodes = apr_hash_make(pool); 1133 baton->renumber_history = apr_hash_make(pool); 1134 baton->last_live_revision = SVN_INVALID_REVNUM; 1135 baton->oldest_original_rev = SVN_INVALID_REVNUM; 1136 baton->allow_deltas = FALSE; 1137 1138 *pb = baton; 1139 return SVN_NO_ERROR; 1140} 1141 1142/* This implements `help` subcommand. */ 1143static svn_error_t * 1144subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1145{ 1146 struct svndumpfilter_opt_state *opt_state = baton; 1147 const char *header = 1148 _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n" 1149 "Type 'svndumpfilter help <subcommand>' for help on a " 1150 "specific subcommand.\n" 1151 "Type 'svndumpfilter --version' to see the program version.\n" 1152 "\n" 1153 "Available subcommands:\n"); 1154 1155 SVN_ERR(svn_opt_print_help4(os, "svndumpfilter", 1156 opt_state ? opt_state->version : FALSE, 1157 opt_state ? opt_state->quiet : FALSE, 1158 /*###opt_state ? opt_state->verbose :*/ FALSE, 1159 NULL, header, cmd_table, options_table, 1160 NULL, NULL, pool)); 1161 1162 return SVN_NO_ERROR; 1163} 1164 1165 1166/* Version compatibility check */ 1167static svn_error_t * 1168check_lib_versions(void) 1169{ 1170 static const svn_version_checklist_t checklist[] = 1171 { 1172 { "svn_subr", svn_subr_version }, 1173 { "svn_repos", svn_repos_version }, 1174 { "svn_delta", svn_delta_version }, 1175 { NULL, NULL } 1176 }; 1177 SVN_VERSION_DEFINE(my_version); 1178 1179 return svn_ver_check_list(&my_version, checklist); 1180} 1181 1182 1183/* Do the real work of filtering. */ 1184static svn_error_t * 1185do_filter(apr_getopt_t *os, 1186 void *baton, 1187 svn_boolean_t do_exclude, 1188 apr_pool_t *pool) 1189{ 1190 struct svndumpfilter_opt_state *opt_state = baton; 1191 struct parse_baton_t *pb; 1192 apr_hash_index_t *hi; 1193 apr_array_header_t *keys; 1194 int i, num_keys; 1195 1196 if (! opt_state->quiet) 1197 { 1198 apr_pool_t *subpool = svn_pool_create(pool); 1199 1200 if (opt_state->glob) 1201 { 1202 SVN_ERR(svn_cmdline_fprintf(stderr, subpool, 1203 do_exclude 1204 ? (opt_state->drop_empty_revs 1205 || opt_state->drop_all_empty_revs) 1206 ? _("Excluding (and dropping empty " 1207 "revisions for) prefix patterns:\n") 1208 : _("Excluding prefix patterns:\n") 1209 : (opt_state->drop_empty_revs 1210 || opt_state->drop_all_empty_revs) 1211 ? _("Including (and dropping empty " 1212 "revisions for) prefix patterns:\n") 1213 : _("Including prefix patterns:\n"))); 1214 } 1215 else 1216 { 1217 SVN_ERR(svn_cmdline_fprintf(stderr, subpool, 1218 do_exclude 1219 ? (opt_state->drop_empty_revs 1220 || opt_state->drop_all_empty_revs) 1221 ? _("Excluding (and dropping empty " 1222 "revisions for) prefixes:\n") 1223 : _("Excluding prefixes:\n") 1224 : (opt_state->drop_empty_revs 1225 || opt_state->drop_all_empty_revs) 1226 ? _("Including (and dropping empty " 1227 "revisions for) prefixes:\n") 1228 : _("Including prefixes:\n"))); 1229 } 1230 1231 for (i = 0; i < opt_state->prefixes->nelts; i++) 1232 { 1233 svn_pool_clear(subpool); 1234 SVN_ERR(svn_cmdline_fprintf 1235 (stderr, subpool, " '%s'\n", 1236 APR_ARRAY_IDX(opt_state->prefixes, i, const char *))); 1237 } 1238 1239 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool)); 1240 svn_pool_destroy(subpool); 1241 } 1242 1243 SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool)); 1244 SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb, 1245 TRUE, NULL, NULL, pool)); 1246 1247 /* The rest of this is just reporting. If we aren't reporting, get 1248 outta here. */ 1249 if (opt_state->quiet) 1250 return SVN_NO_ERROR; 1251 1252 SVN_ERR(svn_cmdline_fputs("\n", stderr, pool)); 1253 1254 if (pb->rev_drop_count) 1255 SVN_ERR(svn_cmdline_fprintf(stderr, pool, 1256 Q_("Dropped %d revision.\n\n", 1257 "Dropped %d revisions.\n\n", 1258 pb->rev_drop_count), 1259 pb->rev_drop_count)); 1260 1261 if (pb->do_renumber_revs) 1262 { 1263 apr_pool_t *subpool = svn_pool_create(pool); 1264 SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"), 1265 stderr, subpool)); 1266 1267 /* Get the keys of the hash, sort them, then print the hash keys 1268 and values, sorted by keys. */ 1269 num_keys = apr_hash_count(pb->renumber_history); 1270 keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t)); 1271 for (hi = apr_hash_first(pool, pb->renumber_history); 1272 hi; 1273 hi = apr_hash_next(hi)) 1274 { 1275 const svn_revnum_t *revnum = svn__apr_hash_index_key(hi); 1276 1277 APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum; 1278 } 1279 qsort(keys->elts, keys->nelts, 1280 keys->elt_size, svn_sort_compare_revisions); 1281 for (i = 0; i < keys->nelts; i++) 1282 { 1283 svn_revnum_t this_key; 1284 struct revmap_t *this_val; 1285 1286 svn_pool_clear(subpool); 1287 this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t); 1288 this_val = apr_hash_get(pb->renumber_history, &this_key, 1289 sizeof(this_key)); 1290 if (this_val->was_dropped) 1291 SVN_ERR(svn_cmdline_fprintf(stderr, subpool, 1292 _(" %ld => (dropped)\n"), 1293 this_key)); 1294 else 1295 SVN_ERR(svn_cmdline_fprintf(stderr, subpool, 1296 " %ld => %ld\n", 1297 this_key, this_val->rev)); 1298 } 1299 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool)); 1300 svn_pool_destroy(subpool); 1301 } 1302 1303 if ((num_keys = apr_hash_count(pb->dropped_nodes))) 1304 { 1305 apr_pool_t *subpool = svn_pool_create(pool); 1306 SVN_ERR(svn_cmdline_fprintf(stderr, subpool, 1307 Q_("Dropped %d node:\n", 1308 "Dropped %d nodes:\n", 1309 num_keys), 1310 num_keys)); 1311 1312 /* Get the keys of the hash, sort them, then print the hash keys 1313 and values, sorted by keys. */ 1314 keys = apr_array_make(pool, num_keys + 1, sizeof(const char *)); 1315 for (hi = apr_hash_first(pool, pb->dropped_nodes); 1316 hi; 1317 hi = apr_hash_next(hi)) 1318 { 1319 const char *path = svn__apr_hash_index_key(hi); 1320 1321 APR_ARRAY_PUSH(keys, const char *) = path; 1322 } 1323 qsort(keys->elts, keys->nelts, keys->elt_size, svn_sort_compare_paths); 1324 for (i = 0; i < keys->nelts; i++) 1325 { 1326 svn_pool_clear(subpool); 1327 SVN_ERR(svn_cmdline_fprintf 1328 (stderr, subpool, " '%s'\n", 1329 (const char *)APR_ARRAY_IDX(keys, i, const char *))); 1330 } 1331 SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool)); 1332 svn_pool_destroy(subpool); 1333 } 1334 1335 return SVN_NO_ERROR; 1336} 1337 1338/* This implements `exclude' subcommand. */ 1339static svn_error_t * 1340subcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1341{ 1342 return do_filter(os, baton, TRUE, pool); 1343} 1344 1345 1346/* This implements `include` subcommand. */ 1347static svn_error_t * 1348subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1349{ 1350 return do_filter(os, baton, FALSE, pool); 1351} 1352 1353 1354 1355/** Main. **/ 1356 1357int 1358main(int argc, const char *argv[]) 1359{ 1360 svn_error_t *err; 1361 apr_status_t apr_err; 1362 apr_pool_t *pool; 1363 1364 const svn_opt_subcommand_desc2_t *subcommand = NULL; 1365 struct svndumpfilter_opt_state opt_state; 1366 apr_getopt_t *os; 1367 int opt_id; 1368 apr_array_header_t *received_opts; 1369 int i; 1370 1371 1372 /* Initialize the app. */ 1373 if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS) 1374 return EXIT_FAILURE; 1375 1376 /* Create our top-level pool. Use a separate mutexless allocator, 1377 * given this application is single threaded. 1378 */ 1379 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 1380 1381 /* Check library versions */ 1382 err = check_lib_versions(); 1383 if (err) 1384 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: "); 1385 1386 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 1387 1388 /* Initialize the FS library. */ 1389 err = svn_fs_initialize(pool); 1390 if (err) 1391 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: "); 1392 1393 if (argc <= 1) 1394 { 1395 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 1396 svn_pool_destroy(pool); 1397 return EXIT_FAILURE; 1398 } 1399 1400 /* Initialize opt_state. */ 1401 memset(&opt_state, 0, sizeof(opt_state)); 1402 opt_state.start_revision.kind = svn_opt_revision_unspecified; 1403 opt_state.end_revision.kind = svn_opt_revision_unspecified; 1404 1405 /* Parse options. */ 1406 err = svn_cmdline__getopt_init(&os, argc, argv, pool); 1407 if (err) 1408 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: "); 1409 1410 os->interleave = 1; 1411 while (1) 1412 { 1413 const char *opt_arg; 1414 1415 /* Parse the next option. */ 1416 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg); 1417 if (APR_STATUS_IS_EOF(apr_err)) 1418 break; 1419 else if (apr_err) 1420 { 1421 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 1422 svn_pool_destroy(pool); 1423 return EXIT_FAILURE; 1424 } 1425 1426 /* Stash the option code in an array before parsing it. */ 1427 APR_ARRAY_PUSH(received_opts, int) = opt_id; 1428 1429 switch (opt_id) 1430 { 1431 case 'h': 1432 case '?': 1433 opt_state.help = TRUE; 1434 break; 1435 case svndumpfilter__version: 1436 opt_state.version = TRUE; 1437 break; 1438 case svndumpfilter__quiet: 1439 opt_state.quiet = TRUE; 1440 break; 1441 case svndumpfilter__glob: 1442 opt_state.glob = TRUE; 1443 break; 1444 case svndumpfilter__drop_empty_revs: 1445 opt_state.drop_empty_revs = TRUE; 1446 break; 1447 case svndumpfilter__drop_all_empty_revs: 1448 opt_state.drop_all_empty_revs = TRUE; 1449 break; 1450 case svndumpfilter__renumber_revs: 1451 opt_state.renumber_revs = TRUE; 1452 break; 1453 case svndumpfilter__preserve_revprops: 1454 opt_state.preserve_revprops = TRUE; 1455 break; 1456 case svndumpfilter__skip_missing_merge_sources: 1457 opt_state.skip_missing_merge_sources = TRUE; 1458 break; 1459 case svndumpfilter__targets: 1460 opt_state.targets_file = opt_arg; 1461 break; 1462 default: 1463 { 1464 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 1465 svn_pool_destroy(pool); 1466 return EXIT_FAILURE; 1467 } 1468 } /* close `switch' */ 1469 } /* close `while' */ 1470 1471 /* Disallow simultaneous use of both --drop-empty-revs and 1472 --drop-all-empty-revs. */ 1473 if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs) 1474 { 1475 err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 1476 _("--drop-empty-revs cannot be used with " 1477 "--drop-all-empty-revs")); 1478 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: "); 1479 } 1480 1481 /* If the user asked for help, then the rest of the arguments are 1482 the names of subcommands to get help on (if any), or else they're 1483 just typos/mistakes. Whatever the case, the subcommand to 1484 actually run is subcommand_help(). */ 1485 if (opt_state.help) 1486 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help"); 1487 1488 /* If we're not running the `help' subcommand, then look for a 1489 subcommand in the first argument. */ 1490 if (subcommand == NULL) 1491 { 1492 if (os->ind >= os->argc) 1493 { 1494 if (opt_state.version) 1495 { 1496 /* Use the "help" subcommand to handle the "--version" option. */ 1497 static const svn_opt_subcommand_desc2_t pseudo_cmd = 1498 { "--version", subcommand_help, {0}, "", 1499 {svndumpfilter__version, /* must accept its own option */ 1500 svndumpfilter__quiet, 1501 } }; 1502 1503 subcommand = &pseudo_cmd; 1504 } 1505 else 1506 { 1507 svn_error_clear(svn_cmdline_fprintf 1508 (stderr, pool, 1509 _("Subcommand argument required\n"))); 1510 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 1511 svn_pool_destroy(pool); 1512 return EXIT_FAILURE; 1513 } 1514 } 1515 else 1516 { 1517 const char *first_arg = os->argv[os->ind++]; 1518 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg); 1519 if (subcommand == NULL) 1520 { 1521 const char* first_arg_utf8; 1522 if ((err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, 1523 pool))) 1524 return svn_cmdline_handle_exit_error(err, pool, 1525 "svndumpfilter: "); 1526 1527 svn_error_clear( 1528 svn_cmdline_fprintf(stderr, pool, 1529 _("Unknown subcommand: '%s'\n"), 1530 first_arg_utf8)); 1531 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 1532 svn_pool_destroy(pool); 1533 return EXIT_FAILURE; 1534 } 1535 } 1536 } 1537 1538 /* If there's a second argument, it's probably [one of] prefixes. 1539 Every subcommand except `help' requires at least one, so we parse 1540 them out here and store in opt_state. */ 1541 1542 if (subcommand->cmd_func != subcommand_help) 1543 { 1544 1545 opt_state.prefixes = apr_array_make(pool, os->argc - os->ind, 1546 sizeof(const char *)); 1547 for (i = os->ind ; i< os->argc; i++) 1548 { 1549 const char *prefix; 1550 1551 /* Ensure that each prefix is UTF8-encoded, in internal 1552 style, and absolute. */ 1553 SVN_INT_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool)); 1554 prefix = svn_relpath__internal_style(prefix, pool); 1555 if (prefix[0] != '/') 1556 prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL); 1557 APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix; 1558 } 1559 1560 if (opt_state.targets_file) 1561 { 1562 svn_stringbuf_t *buffer, *buffer_utf8; 1563 const char *utf8_targets_file; 1564 apr_array_header_t *targets = apr_array_make(pool, 0, 1565 sizeof(const char *)); 1566 1567 /* We need to convert to UTF-8 now, even before we divide 1568 the targets into an array, because otherwise we wouldn't 1569 know what delimiter to use for svn_cstring_split(). */ 1570 1571 SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_targets_file, 1572 opt_state.targets_file, pool)); 1573 1574 SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_targets_file, 1575 pool)); 1576 SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool)); 1577 1578 targets = apr_array_append(pool, 1579 svn_cstring_split(buffer_utf8->data, "\n\r", 1580 TRUE, pool), 1581 targets); 1582 1583 for (i = 0; i < targets->nelts; i++) 1584 { 1585 const char *prefix = APR_ARRAY_IDX(targets, i, const char *); 1586 if (prefix[0] != '/') 1587 prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL); 1588 APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix; 1589 } 1590 } 1591 1592 if (apr_is_empty_array(opt_state.prefixes)) 1593 { 1594 svn_error_clear(svn_cmdline_fprintf 1595 (stderr, pool, 1596 _("\nError: no prefixes supplied.\n"))); 1597 svn_pool_destroy(pool); 1598 return EXIT_FAILURE; 1599 } 1600 } 1601 1602 1603 /* Check that the subcommand wasn't passed any inappropriate options. */ 1604 for (i = 0; i < received_opts->nelts; i++) 1605 { 1606 opt_id = APR_ARRAY_IDX(received_opts, i, int); 1607 1608 /* All commands implicitly accept --help, so just skip over this 1609 when we see it. Note that we don't want to include this option 1610 in their "accepted options" list because it would be awfully 1611 redundant to display it in every commands' help text. */ 1612 if (opt_id == 'h' || opt_id == '?') 1613 continue; 1614 1615 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) 1616 { 1617 const char *optstr; 1618 const apr_getopt_option_t *badopt = 1619 svn_opt_get_option_from_code2(opt_id, options_table, subcommand, 1620 pool); 1621 svn_opt_format_option(&optstr, badopt, FALSE, pool); 1622 if (subcommand->name[0] == '-') 1623 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 1624 else 1625 svn_error_clear(svn_cmdline_fprintf 1626 (stderr, pool, 1627 _("Subcommand '%s' doesn't accept option '%s'\n" 1628 "Type 'svndumpfilter help %s' for usage.\n"), 1629 subcommand->name, optstr, subcommand->name)); 1630 svn_pool_destroy(pool); 1631 return EXIT_FAILURE; 1632 } 1633 } 1634 1635 /* Run the subcommand. */ 1636 err = (*subcommand->cmd_func)(os, &opt_state, pool); 1637 if (err) 1638 { 1639 /* For argument-related problems, suggest using the 'help' 1640 subcommand. */ 1641 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS 1642 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) 1643 { 1644 err = svn_error_quick_wrap(err, 1645 _("Try 'svndumpfilter help' for more " 1646 "info")); 1647 } 1648 return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: "); 1649 } 1650 else 1651 { 1652 svn_pool_destroy(pool); 1653 1654 /* Flush stdout, making sure the user will see any print errors. */ 1655 SVN_INT_ERR(svn_cmdline_fflush(stdout)); 1656 return EXIT_SUCCESS; 1657 } 1658} 1659