1/* 2 * ambient_depth_filter_editor.c -- provide a svn_delta_editor_t which wraps 3 * another editor and provides 4 * *ambient* depth-based filtering 5 * 6 * ==================================================================== 7 * Licensed to the Apache Software Foundation (ASF) under one 8 * or more contributor license agreements. See the NOTICE file 9 * distributed with this work for additional information 10 * regarding copyright ownership. The ASF licenses this file 11 * to you under the Apache License, Version 2.0 (the 12 * "License"); you may not use this file except in compliance 13 * with the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, 18 * software distributed under the License is distributed on an 19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 * KIND, either express or implied. See the License for the 21 * specific language governing permissions and limitations 22 * under the License. 23 * ==================================================================== 24 */ 25 26#include "svn_delta.h" 27#include "svn_wc.h" 28#include "svn_dirent_uri.h" 29#include "svn_path.h" 30 31#include "wc.h" 32 33/* 34 Notes on the general depth-filtering strategy. 35 ============================================== 36 37 When a depth-aware (>= 1.5) client pulls an update from a 38 non-depth-aware server, the server may send back too much data, 39 because it doesn't hear what the client tells it about the 40 "requested depth" of the update (the "foo" in "--depth=foo"), nor 41 about the "ambient depth" of each working copy directory. 42 43 For example, suppose a 1.5 client does this against a 1.4 server: 44 45 $ svn co --depth=empty -rSOME_OLD_REV http://url/repos/blah/ wc 46 $ cd wc 47 $ svn up 48 49 In the initial checkout, the requested depth is 'empty', so the 50 depth-filtering editor (see libsvn_delta/depth_filter_editor.c) 51 that wraps the main update editor transparently filters out all 52 the unwanted calls. 53 54 In the 'svn up', the requested depth is unspecified, meaning that 55 the ambient depth(s) of the working copy should be preserved. 56 Since there's only one directory, and its depth is 'empty', 57 clearly we should filter out or render no-ops all editor calls 58 after open_root(), except maybe for change_dir_prop() on the 59 top-level directory. (Note that the server will have stuff to 60 send down, because we checked out at an old revision in the first 61 place, to set up this scenario.) 62 63 The depth-filtering editor won't help us here. It only filters 64 based on the requested depth, it never looks in the working copy 65 to get ambient depths. So the update editor itself will have to 66 filter out the unwanted calls -- or better yet, it will have to 67 be wrapped in a filtering editor that does the job. 68 69 This is that filtering editor. 70 71 Most of the work is done at the moment of baton construction. 72 When a file or dir is opened, we create its baton with the 73 appropriate ambient depth, either taking the depth directly from 74 the corresponding working copy object (if available), or from its 75 parent baton. In the latter case, we don't just copy the parent 76 baton's depth, but rather use it to choose the correct depth for 77 this child. The usual depth demotion rules apply, with the 78 additional stipulation that as soon as we find a subtree is not 79 present at all, due to being omitted for depth reasons, we set the 80 ambiently_excluded flag in its baton, which signals that 81 all descendant batons should be ignored. 82 (In fact, we may just re-use the parent baton, since none of the 83 other fields will be used anyway.) 84 85 See issues #2842 and #2897 for more. 86*/ 87 88 89/*** Batons, and the Toys That Create Them ***/ 90 91struct edit_baton 92{ 93 const svn_delta_editor_t *wrapped_editor; 94 void *wrapped_edit_baton; 95 svn_wc__db_t *db; 96 const char *anchor_abspath; 97 const char *target; 98}; 99 100struct file_baton 101{ 102 svn_boolean_t ambiently_excluded; 103 struct edit_baton *edit_baton; 104 void *wrapped_baton; 105}; 106 107struct dir_baton 108{ 109 svn_boolean_t ambiently_excluded; 110 svn_depth_t ambient_depth; 111 struct edit_baton *edit_baton; 112 const char *abspath; 113 void *wrapped_baton; 114}; 115 116/* Fetch the STATUS, KIND and DEPTH of the base node at LOCAL_ABSPATH. 117 * If there is no such base node, report 'normal', 'unknown' and 'unknown' 118 * respectively. 119 * 120 * STATUS and/or DEPTH may be NULL if not wanted; KIND must not be NULL. 121 */ 122static svn_error_t * 123ambient_read_info(svn_wc__db_status_t *status, 124 svn_node_kind_t *kind, 125 svn_depth_t *depth, 126 svn_wc__db_t *db, 127 const char *local_abspath, 128 apr_pool_t *scratch_pool) 129{ 130 svn_error_t *err; 131 SVN_ERR_ASSERT(kind != NULL); 132 133 err = svn_wc__db_base_get_info(status, kind, NULL, NULL, NULL, NULL, 134 NULL, NULL, NULL, depth, NULL, NULL, 135 NULL, NULL, NULL, NULL, 136 db, local_abspath, 137 scratch_pool, scratch_pool); 138 139 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 140 { 141 svn_error_clear(err); 142 143 *kind = svn_node_unknown; 144 if (status) 145 *status = svn_wc__db_status_normal; 146 if (depth) 147 *depth = svn_depth_unknown; 148 149 return SVN_NO_ERROR; 150 } 151 else 152 SVN_ERR(err); 153 154 return SVN_NO_ERROR; 155} 156 157/* */ 158static svn_error_t * 159make_dir_baton(struct dir_baton **d_p, 160 const char *path, 161 struct edit_baton *eb, 162 struct dir_baton *pb, 163 svn_boolean_t added, 164 apr_pool_t *pool) 165{ 166 struct dir_baton *d; 167 168 SVN_ERR_ASSERT(path || (! pb)); 169 170 if (pb && pb->ambiently_excluded) 171 { 172 /* Just re-use the parent baton, since the only field that 173 matters is ambiently_excluded. */ 174 *d_p = pb; 175 return SVN_NO_ERROR; 176 } 177 178 /* Okay, no easy out, so allocate and initialize a dir baton. */ 179 d = apr_pcalloc(pool, sizeof(*d)); 180 181 if (path) 182 d->abspath = svn_dirent_join(eb->anchor_abspath, path, pool); 183 else 184 d->abspath = apr_pstrdup(pool, eb->anchor_abspath); 185 186 /* The svn_depth_unknown means that: 1) pb is the anchor; 2) there 187 is an non-null target, for which we are preparing the baton. 188 This enables explicitly pull in the target. */ 189 if (pb && pb->ambient_depth != svn_depth_unknown) 190 { 191 svn_boolean_t exclude; 192 svn_wc__db_status_t status; 193 svn_node_kind_t kind; 194 svn_boolean_t exists = TRUE; 195 196 if (!added) 197 { 198 SVN_ERR(ambient_read_info(&status, &kind, NULL, 199 eb->db, d->abspath, pool)); 200 } 201 else 202 { 203 status = svn_wc__db_status_not_present; 204 kind = svn_node_unknown; 205 } 206 207 exists = (kind != svn_node_unknown); 208 209 if (pb->ambient_depth == svn_depth_empty 210 || pb->ambient_depth == svn_depth_files) 211 { 212 /* This is not a depth upgrade, and the parent directory is 213 depth==empty or depth==files. So if the parent doesn't 214 already have an entry for the new dir, then the parent 215 doesn't want the new dir at all, thus we should initialize 216 it with ambiently_excluded=TRUE. */ 217 exclude = !exists; 218 } 219 else 220 { 221 /* If the parent expect all children by default, only exclude 222 it whenever it is explicitly marked as exclude. */ 223 exclude = exists && (status == svn_wc__db_status_excluded); 224 } 225 if (exclude) 226 { 227 d->ambiently_excluded = TRUE; 228 *d_p = d; 229 return SVN_NO_ERROR; 230 } 231 } 232 233 d->edit_baton = eb; 234 /* We'll initialize this differently in add_directory and 235 open_directory. */ 236 d->ambient_depth = svn_depth_unknown; 237 238 *d_p = d; 239 return SVN_NO_ERROR; 240} 241 242/* */ 243static svn_error_t * 244make_file_baton(struct file_baton **f_p, 245 struct dir_baton *pb, 246 const char *path, 247 svn_boolean_t added, 248 apr_pool_t *pool) 249{ 250 struct file_baton *f = apr_pcalloc(pool, sizeof(*f)); 251 struct edit_baton *eb = pb->edit_baton; 252 svn_wc__db_status_t status; 253 svn_node_kind_t kind; 254 const char *abspath; 255 256 SVN_ERR_ASSERT(path); 257 258 if (pb->ambiently_excluded) 259 { 260 f->ambiently_excluded = TRUE; 261 *f_p = f; 262 return SVN_NO_ERROR; 263 } 264 265 abspath = svn_dirent_join(eb->anchor_abspath, path, pool); 266 267 if (!added) 268 { 269 SVN_ERR(ambient_read_info(&status, &kind, NULL, 270 eb->db, abspath, pool)); 271 } 272 else 273 { 274 status = svn_wc__db_status_not_present; 275 kind = svn_node_unknown; 276 } 277 278 if (pb->ambient_depth == svn_depth_empty) 279 { 280 /* This is not a depth upgrade, and the parent directory is 281 depth==empty. So if the parent doesn't 282 already have an entry for the file, then the parent 283 doesn't want to hear about the file at all. */ 284 285 if (status == svn_wc__db_status_not_present 286 || status == svn_wc__db_status_server_excluded 287 || status == svn_wc__db_status_excluded 288 || kind == svn_node_unknown) 289 { 290 f->ambiently_excluded = TRUE; 291 *f_p = f; 292 return SVN_NO_ERROR; 293 } 294 } 295 296 /* If pb->ambient_depth == svn_depth_unknown we are pulling 297 in new nodes */ 298 if (pb->ambient_depth != svn_depth_unknown 299 && status == svn_wc__db_status_excluded) 300 { 301 f->ambiently_excluded = TRUE; 302 *f_p = f; 303 return SVN_NO_ERROR; 304 } 305 306 f->edit_baton = pb->edit_baton; 307 308 *f_p = f; 309 return SVN_NO_ERROR; 310} 311 312 313/*** Editor Functions ***/ 314 315/* An svn_delta_editor_t function. */ 316static svn_error_t * 317set_target_revision(void *edit_baton, 318 svn_revnum_t target_revision, 319 apr_pool_t *pool) 320{ 321 struct edit_baton *eb = edit_baton; 322 323 /* Nothing depth-y to filter here. */ 324 return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, 325 target_revision, pool); 326} 327 328/* An svn_delta_editor_t function. */ 329static svn_error_t * 330open_root(void *edit_baton, 331 svn_revnum_t base_revision, 332 apr_pool_t *pool, 333 void **root_baton) 334{ 335 struct edit_baton *eb = edit_baton; 336 struct dir_baton *b; 337 338 SVN_ERR(make_dir_baton(&b, NULL, eb, NULL, FALSE, pool)); 339 *root_baton = b; 340 341 if (b->ambiently_excluded) 342 return SVN_NO_ERROR; 343 344 if (! *eb->target) 345 { 346 /* For an update with a NULL target, this is equivalent to open_dir(): */ 347 svn_node_kind_t kind; 348 svn_wc__db_status_t status; 349 svn_depth_t depth; 350 351 /* Read the depth from the entry. */ 352 SVN_ERR(ambient_read_info(&status, &kind, &depth, 353 eb->db, eb->anchor_abspath, 354 pool)); 355 356 if (kind != svn_node_unknown 357 && status != svn_wc__db_status_not_present 358 && status != svn_wc__db_status_excluded 359 && status != svn_wc__db_status_server_excluded) 360 { 361 b->ambient_depth = depth; 362 } 363 } 364 365 return eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision, 366 pool, &b->wrapped_baton); 367} 368 369/* An svn_delta_editor_t function. */ 370static svn_error_t * 371delete_entry(const char *path, 372 svn_revnum_t base_revision, 373 void *parent_baton, 374 apr_pool_t *pool) 375{ 376 struct dir_baton *pb = parent_baton; 377 struct edit_baton *eb = pb->edit_baton; 378 379 if (pb->ambiently_excluded) 380 return SVN_NO_ERROR; 381 382 if (pb->ambient_depth < svn_depth_immediates) 383 { 384 /* If the entry we want to delete doesn't exist, that's OK. 385 It's probably an old server that doesn't understand 386 depths. */ 387 svn_node_kind_t kind; 388 svn_wc__db_status_t status; 389 const char *abspath; 390 391 abspath = svn_dirent_join(eb->anchor_abspath, path, pool); 392 393 SVN_ERR(ambient_read_info(&status, &kind, NULL, 394 eb->db, abspath, pool)); 395 396 if (kind == svn_node_unknown 397 || status == svn_wc__db_status_not_present 398 || status == svn_wc__db_status_excluded 399 || status == svn_wc__db_status_server_excluded) 400 return SVN_NO_ERROR; 401 } 402 403 return eb->wrapped_editor->delete_entry(path, base_revision, 404 pb->wrapped_baton, pool); 405} 406 407/* An svn_delta_editor_t function. */ 408static svn_error_t * 409add_directory(const char *path, 410 void *parent_baton, 411 const char *copyfrom_path, 412 svn_revnum_t copyfrom_revision, 413 apr_pool_t *pool, 414 void **child_baton) 415{ 416 struct dir_baton *pb = parent_baton; 417 struct edit_baton *eb = pb->edit_baton; 418 struct dir_baton *b = NULL; 419 420 SVN_ERR(make_dir_baton(&b, path, eb, pb, TRUE, pool)); 421 *child_baton = b; 422 423 if (b->ambiently_excluded) 424 return SVN_NO_ERROR; 425 426 /* It's not excluded, so what should we treat the ambient depth as 427 being? */ 428 if (strcmp(eb->target, path) == 0) 429 { 430 /* The target of the edit is being added, so make it 431 infinity. */ 432 b->ambient_depth = svn_depth_infinity; 433 } 434 else if (pb->ambient_depth == svn_depth_immediates) 435 { 436 b->ambient_depth = svn_depth_empty; 437 } 438 else 439 { 440 /* There may be a requested depth < svn_depth_infinity, but 441 that's okay, libsvn_delta/depth_filter_editor.c will filter 442 further calls out for us anyway, and the update_editor will 443 do the right thing when it creates the directory. */ 444 b->ambient_depth = svn_depth_infinity; 445 } 446 447 return eb->wrapped_editor->add_directory(path, pb->wrapped_baton, 448 copyfrom_path, 449 copyfrom_revision, 450 pool, &b->wrapped_baton); 451} 452 453/* An svn_delta_editor_t function. */ 454static svn_error_t * 455open_directory(const char *path, 456 void *parent_baton, 457 svn_revnum_t base_revision, 458 apr_pool_t *pool, 459 void **child_baton) 460{ 461 struct dir_baton *pb = parent_baton; 462 struct edit_baton *eb = pb->edit_baton; 463 struct dir_baton *b; 464 const char *local_abspath; 465 svn_node_kind_t kind; 466 svn_wc__db_status_t status; 467 svn_depth_t depth; 468 469 SVN_ERR(make_dir_baton(&b, path, eb, pb, FALSE, pool)); 470 *child_baton = b; 471 472 if (b->ambiently_excluded) 473 return SVN_NO_ERROR; 474 475 SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton, 476 base_revision, pool, 477 &b->wrapped_baton)); 478 /* Note that for the update editor, the open_directory above will 479 flush the logs of pb's directory, which might be important for 480 this svn_wc_entry call. */ 481 482 local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); 483 484 SVN_ERR(ambient_read_info(&status, &kind, &depth, 485 eb->db, local_abspath, pool)); 486 487 if (kind != svn_node_unknown 488 && status != svn_wc__db_status_not_present 489 && status != svn_wc__db_status_excluded 490 && status != svn_wc__db_status_server_excluded) 491 { 492 b->ambient_depth = depth; 493 } 494 495 return SVN_NO_ERROR; 496} 497 498/* An svn_delta_editor_t function. */ 499static svn_error_t * 500add_file(const char *path, 501 void *parent_baton, 502 const char *copyfrom_path, 503 svn_revnum_t copyfrom_revision, 504 apr_pool_t *pool, 505 void **child_baton) 506{ 507 struct dir_baton *pb = parent_baton; 508 struct edit_baton *eb = pb->edit_baton; 509 struct file_baton *b = NULL; 510 511 SVN_ERR(make_file_baton(&b, pb, path, TRUE, pool)); 512 *child_baton = b; 513 514 if (b->ambiently_excluded) 515 return SVN_NO_ERROR; 516 517 return eb->wrapped_editor->add_file(path, pb->wrapped_baton, 518 copyfrom_path, copyfrom_revision, 519 pool, &b->wrapped_baton); 520} 521 522/* An svn_delta_editor_t function. */ 523static svn_error_t * 524open_file(const char *path, 525 void *parent_baton, 526 svn_revnum_t base_revision, 527 apr_pool_t *pool, 528 void **child_baton) 529{ 530 struct dir_baton *pb = parent_baton; 531 struct edit_baton *eb = pb->edit_baton; 532 struct file_baton *b; 533 534 SVN_ERR(make_file_baton(&b, pb, path, FALSE, pool)); 535 *child_baton = b; 536 if (b->ambiently_excluded) 537 return SVN_NO_ERROR; 538 539 return eb->wrapped_editor->open_file(path, pb->wrapped_baton, 540 base_revision, pool, 541 &b->wrapped_baton); 542} 543 544/* An svn_delta_editor_t function. */ 545static svn_error_t * 546apply_textdelta(void *file_baton, 547 const char *base_checksum, 548 apr_pool_t *pool, 549 svn_txdelta_window_handler_t *handler, 550 void **handler_baton) 551{ 552 struct file_baton *fb = file_baton; 553 struct edit_baton *eb = fb->edit_baton; 554 555 /* For filtered files, we just consume the textdelta. */ 556 if (fb->ambiently_excluded) 557 { 558 *handler = svn_delta_noop_window_handler; 559 *handler_baton = NULL; 560 return SVN_NO_ERROR; 561 } 562 563 return eb->wrapped_editor->apply_textdelta(fb->wrapped_baton, 564 base_checksum, pool, 565 handler, handler_baton); 566} 567 568/* An svn_delta_editor_t function. */ 569static svn_error_t * 570close_file(void *file_baton, 571 const char *text_checksum, 572 apr_pool_t *pool) 573{ 574 struct file_baton *fb = file_baton; 575 struct edit_baton *eb = fb->edit_baton; 576 577 if (fb->ambiently_excluded) 578 return SVN_NO_ERROR; 579 580 return eb->wrapped_editor->close_file(fb->wrapped_baton, 581 text_checksum, pool); 582} 583 584/* An svn_delta_editor_t function. */ 585static svn_error_t * 586absent_file(const char *path, 587 void *parent_baton, 588 apr_pool_t *pool) 589{ 590 struct dir_baton *pb = parent_baton; 591 struct edit_baton *eb = pb->edit_baton; 592 593 if (pb->ambiently_excluded) 594 return SVN_NO_ERROR; 595 596 return eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool); 597} 598 599/* An svn_delta_editor_t function. */ 600static svn_error_t * 601close_directory(void *dir_baton, 602 apr_pool_t *pool) 603{ 604 struct dir_baton *db = dir_baton; 605 struct edit_baton *eb = db->edit_baton; 606 607 if (db->ambiently_excluded) 608 return SVN_NO_ERROR; 609 610 return eb->wrapped_editor->close_directory(db->wrapped_baton, pool); 611} 612 613/* An svn_delta_editor_t function. */ 614static svn_error_t * 615absent_directory(const char *path, 616 void *parent_baton, 617 apr_pool_t *pool) 618{ 619 struct dir_baton *pb = parent_baton; 620 struct edit_baton *eb = pb->edit_baton; 621 622 /* Don't report absent items in filtered directories. */ 623 if (pb->ambiently_excluded) 624 return SVN_NO_ERROR; 625 626 return eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, pool); 627} 628 629/* An svn_delta_editor_t function. */ 630static svn_error_t * 631change_file_prop(void *file_baton, 632 const char *name, 633 const svn_string_t *value, 634 apr_pool_t *pool) 635{ 636 struct file_baton *fb = file_baton; 637 struct edit_baton *eb = fb->edit_baton; 638 639 if (fb->ambiently_excluded) 640 return SVN_NO_ERROR; 641 642 return eb->wrapped_editor->change_file_prop(fb->wrapped_baton, 643 name, value, pool); 644} 645 646/* An svn_delta_editor_t function. */ 647static svn_error_t * 648change_dir_prop(void *dir_baton, 649 const char *name, 650 const svn_string_t *value, 651 apr_pool_t *pool) 652{ 653 struct dir_baton *db = dir_baton; 654 struct edit_baton *eb = db->edit_baton; 655 656 if (db->ambiently_excluded) 657 return SVN_NO_ERROR; 658 659 return eb->wrapped_editor->change_dir_prop(db->wrapped_baton, 660 name, value, pool); 661} 662 663/* An svn_delta_editor_t function. */ 664static svn_error_t * 665close_edit(void *edit_baton, 666 apr_pool_t *pool) 667{ 668 struct edit_baton *eb = edit_baton; 669 return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool); 670} 671 672svn_error_t * 673svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor, 674 void **edit_baton, 675 svn_wc__db_t *db, 676 const char *anchor_abspath, 677 const char *target, 678 const svn_delta_editor_t *wrapped_editor, 679 void *wrapped_edit_baton, 680 apr_pool_t *result_pool) 681{ 682 svn_delta_editor_t *depth_filter_editor; 683 struct edit_baton *eb; 684 685 SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); 686 687 depth_filter_editor = svn_delta_default_editor(result_pool); 688 depth_filter_editor->set_target_revision = set_target_revision; 689 depth_filter_editor->open_root = open_root; 690 depth_filter_editor->delete_entry = delete_entry; 691 depth_filter_editor->add_directory = add_directory; 692 depth_filter_editor->open_directory = open_directory; 693 depth_filter_editor->change_dir_prop = change_dir_prop; 694 depth_filter_editor->close_directory = close_directory; 695 depth_filter_editor->absent_directory = absent_directory; 696 depth_filter_editor->add_file = add_file; 697 depth_filter_editor->open_file = open_file; 698 depth_filter_editor->apply_textdelta = apply_textdelta; 699 depth_filter_editor->change_file_prop = change_file_prop; 700 depth_filter_editor->close_file = close_file; 701 depth_filter_editor->absent_file = absent_file; 702 depth_filter_editor->close_edit = close_edit; 703 704 eb = apr_pcalloc(result_pool, sizeof(*eb)); 705 eb->wrapped_editor = wrapped_editor; 706 eb->wrapped_edit_baton = wrapped_edit_baton; 707 eb->db = db; 708 eb->anchor_abspath = anchor_abspath; 709 eb->target = target; 710 711 *editor = depth_filter_editor; 712 *edit_baton = eb; 713 714 return SVN_NO_ERROR; 715} 716